This is my first post, so please don't jump all over me...
I wanted to backup the .avi files and picture files on DVDs such that as plain files, so that I can simply mount the DVDs and play/display them on a different computer. I found multicd by Dan Born
http://danborn.net/ and thought it would do the trick. However, I ended up tweaking it so much that it essentially resulted in a rewrite. It's now done, and I thought that I should share it with this group, that has helped me so much so far!
In the end I have two new buttons in the KnoppMyth screen, "Backup Videos to DVD" and "Backup Gallery to DVD". When you press one, you'll get Xdialog windows that will prompt you for action when needed. At the end of each DVD burn, the list of files that have been put on that DVD is displayed for you to review (and write on the DVD, before you store it away).
You'll need to have dvd burning software (growisofs), perl, and Xdialog installed.
Here is what you need to do (may need some care with unintended line breaks!)
1. vi /myth/bin/mythdvdbackup # insert the follwing test up to #######
#!/bin/bash
PROGRAM=multicd
PID=0
TMP=`ps -edalf | grep -v grep | grep perl | grep $PROGRAM | sed -e 's/ */ /g' | cut -d " " -f 16`
for P in $TMP; do
BASE=`basename $P`
if test "$BASE" == "$PROGRAM" ; then
PID=`ps -edalf | grep -v grep | grep perl | grep $PROGRAM | sed -e 's/ */ /g' | cut -d " " -f 4`
fi
done
if [ $PID == 0 ] ; then
nice /myth/bin/mythdvdbackup2 $@ &
sleep 2
else
Xdialog --infobox "Backup already running" 300x200 3000 &
fi
#######
2. vi /myth/bin/mythdvdbackup2 # insert the follwing test up to ######
#!/bin/bash
BACKUPDIR=/myth/backup
INDEX=$BACKUPDIR/index_%d
EXCLUDES=/myth/tmp/mythbackup.exclude
LOGDIR=/myth/log
cat $BACKUPDIR/index* | grep -e $1 | grep -v 'CD number' | sed -e 's/^ *//' > $EXCLUDES
Xdialog --infobox "Starting backup of $1 in the background" 300x200 2000 &
# add option --noburn to only try things
/myth/bin/dvdburn --files $1 --exclude_list=$EXCLUDES --index_file=$INDEX > $LOGDIR/mythbackup.log 2>&1
Xdialog --msgbox "Backup of $1 terminated" 300x200
######
3. vi /myth/bin/dvdburn # insert the follwing test up to ######
#!/usr/bin/perl
require 5;
use strict;
use warnings;
use integer;
use Fcntl;
use Cwd qw(chdir cwd);
use FileHandle qw(autoflush);
use Math::BigInt;
#
# Configuration
#
my $index_file;
my $backup_file;
my $exclude_file;
my $dvd_size = "4482M";
my $eject_cmd = "eject /dev/dvd";
my $burn_cmd = "growisofs -use-the-force-luke=tty -Z /dev/dvd -R -J";
my $noburn = 0;
my @backups = ();
my @excludes = ();
#
# if configuration file is found, allow it to overwrite preset data
#
get_userconfig();
if ( defined $backup_file ) {
push @backups, makelist($backup_file);
}
if ( defined $exclude_file ) {
push @excludes, makelist($exclude_file);
}
dumpsettings();
STDOUT->autoflush(1);
STDERR->autoflush(1);
# Constants related to the values returned by the stat function.
#
my $MODE_I = 2;
my $UID_I = 4;
my $GID_I = 5;
my $SIZE_I = 7;
my $ATIME_I = 8;
my $MTIME_I = 9;
my $BLKSIZE = 11;
my $TYPE_MASK = 0770000;
my $MODE_MASK = 0007777;
my $TYPE_DIR = 04;
my $TYPE_FILE = 010;
my $TYPE_SYMLINK = 012;
$ENV{PATH} = "$ENV{PATH}:/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin";
# A tree node is represented by a hash ref and looks like:
#
# {
# name => filename
# uid => user id of owner
# gid => group id of owner
# type => type of file
# mode => permissions of file
# atime => access time
# mtime => modification time
# parent => parent of this node
# kids => reference to an array of the children of this node
# }
my($type_mode, $uid, $gid, $type, $mode, $size, $atime, $mtime, $pref_blk);
my ($blk_size, $use_size, $tot_size);
$blk_size=2048;
my $dvd_count = 1;
# Analyze the input files and expand directories if available
my @burn_files;
my @input_files;
my $file;
my %exclude_files;
for (my $i = 0; $i < @excludes; $i++) {
$exclude_files{$excludes[$i]} = 1;
}
PREPARE:
foreach $file ( @backups ) {
unless(($type_mode, $uid, $gid, $size, $atime, $mtime, $pref_blk) =
(lstat $file)[$MODE_I, $UID_I, $GID_I, $SIZE_I, $ATIME_I,
$MTIME_I, $BLKSIZE])
{
warn "couldn't lstat $file: $!\n";
next PREPARE;
}
$type = ($type_mode & $TYPE_MASK) / ($MODE_MASK + 1);
if ($type == $TYPE_DIR) {
if (opendir DIR, $file) {
my $f;
while (defined($f = readdir(DIR))) {
if ( $f ne '.' and $f ne '..' ) {
push @backups, "$file/$f";
}
}
} else {
warn "couldn't read directory $file: $!\n";
}
}
elsif ($type == $TYPE_FILE) {
push @input_files, $file;
}
}
my $done;
DVD:
while (not defined $done) {
@burn_files = ();
$tot_size = $dvd_size;
FILE:
foreach $file ( @input_files ) {
if ( defined $exclude_files{$file} ) {
next FILE;
}
foreach (@burn_files) {
if ( "$_" eq "$file" ) {
next FILE;
}
}
unless(($type_mode, $uid, $gid, $size, $atime, $mtime, $pref_blk) =
(lstat $file)[$MODE_I, $UID_I, $GID_I, $SIZE_I, $ATIME_I,
$MTIME_I, $BLKSIZE])
{
warn "couldn't lstat $file: $!\n";
next COPY_FILE;
}
$type = ($type_mode & $TYPE_MASK) / ($MODE_MASK + 1);
# Skip directory names and symbolic links
if ($type == $TYPE_SYMLINK or $type == $TYPE_DIR) {
next FILE;
}
$use_size = $size / $blk_size;
$use_size += 1;
$use_size *= $blk_size;
if ( $use_size < $tot_size ) {
push @burn_files, $file;
$tot_size -= $use_size;
}
}
if ( $#burn_files < 0 ) {
last DVD;
}
# Prepare to run dvdrecord.
my $dvdrecord = "$burn_cmd";
foreach (@burn_files) {
s/"/\\"/g;
$dvdrecord .= ' "'.$_.'"';
}
# If running without multi, burn the cd here.
print "Burning DVD $dvd_count ...\n";
print "burn command: $dvdrecord\n";
open SAVEOUT, ">&STDOUT";
open STDOUT, ">&STDERR";
my $status = 0;
if ( $noburn == 0 ) {
$status = system $dvdrecord;
}
while ($status != 0) {
while ($status != 0) {
my $cont = cont_prompt($dvd_count, $!);
if ($cont eq 's') {
open STDOUT, ">&SAVEOUT";
close SAVEOUT;
last;
} elsif ($cont eq 'q') {
open STDOUT, ">&SAVEOUT";
close SAVEOUT;
last DVD;
} elsif ($cont eq 'r') {
$status = system $dvdrecord;
}
}
}
if (defined $eject_cmd) {
system $eject_cmd;
}
open STDOUT, ">&SAVEOUT";
close SAVEOUT;
if ($status == 0) {
print "DVD number " . $dvd_count . " created successfully.\n\n";
if (defined $index_file) {
system "echo 'DVD number $dvd_count:' >> '$index_file'";
foreach (@burn_files) {
if ( $noburn == 0) {
system "echo \" $_\" >> \"$index_file\"";
}
$exclude_files{$_} = 1;
}
}
}
if (defined $index_file and $noburn == 0) {
system "Xdialog --no-cancel --tailbox '$index_file' 600x300";
}
$dvd_count++;
}
print "\nAll done.\n";
#
# End main program.
#
##
# $code cont_prompt($num[, $errormsg])
#
# Prompts the user to continue after a burn has failed. Returns true if user
# wants to continue, false otherwise. The parameters should be the number of
# the DVD to use in the print statements, and the error message if any. The
# error message is an optional parameter.
#
sub cont_prompt {
my($num, $errormsg) = @_;
my $prob = "";
if ($errormsg) {
$prob = "Burn of DVD number $num failed: $errormsg.";
} else {
$prob = "Burn of DVD number $num failed.";
}
{
print "(s)kip CD, (q)uit program, or (r)etry? ";
$_ = readpipe "Xdialog --stdout --no-tags --no-cancel --radiolist '$prob' 300x300 4 r Retry y s Skip n q Abort n";
chomp;
if (/([sqr])/i) {
return lc($1);
} else {
redo;
}
}
}
##
# boolean get_userconfig(\%config)
#
# Get the configuration info for this user and put it in the hash
# referenced by $cfg.
#
# Configuration can come from any of three places:
# A global configuration file: /etc/dvdburnrc
# A config file in a user's home directory: $HOME/.dvdburnrc
# The command line.
# Options given on the command line have the same name as the ones in
# the config files. If the same option is specified in more than one
# place, then options in the home directory file override the options
# in the global file, and the command line options override the other
# two.
#
# Returns true if successful.
#
sub get_userconfig {
my($cfg) = @_;
my $global_cf_file = '/etc/dvdburnrc';
my $local_cf_file = "$ENV{HOME}/.dvdburnrc";
my @optnames = qw(dvd_size files exclude dvd_burn noburn
help checkrc files_list exclude_list index_file);
my @booleans = qw(noburn help checkrc);
my @listvalues = qw(files exclude);
my %optnames;
@optnames{@optnames} = (1) x @optnames;
my %booleans;
@booleans{@booleans} = (1) x @booleans;
my %listvalues;
@listvalues{@listvalues} = (1) x @listvalues;
my $key;
# Load things from the global file, and then override those with whatever is
# found in a local file.
my $config_file;
foreach $config_file ($global_cf_file, $local_cf_file) {
open CF, $config_file or next;
while (<CF>) {
if (/^(.*?)\#/) { # Strip away comments.
$_ = $1;
}
next if /^\s*$/; # Skip blank lines.
if (/^\s*(.*?)\s*$/) { # Trim whitespace off the ends of the line.
$_ = $1;
}
my $value;
($key, $value) = split /\s*=\s*/, $_, 2;
unless ($optnames{$key}) {
print "Unknown option: '$key'. Check your $config_file file.\n";
close CF;
return 0;
}
if ($listvalues{$key}) {
$cfg->{$key} = get_listvalue($value);
} else {
$cfg->{$key} = $value;
}
}
close CF;
}
# Handle command line args. Command line args override values from the two
# config files.
while (@ARGV) {
$_ = shift @ARGV;
unless(/^--/) {
print "Bad command line arg: '$_'\n";
return 0;
}
my $value;
$key = '';
substr($_, 0, 2) = '';
if (/=/) {
($key, $value) = split /=/, $_, 2;
if ($listvalues{$key}) {
$value = [$value];
}
} else {
$key = $_;
while (@ARGV) {
$_ = shift @ARGV;
if (/^--/) {
unshift @ARGV, $_;
last;
}
$value = 1;
if ($listvalues{$key}) {
push @{$cfg->{$key}}, $_;
} else {
$value = $_;
last;
}
}
}
unless ($optnames{$key}) {
die "Unknown command line option: '$key'.\n";
}
if (not defined $value) {
if ($booleans{$key}) {
$cfg->{$key} = 1;
} elsif ($key eq 'exclude') {
delete $cfg->{exclude};
} elsif ($cfg->{$key} != 1) {
die "Value for command line option '$key' not specified.\n";
}
} elsif (!$listvalues{$key}) {
$cfg->{$key} = $value;
}
}
if (defined $cfg->{dvd_size}) {
$dvd_size = $cfg->{dvd_size};
}
if (defined $cfg->{dvd_burn}) {
$burn_cmd = $cfg->{dvd_burn};
}
if (!defined $dvd_size) {
die "ERROR: media size (dvd_size) not configured\n";
}
if (!defined $burn_cmd) {
die "ERROR: DVD burn command (dvd_burn) not configured\n";
}
if (not (defined $cfg->{files} or defined $cfg->{files_list})) {
die "Must specify one of either 'files' or 'files_list'.\n";
}
# Compute "since" time.
if (defined $cfg->{since}) {
my %t = (d => 86400, h => 3600, m => 60, s => 1);
my $time = time();
foreach my $unit (split /\s+/, $cfg->{since}) {
if ($unit =~ /^(\d+)([dhms])$/i) {
$time -= $1 * $t{$2};
}
}
$cfg->{since} = $time;
}
# Make sure all path values are absolute.
foreach (@{$cfg->{files}}, @{$cfg->{exclude}}) {
next unless defined $_;
if (/\.\./ or not m|^/|) {
print "$_: Relative paths are bad. Absolute paths only.\n";
}
}
if (defined $cfg->{checkrc}) {
die "Configuration successfully checked\n";
}
if (defined $cfg->{help}) {
print "usage: burnfiles \[options\]\n";
print " --files <file list> list of files (dirs) to burn\n";
print " --files_list==<file> file containing burn list\n";
print " --exclude <file list> list of files to exclude\n";
print " --exclude_list==<file> file containing exclude list\n";
print " --index_file==<file> file to write DVD index\n";
print " --dvd_size==<size> media size in value[KMG]\n";
print " --dvd_burn==<cmd> burn command\n";
print " --noburn simulate only\n";
print " --checkrc only check .dvdburnrc\n";
print " --help this help text\n";
die;
}
if (defined $cfg->{files}) {
@backups = @{$cfg->{files}};
}
if (defined $cfg->{exclude}) {
@excludes = @{$cfg->{exclude}};
}
if (defined $cfg->{'files_list'}) {
$backup_file = $cfg->{'files_list'};
}
if (defined $cfg->{'exclude_list'}) {
$exclude_file = $cfg->{'exclude_list'};
}
if (defined $cfg->{'index_file'}) {
$index_file = $cfg->{'index_file'};
}
if (defined $cfg->{'noburn'}) {
$noburn = $cfg->{'noburn'};
}
foreach ($dvd_size) {
if (/^(.+?)([KMG])$/i) {
$_ = Math::BigInt->new($1);
my $mult = uc($2);
if ($mult eq 'K') {
$_->bmul(2**10);
} elsif ($mult eq 'M') {
$_->bmul(2**20);
} elsif ($mult eq 'G') {
$_->bmul(2**30);
}
}
}
if (defined $index_file) {
my($min, $hour, $mday, $mon, $year) = (localtime)[1, 2, 3, 4, 5];
$year -= 100;
$mon++;
foreach ($min, $hour) {
if ($_ < 10) {
$_ = "0$_";
}
}
my $date = "$year-$mon-$mday" . "_$hour:$min";
$index_file =~ s/%d/$date/g;
}
return 1;
}
##
# \@list get_listvalue($value)
#
# This sub will take a list of values as a string, where the seperate values
# are delimited by either whitespace or double quotes, and return a reference
# to an array of these values. Example:
# Given a string like this: "value one" value2 "value3"
# will return an array of strings like this: ["value one", "value2", "value3"]
# If the list is empty, undef is returned.
#
sub get_listvalue {
my($value) = @_;
my @value = map chr $_, unpack 'C*', $value;
my(@list, $i, $cur, $dq);
$dq = 0;
for ($i = 0; $i < @value; $i++) {
if ($value[$i] eq '"') {
if (not $dq) {
$dq = 1;
} else {
push @list, $cur if defined $cur;
$cur = undef;
$dq = 0;
}
} elsif ($value[$i] eq '\\') {
if (($i +1) < @value and $value[$i + 1] eq '"') {
$cur .= '\\"';
$i++;
} else {
$cur .= '\\';
}
} elsif (($value[$i] =~ /\s/ and $dq) or $value[$i] !~ /\s/) {
$cur .= $value[$i];
} elsif ($value[$i] =~ /\s/) {
push @list, $cur if defined $cur;
$cur = undef;
}
}
push @list, $cur if defined $cur;
if (@list) {
return \@list;
} else {
return undef;
}
}
# void show_options(\%config)
#
# Prints all the options to stderr.
#
sub show_options {
my($cfg) = @_;
print STDERR "-- Options --\n";
print STDERR "Files to backup:\n", map{ " '$_'\n" } @{$cfg->{files}};
print STDERR "\n";
print STDERR "Files to exclude:\n", map{ " '$_'\n" } @{$cfg->{exclude}};
print STDERR "\n";
print STDERR "All the rest:\n", map{ " $_: '$cfg->{$_}'\n" }
sort grep {defined $cfg->{$_} and $_ ne 'files' and $_ ne 'exclude'}
keys %{$cfg};
print STDERR "-- Options --\n";
}
sub makelist {
my($file) = @_;
my @list;
unless (sysopen LIST, $file, 0) {
print "Error opening file $file: $!\n";
return @list;
}
while (<LIST>) {
chomp;
push @list, $_;
}
close LIST;
return @list;
}
sub dumpsettings {
print "DVD size: $dvd_size\n";
print "Burn command: $burn_cmd\n";
print "Index file: $index_file\n\n";
print "Backup files:\n";
dumplist(@backups);
print "Exclude files:\n";
dumplist(@excludes);
}
sub dumplist {
my(@list) = @_;
foreach ( @list ) {
print " \'$_\'\n";
}
print "\n";
}
########
4. chmod +x /myth/bin/mythdvdbackup* /myth/bin/dvdburn
5. cp /usr/share/mythtv/knoppmyth.xml ~/.mythtv
6. vi ~/.mythtv/knoppmyth.xml # and insert up to ##### where you like
<button>
<type>Backup Videos to DVD</type>
<text>Backup Videos to DVD</text>
<action>EXEC /myth/bin/mythdvdbackup /myth/video</action>
</button>
<button>
<type>Backup Gallery to DVD</type>
<text>Backup Gallery to DVD</text>
<action>EXEC /myth/bin/mythdvdbackup /myth/gallery</action>
</button>
#######
Now restart the frontend and try it out. Hope you'll find it usefull...