MediaTomb and PS3: The Lazy Man's solution

I've been using MediaTomb with my PlayStation 3 for a while now, and it's been working just peachy. However, it takes a lot of work keeping up with the different media formats in the library, and especially those media which don't play by default on the PS3: QPeL MPEG4, RealMedia, Flash, DVD ISOs ... After creating the umptieth transcoding profile fit for one specific filetype, I wondered if I could just create one single transcoding profile that fits everything I throw at it.

This article has been superseded by a new one, describing a way to combine transcoding and remuxing for HD content.

The thing that pushed me to try this was the ongoing suckification of the PS3's media center capabilities. I have no idea how or why, but the newer my firmware gets, the less stable the media player gets when it comes to supporting different media types. First there was the mp4 choppy playback issue that Sony refuses to acknowledge, and lately even plain divx/xvid starts to stutter now and then, seemingly randomly. Ironically, a transcoded mpeg2 stream of 10 times the bandwidth of the source divx plays just fine. Quality loss is - as far as I can tell, and my eyes aren't that bad yet - negligible, so why not transcode everything and be done with it?

Such a profile would make the MediaTomb config a lot cleaner! Here's what mine looked like after cleaning it up to work with a single catch-all transcoding profile:

eth0 50000 MediaTomb uuid:05fb1733-b4bb-449f-9995-xxxx220dxxxx /etc/mediatomb /usr/share/mediatomb/web mediatomb.db localhost mediatomb mediatomb 128 5 yes no * UTF-8 ISO-8859-15 /usr/share/mediatomb/js/common.js /usr/share/mediatomb/js/playlists.js /usr/share/mediatomb/js/import.js /usr/share/mediatomb/js/import-dvd.js video/mpeg yes

As you can tell, I map everything that is not mp3 (because mp3 is the only audio that I have, and Just Works on the PS3) to video/transcode, and tell MediaTomb to transcode that using the /usr/local/bin/mediatomb-multifunctional.sh script. And here's that script then:

#!/bin/bash # # General all-covering MediaTomb transcoding script. # This turns EVERYTHING in an mpeg2 stream for PS3 use. # # v0.1 2010/06/05 # ############################################################################# # Change this to enable different subtitle languages. SUBS="nl,en" # Change this line to set the average bitrate. # Use something like 8000 for wired connections; lower to 2000 for wireless. AVBIT=8000 # Change this line to set the maximum bitrate. # Use something like 12000 for wired connections; lower to 5000 for wireless. MVBIT=12000 # Change this line to set the audio bitrate in kbps. 256 is nice. AABIT=256 # Change this line to set your favourite subtitle font. SFONT="/etc/mediatomb/DejaVuSans.ttf" # Change this line to set the size of subtitles. 25 is okay. SUBSIZE=20 # Enable downscaling of HD content to 720 pixels wide (DVD format)? DOWNSCALE=1 # If downscaling is enabled, anything over this width will be downscaled. MAXSIZE=900 # Enable logging to file? LOGGING=1 # If logging is enabled, log to which file? LOGFILE="/var/log/mediatomb-transcode.log" ############################################################################# # Do not change anything below this line. ############################################################################# # Variables ############################################################################# FILE=$1 MENCODER=$(which mencoder) MEDIAINFO=$(which mediainfo) LSDVD=$(which lsdvd) MENCOPTS="-oac lavc -ovc lavc -of mpeg -lavcopts \ abitrate=${AABIT}:vcodec=mpeg2video:keyint=1:vbitrate=${AVBIT}:\ vrc_maxrate=${MVBIT}:vrc_buf_size=1835 \ -mpegopts muxrate=12000 -af lavcresample=44100 " SUBOPTS="-slang ${SUBS} " SRTOPTS="-font ${SFONT} -subfont-autoscale 0 -subfont-text-scale ${SUBSIZE} -subpos 100 " SIZEOPTS="-vf harddup,scale=720:-2 " NOSIZEOPTS="-vf harddup " S24FPS="23.976" S24OPT="-ofps 24000/1001" S30FPS="29.97" S30OPT="-ofps 30000/1001" MKVOPTS="-aid 0 -sid 0 " WIDTH="" SFPS="" COMBINEDOPTS="" ############################################################################# # Functions ############################################################################# function log { if [ "${LOGGING}" == "1" ] ; then echo -e "$(date +'%Y/%m/%d %H:%m:%S') \t $1" >> ${LOGFILE} fi } function mediainfo { MIOUT=$(mktemp /tmp/tmp.mediainfo.XXXXXX) log "Tempfile is ${MIOUT}" ${MEDIAINFO} "${FILE}" > ${MIOUT} WIDTH=$(grep -e "^Width" ${MIOUT} | sed -e 's/[ ]*//g' -e 's/.*:\(.*\)pixels/\1/') SFPS=$(grep -e "^Frame" ${MIOUT} | sed -e 's/[ ]*//g' -e 's/.*:\(.*\)fps/\1/') log "Width of ${WIDTH} and FPS of ${SFPS} detected." rm -f "${MIOUT}" } function setopts { SUBLINK=$(mktemp /tmp/tmp.mmsublink.XXXXXX) if [ "${DOWNSCALE}" == "1" -a ${WIDTH} -gt ${MAXSIZE} ] ; then log "Rescaling to 720 pixels." COMBINEDOPTS="${COMBINEDOPTS} ${SIZEOPTS}" else log "Rescaling disabled or file within limits." COMBINEDOPTS="${COMBINEDOPTS} ${NOSIZEOPTS}" fi if [ "${SFPS}" == "${S24FPS}" ] ; then log "Framerate adjusted for mencoder." COMBINEDOPTS="${COMBINEDOPTS} ${S24OPT}" else if [ "${SFPS}" == "${S30FPS}" ] ; then COMBINEDOPTS="${COMBINEDOPTS} ${S30OPT}" else log "Framerate acceptable for mencoder." fi fi if [ -e "$(echo $FILE | sed 's/...$/sub/')" ] ; then SUB=$(echo $FILE | sed 's/...$/sub/') rm $SUBLINK && ln -s "${SUB}" "${SUBLINK}" log "Subtitle found: ${SUB}" COMBINEDOPTS="-sub ${TEMPSUB} ${COMBINEDOPTS}" else if [ -e "$(echo $FILE | sed 's/...$/srt/')" ] ; then SUB=$(echo $FILE | sed 's/...$/srt/') rm $SUBLINK && ln -s "${SUB}" "${SUBLINK}" log "Subtitle found: ${SUB}" COMBINEDOPTS="-sub ${SUBLINK} ${COMBINEDOPTS}" else log "No external subtitles." fi fi } ############################################################################# # Actual code ############################################################################# log "Starting MediaTomb Multifunctional Transcoder." find /tmp/tmp.mmsublink.* -mtime +1 -exec rm {} \; FEXT=$(echo $FILE | sed 's/.*\.//') if [ "$(echo $FILE | grep 'http://')" != "" ] ; then FEXT="URL" fi case $FEXT in "iso") log "ISO file specified: \"${FILE}\"" CHAPTER=$(${LSDVD} "${FILE}" | grep Longest | sed 's/.* //') log "Chapter ${CHAPTER} selected..." COMBINEDOPTS="${MENCOPTS} ${SUBOPTS} ${SIZEOPTS}" log "Starting mencoder:" log "${MENCODER} -dvd-device \"${FILE}\" dvd://${CHAPTER} ${COMBINEDOPTS} -o \"$2\"" exec ${MENCODER} -dvd-device "${FILE}" dvd://${CHAPTER} ${COMBINEDOPTS} -o "$2" &>/dev/null & ;; "ogm" | "mkv") log "OGM/MKV file specified: \"${FILE}\"" mediainfo COMBINEDOPTS="${MKVOPTS} ${MENCOPTS} ${SRTOPTS}" setopts log "Starting mencoder:" log "${MENCODER} \"${FILE}\" ${COMBINEDOPTS} -o \"$2\"" exec ${MENCODER} "${FILE}" ${COMBINEDOPTS} -o "$2" &>/dev/null & ;; "URL") log "URL specified: \"${FILE}\"" log "Starting mencoder:" log "ffmpeg -i \"${FILE}\" -acodec mp2 -vcodec mpeg2video -f mpegts -b ${AVBIT}000 -ab ${AABIT}k -y \"$2\"" exec ffmpeg -i "${FILE}" -acodec mp2 -vcodec mpeg2video -f mpegts -b ${AVBIT}000 -ab ${AABIT}k -y "$2" &>/dev/null & ;; *) log "Regular file specified: \"${FILE}\"" mediainfo COMBINEDOPTS="${MENCOPTS} ${SRTOPTS}" setopts log "Starting mencoder:" log "${MENCODER} \"${FILE}\" ${COMBINEDOPTS} -o \"$2\"" exec ${MENCODER} "${FILE}" ${COMBINEDOPTS} -o "$2" &>/dev/null & ;; esac log "Script ended."

As you can tell, you need mencoder, ffmpeg, mediainfo and lsdvd. Why ffmpeg? Because mencoder on my AMD64 system didn't like the codec in YouTube's mp4s. According to the MediaTomb docs, ffmpeg doesn't like writing to FIFO; this is true, since ffmpeg temporarily opens its output file read-only before starting to write to it. If the output file is a (new) FIFO, nothing is in it yet, so the call blocks on this. This is fixed by using the "-y" switch, which basically tells ffmpeg to write to the given target, regardless of whether it already exists or not, skipping the read-check.

This script checks for various things. First of all, mediainfo is used to determine video dimensions and framerate. Mencoder doesn't like floating point framerates, so 24000/1001 and 30000/1001 are being used for 23.976 and 29.97 respectively. Since my CPU (AMD 5050E, dualcore 2.5) isn't powerful enough to transcode HD content on the fly, I added an option to allow downscaling of content to 720 pixels wide (basically DVD format). It also checks for the presence of subtitles, and embeds them in the stream if found, using the specific options (font, size) you can set in the first part of the script.

My config has YouTube enabled (yes! I made the step to mediatomb 0.12+!); add your own YT user in the config to get to see your own favourites. And really, check out the MuppetsStudio content - it's hilarious :-)

Those of you who have read through the script will have noticed I do a very dirty thing to get subtitles working: subtitles are symlinked to a tempfile, and this is being passed to mencoder. The reason for this is that I still haven't figured out how I can pass subtitle filenames with spaces and/or special characters in it, inside the COMBINEDOPTS parameter without bash treating every word as a separate argument. As far as I know bash, this isn't possible. I'm open for suggestions on how to clean this up - I've cracked my head at it for several hours and haven't found a solution.

I've only tested this for a few days now so there might still be bugs or tweaks needed in the script, but so far anything I've thrown at it works: realmedia, divx with and without qpel, dvd isos and ogm. I'll do some more testing later on; check back for revisions of the script now and then - or tweak it yourself, and let me know what you did to improve it.

Enjoy!