Notes
- DvdMedia. List of most reliable DVD media brands.
Audio Book Recoding
mkfifo fifo.wav # named pipe, recommended by mplayer developers
mplayer -af resample=32000:0:2,pan=1:0.5:0.5,volnorm=2,format=s16ne \
-ao pcm:file=fifo.wav file.mp3 # output to named pipe, start this process 1st
# May need to implement input file sample rate testing. Also, not good resampling.
# Best resampler is Shibatch Audio Tools http://shibatch.sourceforge.net/
# See review at http://www.mainly.me.uk/resampling/index.html
speexenc --vbr --dtx --denoise fifo.wav file.spx # run after mplayer started
# defaults best compromise for speex, but with added above prefilters
# Sample file sizes: spx as above = 4.6 MB, spx q10 = 7.0MB, ogg q-1 = 6.8MB (bad)
# spx q8 no resample = 6.1MB (not better); starting file 44.1KHz 170Kbps MP3 = 35.8
Audio Book Ripping Script
Rips simultaneously from three drives in parallel. Backgrounds encoding process.
#!/usr/bin/perl
use strict;
use warnings;
use threads;
use Thread::Queue;
use Thread::Semaphore;
use YAML::XS qw(LoadFile);
my $d = LoadFile 'data.yml';
my $encoding_threads = 3;
my @drives = qw(/dev/cdrom0 /dev/cdrom1 /dev/cdrom2);
my $semaphore = new Thread::Semaphore;
print "--- Ripping \"$$d{title}\" ---\n";
my $encode_queue = Thread::Queue->new();
my @enc_thr; # Holds speex process threads
for (1..$encoding_threads) {
my $thr = async {
while (defined(my $encodejob = $encode_queue->dequeue())) {
&encode($encodejob);
}
};
push @enc_thr, $thr;
}
my $read_queue = Thread::Queue->new();
$read_queue->enqueue( $$d{starting_disc}..$$d{num_discs} );
$read_queue->enqueue( (undef) x (scalar @drives) );
my @read_thr;
for my $drive (@drives) {
my $thr = async {
while (defined(my $disc = $read_queue->dequeue())) {
&read($drive, $disc);
$encode_queue->enqueue($disc);
}
};
push @read_thr, $thr;
&small_sleep(0.1);
}
$_->join for (@read_thr);
$encode_queue->enqueue( (undef) x $encoding_threads );
print "Waiting for background encoding threads to finish...\n";
$_->join for (@enc_thr);
`eject $_` for (@drives);
print "Done\n";
sub read {
my ($drive, $disc) = @_;
my @track_groups = split /\n/, $$d{track_groups};
$semaphore->down;
print("Please insert disc $disc\n");
qx(eject $drive);
# Wait until disc inserted:
sleep 1 while (&rip($drive, "-1:[1.1]", "/dev/null"));
$semaphore->up;
#sleep 2;
for my $t (@track_groups) {
sleep 1 while (&rip($drive, $t, "$disc.$t.wav"));
}
}
sub rip {
my ($drive, $tracks, $filename) = @_;
open STDERR, ">>/dev/null";
return system("cdparanoia", "-q", "-Z", "-d", "$drive", "--",
"$tracks", "$filename");
close STDERR;
}
sub encode {
my ($disc) = shift(@_);
for my $aref (&meta($disc)) {
my ($filename, $spx, @comments) = @$aref;
&downmix($filename);
&normalize($filename);
&resample($filename);
&speex(@$aref);
}
}
# Takes $disc as input, returns .wav filename, .spx filename,
# followed by list of comments as an arrayref for each track group.
sub meta {
my ($disc) = shift(@_);
my @list = split /\n/, $$d{chapters};
my @track_groups = split /\n/, $$d{track_groups};
my $num_lectures = sprintf("%02d", scalar @list);
my $outdir = "$$d{title} ($$d{year})";
my @output_array;
for my $i (0..@track_groups) {
my $lecture_num = sprintf("%02d", ($disc-1)*@track_groups + $i + 1);
my $filename = "$disc.$track_groups[$i].wav";
my @comments = ("Title=$list[$lecture_num -1]",
"Artist=$$d{author}",
"Album=$$d{title}",
"Date=$$d{year}",
"Track Number=$lecture_num of $num_lectures",
"Genre=Speech",
"Comment=$$d{comment}");
mkdir("$$d{title} ($$d{year})");
my $spx = "$$d{title} ($$d{year})/" .
"($lecture_num.$num_lectures) $list[$lecture_num - 1].spx";
push(@output_array, [$filename, $spx, @comments]);
}
return @output_array;
}
sub speex {
my ($filename, $spx, @comments) = @_;
my @cmd = qw(nice ionice -c3 speexenc --vbr --dtx);
for my $c (@comments) {
push(@cmd, ("--comment", "$c"));
}
push(@cmd, ("$filename", "$spx"));
my $e = system(@cmd);
unlink("$filename") if (! $e);
return $e;
}
sub downmix {
my $filename = shift(@_);
my $e = system("nice", "sox", "$filename",
"mono_$filename", "channels", "1");
rename("mono_${filename}", "$filename");
return $e;
}
sub normalize {
my ($filename) = shift(@_);
my $e = system("nice", "normalize-audio", "-q", "$filename");
return $e;
}
sub resample {
my $filename = shift(@_);
my $e = system("nice", "ssrc", "--quiet", "--twopass", "--rate",
"32000", "$filename", "resample_$filename");
rename("resample_${filename}", "$filename");
return $e;
}
sub small_sleep {
select(undef, undef, undef, shift(@_) );
}
Alsa A52 real-time encoding
Requires soundcard with s/pdif out.
- Install following:
aptitude install libavcodec-unstripped-52 libavcodec-dev
- Download alsa-plugins source.
- Extract in /usr/src
- ./configure && make
- copy ./a52/.lib/*.so /usr/lib/alsa-lib/
Cheat Sheets
Important Experimental Conclusions
- ffmpeg for aac encoding scrambles channel mapping when played back with mplayer
- nero and holyroses script for aac encoding keeps channels correct
- aften keeps channels correct for AC3 encoding
- aften vbr encoding works on integra, but seeeming stream errors in beginning
- aften cbr works perfectly with defaults using wav as input
- eac3to for 5 -> 2ch PL downmix is OK, but slow encoding
- handbrake can't do audio only, and crashes if nonexistant subtitle is selected
- real-time a52 encoding at 640kbps with alsa and playback of 6 channel wav of aac results in perfect sound, if using nero AAC encoder. Initially there were some problems with slow video, but this was fixed with a reboot and ensuring resample to 48k always included in filter chain. FFmpeg would probably work as well, but channel mapping is not correct.
- DTS to AC3 encoding works well with ffmpeg -> aften pipe, but ffmpeg has difficulty with dts output to file only. Therefore use ffmpeg input.mkv -f wav - | aften - out.ac3
- playback of AAC 5-channel sources is barely acceptable with mplayer -channels 2 option
- playback of AAC 2-channel sources is perfect (sent to integra as PCM)
Initial Mythbuntu (ash) Customizations
echo "Port 22003" >> /etc/ssh/sshd_config
ssh linode "allsystemsgo"
cp commtrim fill_mythvideo_metadata.pl hdhomerun_config* jamu.py music myth* prepbackup.ash restartx /usr/local/bin/
alsamixer # Set digital SPDIF to on
cat <<'EOF'>/etc/X11/xorg.conf
Section "Device"
Identifier "Default Device"
Driver "intel"
Option "XvMC" "true"
EndSection
EOF
#!/bin/sh
cat <<'EOF'>/usr/local/bin/lircstart.sh
/etc/init.d/lirc stop
sleep 1
killall > /dev/null 2>&1
sleep 1
lircd --driver=devinput --device=/dev/input/by-id/usb-Gyration_Gyration_RF_Technology_Receiver-event-kbd \
--pidfile=/var/run/lirc1.pid --listen=9988 --output=/var/run/lirc/lircd;
lircd --driver=devinput --device=/dev/input/by-id/usb-Gyration_Gyration_RF_Technology_Receiver-event-mouse \
--pidfile=/var/run/lirc2.pid --connect=localhost:9988;
EOF
# /etc/init.d/lirc patch
cp /etc/init.d/lirc /etc/init.d/lirc.dist
--- lirc.dist 2010-03-28 23:46:57.233064001 -0500
+++ lirc.gyration 2010-03-28 23:46:57.233064001 -0500
@@ -167,6 +167,12 @@
else
log_end_msg 1
fi
+ # Hack to get Gyration mouse events working
+ killall lircd >/dev/null 2>&1
+ lircd --driver=devinput --device=/dev/input/by-id/usb-Gyration_Gyration_RF_Technology_Receiver-event-kbd \
+ --pidfile=/var/run/lirc1.pid --listen=9988 --output=/var/run/lirc/lircd;
+ lircd --driver=devinput --device=/dev/input/by-id/usb-Gyration_Gyration_RF_Technology_Receiver-event-mouse \
+ --pidfile=/var/run/lirc2.pid --connect=localhost:9988;
fi
if [ "$START_LIRCMD" = "true" ]; then
@@ -194,10 +200,13 @@
log_daemon_msg "Stopping remote control mouse daemon: LIRCMD"
start-stop-daemon --stop --quiet --exec /usr/sbin/lircmd
log_end_msg $?
+ # Hack to get Gyration mouse events working
+ killall lircd >/dev/null 2>&1
fi
if [ "$START_LIRCD" = "true" ]; then
log_daemon_msg "Stopping remote control daemon(s): LIRC"
+
start-stop-daemon --stop --quiet --exec /usr/sbin/lircd
log_end_msg $?
[ -h "$OLD_SOCKET" ] && rm -f $OLD_SOCKET
echo "/usr/lib/libIntelXvMC.so" > /etc/X11/XvMCConfig
echo "/mnt/dv *(ro,async,no_root_squash,no_subtree_check)" > /etc/exports
dpkg -i nxclient_3.4.0-5_x86_64.deb
dpkg -i nxnode_3.4.0-11_x86_64.deb
dpkg -i nxserver_3.4.0-12_x86_64.deb
# Files and Directories to restore
/home/rde/.lirc
/home/rde/.lircrc
/etc/lirc
/etc/asound*
/mnt/* #symlinks
/usr/local/bin/integra, music, integra-query
# To bundle all of the above:
tar czPf mythbuntu_9.10_tweaks.tar.gz /etc/ssh /usr/local/bin/commtrim \
/usr/local/bin/fill_mythvideo_metadata.pl /usr/local/bin/hdhomerun_config* \
/usr/local/bin/jamu.py /usr/local/bin/music /usr/local/bin/prepbackup.ash /usr/local/bin/restartx \
/usr/local/bin/integra* /etc/X11/xorg.conf /usr/local/bin/lircstart.sh /etc/init.d/lirc.gyration \
/etc/init.d/lirc /etc/X11/XvMCConfig /home/rde/.lirc* /etc/lirc/ /etc/asound* /mnt/home /mnt/bulk \
/mnt/n1000d /mnt/n500d /mnt/working /root/.bashrc /etc/auto.* /etc/sudoers /etc/apache2/htdigest_master \
/etc/apache2/htaccess_master /home/rde/.bashrc /root/.ssh /home/rde/.ssh \
/etc/ntp.conf /etc/ssl /usr/local/bin/harshpurge /usr/local/bin/myth* /usr/local/bin/*perms \
/usr/lib/alsa-lib/libasound_module_pcm_a52.* /etc/mplayer/mplayer.conf \
--exclude '/etc/ssl/certs'
# Playing 1280p files is difficult. Following options work:
mplayer -vfm ffmpeg -lavdopts lowres=1:fast:skiploopfilter=all -fs filename.mkv
Optimized Media Encoding and Transcoding
# Use mythtranscode to cut out commercials (make cutlist first)
mythtranscode -m -c chanid -s starttime -l -o output.mpg
# Extract closed captioning
ccextractor -utf8 mpeg2.mpg -o output.srt
# CRF (Constant quality, x264 specific setting; 26 recommended highest, but 32 much smaller files)
# After experimentation, a crf of 30 is near source qualty on 720p HD.
# Preset -vpre hq results in smaller file size, takes ~ 3x file duration for 720p. -vpre normal ~ 1x duration.
# The option -vsync 1 was tried, but causes audio sync skew.
# Specifying the rate as 30000/1001 decreases file size by about 20% from 59.94fps.
# A mp4 container can be used, but the whole file must be complete to play it.
# The map paramaters select which streams are to be encoded (0 video and 2 audio below)
ffmpeg -y -i mpeg2input.mpg -r 30000/1001 -map 0:0 -vcodec libx264 -crf 30 -vpre normal -map 0:2 \
-acodec copy -threads 0 output.mkv
# If program is stereo, can use the following to copy audio into ogg vorbis @ about 90kbps:
-acodec vorbis -aq 25 -ac 2
# File size trials with various parameters:
# Source ATSC mpeg2 720p 24 episode: 4.5G
# crf vpre rate size(MB) qual
# --- ---- ---- -------- ----
# 26 hq 60 922 perfect
# 28 nl 60 751 excellent
# 28 hq 30 592 excellent
# 30 hq 60 589 good
# 30 nl 60 683 good
# 30 nl 30 478 good
# 32 hq 60 356 fair
# Add srt subtitles to existing mkv file
# The charset and language info is forced with the options below
mkvmerge -v -o output.mkv --language 1:eng --language 2:eng input.mkv --language 0:eng subtitles.srt
# Handbrake can do the same with a single command, and has advantages of automatic decomb filtering.
# The following command sets maximum resolution to 1280 width or 720 height, and copies all subtitiles.
# This is a universal command that should be suitable for any MPEG2 or MPEG4 with resolution > 720p:
HandBrakeCLI -e x264 -q 26.0 --decomb --detelecine --loose-anamorphic -X 1280 -Y 720 \
-m -x b-adapt=2:rc-lookahead=50 -s 1,2,3,4,5,6,7,8,9,10 -N eng -i infile.mpg -o outfile.mkv
Obsolete Media Encoding Methods
# Two pass MPEG2 (ATSC) to MPEG4 (H.264), keeping audio (AC3) stream untouched.
# Obsolete since crf gives better results.
ffmpeg -y -i mpeg2input.mpg -r 30000/1001 -b 2M -bt 4M -vcodec libx264 -pass 1 -vpre fastfirstpass -an -vn -threads 0
ffmpeg -y -i mpeg2input.mpg -r 30000/1001 -b 2M -bt 4M -vcodec libx264 -pass 2 -vpre hq -acodec copy output.mkv
# Audio above just keeps AC3. MPEG4 container requires AAC. AAC can be compressed with various settings,
# including the switch "-aq [0-255]" (undocumented) to specify quality. Trials verified that compression of 5.1 ac3
to 5.1 aac destroys quality without saving much space. The map option is used to specify audio an video streams to encode.
ffmpeg-y -i mpeg2input.mpg -r 30000/1001 -crf 36 -map 0:0 -vcodec libx264 -map 0:2 -acodec libfaac -ab 224k -threads 0 output.mpg
MythTV Commercial Cut Script
#!/bin/bash
### removecommercials - for mythtv user job.
### $author Zack White - zwhite dash mythtv a t nospam darkstar deleteme frop dot org
### $Modified 20080330 Richard Hendershot - rshendershot a t nospam gmail deleteme dot youknowcom
### $Modified 20100112 Aaron Larson to get password from mythtv config file, clear autoskip list after transcoding, and detect pre-flagged files.
# Should be set as a mythtv user job with a command as:
# removecommercials %DIR% %FILE% %CHANID% %STARTTIME%
#
# initialize; all except SKIP are required for this to function correctly
declare VIDEODIR=$1
declare FILENAME=$2
declare CHANID=$3
declare STARTTIME=$(echo $4 | sed -e 's/\([0-9]\{4\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\)/\1-\2-\3-\4-\5-\6/')
declare SKIP=$5
# for lossless transcoding autodetect. Set to empty string for rtJpeg/mpeg4.
declare MPEG2=--mpeg2
declare PROG=$(basename $0)
if [ -z "${VIDEODIR}" -o -z "${FILENAME}" -o -z "${CHANID}" -o -z "${STARTTIME}" ]; then
cat - <<-EOF
Usage: $PROG <VideoDirectory> <FileName> <ChannelID> <StartTime> [SKIP]
Flag commercials (if they are not already flagged), do a lossless transcode
to remove the commercials, and fixup the database as necessary. The net
effect is that this script can be run as the *only* job after a recording,
or as a job after a commercial flagging job (either way). The optional 5th
parameter 'SKIP', if specified as a non zero length string, will transcode
using the existing cutlist.
EOF
exit 5
fi
if [ ! -f "${VIDEODIR}/${FILENAME}" ]; then
echo "$PROG: File does not exist: ${VIDEODIR}/${FILENAME}"
exit 6
fi
if [ ! -d "${VIDEODIR}" ]; then
echo "$PROG: <VideoDirectory> must be a directory"
exit 7
fi
if [ ! -d "${VIDEODIR}/originals" ]; then
mkdir "${VIDEODIR}"/originals
fi
if [ ! -d "${VIDEODIR}/originals" ]; then
echo "$PROG: you must have write access to <VideoDirectory>"
exit 8
fi
# mythtv stores the mysql configuration information in the following
# file. Extract the DB user and password.
mythConfig=~/.mythtv/config.xml
mysqlArgs=""
if [ -e "$mythConfig" ]; then
mysqlUserOpt=$(sed $mythConfig -n -e '/<DBUserName/p')
if [ -n "$mysqlUserOpt" ]; then
mysqlUser=$(echo $mysqlUserOpt | sed 's: *</*DBUserName> *::g')
mysqlArgs+=" -u $mysqlUser"
fi
mysqlPassOpt=$(sed $mythConfig -n -e '/<DBPassword/p')
if [ -n "$mysqlPassOpt" ]; then
mysqlPass=$(echo $mysqlPassOpt | sed 's: *</*DBPassword> *::g')
if [ -n "$mysqlPass" ]; then
mysqlArgs+=" -p$mysqlPass"
fi
fi
fi
if [ -z "${SKIP}" ]; then
# if transcode was run after mythcommflag in the normal setup screens
# then the current file may not match the existing index, so rebuild
echo "$PROG: Rebuilding seek list for ${FILENAME}"
mythcommflag -c ${CHANID} -s ${STARTTIME} --quiet --rebuild
ERROR=$?
if [ $ERROR -ne 0 ]; then
echo "$PROG: Rebuilding seek list failed for ${FILENAME} with error $ERROR"
exit $ERROR
else
echo "$PROG: Rebuilding seek list successful for ${FILENAME}"
fi
# flag commercials (generate skiplist)
# you can use mythcommflag -f ${VIDEODIR}/${FILENAME} --getskiplist
# to view results
# has mythcommflag already run?
alreadyFlagged=$(mysql $mysqlArgs -B -N -e "select commflagged from recorded where basename = '${FILENAME}'" mythconverg)
if [ "$alreadyFlagged" == "1" ]; then
echo "$PROG: ${FILENAME} already flagged, skipping mythcommflag."
else
echo "$PROG: Commercial flagging ${FILENAME}"
mythcommflag -c ${CHANID} -s ${STARTTIME} --quiet
ERROR=$?
if [ $ERROR -gt 126 ]; then
echo "$PROG: Commercial flagging failed for ${FILENAME} with error $ERROR"
exit $ERROR
else
echo "$PROG: Commercial flagging successful for ${FILENAME}"
fi
fi
# generate cutlist from skiplist
# you can use mythcommflag -f ${VIDEODIR}/${FILENAME} --getcutlist
# to view results
echo "$PROG: Creating cutlist from skiplist for ${FILENAME}"
mythcommflag -c ${CHANID} -s ${STARTTIME} --quiet --gencutlist
ERROR=$?
if [ $ERROR -ne 0 ]; then
echo "$PROG: Creating cutlist from skiplist failed for ${FILENAME} with error $ERROR"
exit $ERROR
else
echo "$PROG: Creating cutlist from skiplist successful for ${FILENAME}"
fi
else
echo "$PROG: skipping commercial detection due to parameter $SKIP"
fi #end skip
# cut the commercials from the file. creates a new file and a map file.
echo "$PROG: Transcoding commercials out of original file (${FILENAME})"
mythtranscode -c ${CHANID} -s ${STARTTIME} $MPEG2 --honorcutlist -o "${VIDEODIR}/${FILENAME}.mpeg"
ERROR=$?
if [ $ERROR -ne 0 ]; then
echo "$PROG: Transcoding failed for ${FILENAME} with error $ERROR"
exit $ERROR
else
echo "$PROG: Transcoding successful for ${FILENAME}"
fi
echo "$PROG: Moving ${VIDEODIR}/${FILENAME} to ${VIDEODIR}/originals/${FILENAME}"
mv "${VIDEODIR}/${FILENAME}" "${VIDEODIR}/originals"
ERROR=$?
if [ $ERROR -ne 0 ]; then
echo "$PROG: Moving failed with error $ERROR"
exit $ERROR
else
echo "$PROG: Moving successful"
fi
echo "$PROG: Moving ${VIDEODIR}/${FILENAME}.mpeg to ${VIDEODIR}/${FILENAME}"
if [ ! -f "${VIDEODIR}/${FILENAME}" ]; then
mv "${VIDEODIR}/${FILENAME}.mpeg" "${VIDEODIR}/${FILENAME}"
ERROR=$?
if [ $ERROR -ne 0 ]; then
echo "$PROG: Moving failed with error $ERROR"
exit $ERROR
else
echo "$PROG: Moving successful"
fi
else
echo "$PROG: cannot replace original. skipping file move. (${VIDEODIR}/${FILENAME})"
fi
echo "$PROG: removing map file: ${VIDEODIR}/${FILENAME}.mpeg.map"
if [ -f "${VIDEODIR}/${FILENAME}.mpeg.map" ]; then
rm "${VIDEODIR}/${FILENAME}.mpeg.map"
ERROR=$?
if [ $ERROR -ne 0 ]; then
echo "$PROG: unable to remove map file: ${VIDEODIR}/${FILENAME}.mpeg.map"
else
echo "$PROG: removed map file successfully"
fi
fi
# file has changed, rebuild index
echo "$PROG: Rebuilding seek list for ${FILENAME}"
mythcommflag -c ${CHANID} -s ${STARTTIME} --quiet --rebuild
ERROR=$?
if [ $ERROR -ne 0 ]; then
echo "$PROG: Rebuilding seek list failed for ${FILENAME} with error $ERROR"
exit $ERROR
else
echo "$PROG: Rebuilding seek list successful for ${FILENAME}"
fi
echo "$PROG: Clearing cutlist for ${FILENAME}"
mythcommflag -c ${CHANID} -s ${STARTTIME} --quiet --clearcutlist
ERROR=$?
if [ $ERROR -eq 0 ]; then
echo "$PROG: Clearing cutlist successful for ${FILENAME}"
else
echo "$PROG: Clearing cutlist failed for ${FILENAME} with error $ERROR"
exit $ERROR
fi
# mythcommflag sets cutlist to zero, but doesn't update the filesize.
# Fix the database entry for the file
mysql $mysqlArgs mythconverg << EOF
UPDATE
recorded
SET
cutlist = 0,
filesize = $(ls -l "${VIDEODIR}/${FILENAME}" | awk '{print $5}')
WHERE
basename = '${FILENAME}';
EOF
echo "$PROG: Clearing autoskip list: ${VIDEODIR}/${FILENAME}"
mysql $mysqlArgs mythconverg << EOF
DELETE FROM
recordedmarkup
WHERE
CONCAT( chanid, starttime ) IN (
SELECT
CONCAT( chanid, starttime )
FROM
recorded
WHERE
basename = '$FILENAME'
);
EOF
# If you want to keep the originals, comment out this line.
echo "$PROG: removing saved copy of: ${VIDEODIR}/originals/${FILENAME}"
rm "${VIDEODIR}/originals/${FILENAME}"
ERROR=$?
if [ $ERROR -ne 0 ]; then
echo "$PROG: failed to remove ${VIDEODIR}/originals/${FILENAME}"
exit $ERROR
fi
Linux Media Applications
List of sites with info on media applications of linux.