View unanswered posts    View active topics

All times are UTC - 6 hours





Post new topic Reply to topic  [ 3 posts ] 
Print view Previous topic   Next topic  
Author Message
Search for:
PostPosted: Tue Feb 14, 2006 10:47 pm 
Offline
Joined: Thu Dec 15, 2005 2:49 pm
Posts: 5
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...


Top
 Profile  
 
 Post subject:
PostPosted: Wed Feb 15, 2006 2:51 am 
Offline
Joined: Tue Mar 22, 2005 9:18 pm
Posts: 1393
Location: Brisbane, Queensland, Australia
This looks great, I personally don't have a DVD burner in my KnoppMyth box, but I think this will be great for others.

I would just like to provide some feedback though.

You have hardcoded the paths to the video and gallery directories, but as you would be aware these are easily changed in the MythFrontend. Could I suggest that you pass a variable to your script, eg.
Code:
/myth/bin/mythdvdbackup vid
and then in your script you get the path for the video directory from the DB.

Whilst I can't work the code out off the top of my head I am willing to help you out with it, if you like. This was people can happily change the location without having the change the buttons. It will also make it extensible in that later you have a single button that would produce a pop-up that will list what you could backup, but this is for much later.

_________________
Girkers


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 09, 2006 10:07 pm 
Offline
Joined: Thu Dec 15, 2005 2:49 pm
Posts: 5
Thanks, Girkers, for the good suggestion and for providing me with the script to query the video and gallery path from mysql. With that it was easy to update the scripts. Here is the new /myth/bin/mythdvdbackup command:

Code:
#!/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

DIR=`mysql -uroot mythconverg -B --exec "SELECT data FROM settings WHERE value='$@';" | tr '\012' ' ' | cut -d' ' -f 2`

if [ $PID == 0 ] ; then
  nice /myth/bin/mythdvdbackup2 $DIR &
  sleep 2
else
  Xdialog --infobox "Backup already running" 300x200 3000 &
fi


and the buttons in ~/.mythtv/knoppmyth.xml now should look like this:

Code:
   <button>
     <type>Backup Videos to DVD</type>
     <text>Backup Videos to DVD</text>
     <action>EXEC /myth/bin/mythdvdbackup VideoStartupDir</action>
   </button>

   <button>
     <type>Backup Gallery to DVD</type>
     <text>Backup Gallery to DVD</text>
     <action>EXEC /myth/bin/mythdvdbackup GalleryDir</action>
   </button>


Enjoy!


Top
 Profile  
 

Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 3 posts ] 


All times are UTC - 6 hours




Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group

Theme Created By ceyhansuyu