Command disabled: backlink

VOB to AVI using Mencoder

This is a quite complex bash shell script to recode DVD VOB files to AVI using MEncoder, Avidemux and other tools. Automates crop detection, aspect ratio calc, dual audio muxing and subtitle ripping. I'm not saying this is all perfect of course… :-D

#!/bin/bash
 
function gethash() {
	key="$IN $OPTS"
	echo "::: gethash: initializing with key '$key'" >&2 ;
#ugly:	hash=`echo "$key" | md5sum | cut -f1 -d' '`
	hash=`echo "OPTS $OPTS" | sed 's/[^[:alnum:]]/_/g' `
	echo "$hash"
}
 
function init() {
	if [ "$INIT_DONE" ]; then echo "+ init: done already" >&2; return; fi
	echo "::: init: initializing" >&2 ;
	hash=`gethash`
	echo "+ init: hash is '$hash'" >&2 ;
	basedir=`dirname "$IN"`
	basename=`basename "$IN"`
	export TMPDIR="$basedir/$basename.$hash.vob2avi"
	echo "+ init: TMPDIR is '$TMPDIR'" >&2 ;
	mkdir -p "$TMPDIR"
	if [ ! -d "$TMPDIR" ]; then
		echo "FATAL: cannot create directory: $TMPDIR" >&2 ; 
		exit -1
	fi
	INIT_DONE="yes"
	echo "+ init: done" >&2 ;
}
 
function setupaudio() {
	if [ "$DUMMYMODE" ]; then
		whiptail --title "ERROR" --msgbox "audio setup can not be done in DUMMY MODE!" 7 75 ;
		return
	fi
	init
	demux
	echo "::: setupaudio: setting up audio track configuration for '$IN'" >&2 ;
 
	pushd "$TMPDIR" > /dev/null
	if [ -f aids ]; then echo "+ setupaudio: done already" >&2; return; fi
	touch aids
	tracks=`find "." | egrep "audio_[0-9]+.ac3" | sort -n`
	for track in $tracks; do
		track=`basename $track`
		echo "+ querying user for $track" >&2
		while true; do
			tagfile=`mktemp`
			if 
				whiptail --title "choose action for track $track" --menu "" 24 75 17\
				listen "Listen to this track" \
				remove "Do not include this track in final avi" \
				include "Include this track in final avi" \
				first "Make this the first audio track" \
				2>"$tagfile"
			then 
				tag=`cat "$tagfile"`
				rm -f "$tagfile"
				echo "+ setupaudio: user voted for menu item $tag with $track" >&2 ;
				case "$tag" in
				listen)
					echo "+ playing $track" >&2
					#kaffeine "$track"
					#mplayer -audiofile "$track" "$IN"
					vlc "$track"
					;;
				remove)
					echo "+ removing $track" >&2
					newaids=`cat aids | grep -v $track`
					echo -e "$newaids" > aids
					break;
					;;
				include) 
					echo "+ including $track" >&2
					echo $track >> aids
					echo "+ included $track" >&2
					break;
					;;
				first)
					echo "+ including $track as first" >&2
					newaids=`cat aids | grep -v $track`
					echo $track > aids
					echo -e "$newaids" >> aids
					break;
					;;
				esac
			else
				rm -f $tagfile
				break;
			fi
		done
	done
	popd >/dev/null
}
 
function demux() {
	if [ "$DUMMYMODE" ]; then
		whiptail --title "ERROR" --msgbox "demuxing can not be done in DUMMY MODE!" 7 75 ;
		return
	fi
	init
	if [ -f "$TMPDIR/demux_done" ]; then
		echo "+ demux: done already" >&2 ;
		return
	fi	
 
	echo "::: demux: demultiplexing '$IN'" >&2 ;
 
	info=`info`
 
	videoids=`echo -e "$info" | egrep "^ID_VIDEO_ID=" | sort | uniq | cut -f2 -d"="`
	audioids=`echo -e "$info" | egrep "^ID_AUDIO_ID=" | sort | uniq | cut -f2 -d"="`
	#mplayer is unreliable: subtitleids=`echo -e "$info" | egrep "^ID_SUBTITLE_ID=" | sort | uniq | cut -f2 -d"="`
	maxsubtitleid=`echo -e "$info" | egrep "^detected \([0-9]+\) subtitle" | egrep -o "[0-9]+"`
	subtitleids=`seq 0 $(($maxsubtitleid - 1))`
 
	# debug
	#echo -e "$videoids"
	#echo -e "$audioids"
	#echo -e "$subtitleids"
	#echo -e "$info";
	#return
	# /debug
 
	pushd "$TMPDIR" > /dev/null
	flagfile="demux_lock"
	rm -f "$flagfile"
	fifos=""
	#for id in $videoids; do
	#	id=`printf "0x%02x" $(($id + 224))`
	#	echo "+ demux: found video ID#$id" >&2 ;
	#	fifo="video_$id.fifo"
	#	mkfifo "$fifo"
	#	fifos="$fifos $fifo"
	#	(
	#		#wait for flagfile
	#		while [ ! -f "$flagfile" ]; do sleep 1; done ; 
	#		tcdemux -i "$fifo" -A $id > "video_$id.vob"
	#	) &
	#done
	for id in $audioids; do
		#id=`printf "0x%02x" $id`
		id=`printf "%d"  $(($id - 128))`
		echo "+ demux: found audio ID#$id" >&2 ;
 
		#cat "$fifo" | mplayer -quiet -vo null -ao null -aid $id -dumpaudio -dumpfile "audio_$id.ac3" -
 
		#tcextract -i "$IN" -x ac3 -a $id > "audio_$id.ac3"
		#mplayer -vo null -ao null -aid $id -dumpaudio -dumpfile "audio_$id.ac3" "$IN"
		#continue
 
		fifo="audio_$id.fifo"
		mkfifo "$fifo"
		fifos="$fifos $fifo"
		(
			#wait for flagfile
			while [ ! -f "$flagfile" ]; do sleep 1; done ; 
 
			#tcdemux -i "$fifo" -A $id > "audio_$id.vob"
			#mencoder "$fifo" -quiet -oac copy -aid $id -ovc frameno -o "$TMPDIR/audio_${id}.avi"
			#cat "$fifo" | tcextract -x ac3 -a $id > "audio_$id.ac3"
			cat "$fifo" | tcextract -t vob -x ac3 -a $id > "audio_$id.ac3"
			#cat "$fifo" | mplayer -quiet -vo null -ao null -aid $id -dumpaudio -dumpfile "audio_$id.ac3" -
		) &
	done
 
	#return
 
	for id in $subtitleids; do
		id=`printf "0x%02x"  $(($id + 32))`
		echo "+ demux: found subtitle ID#$id" >&2 ;
 
		#tcextract -i "$IN" -x ps1 -a $id > "subtitle_$id.ps1"
		#continue
 
		fifo="subtitle_$id.fifo"
		mkfifo "$fifo"
		fifos="$fifos $fifo"
		(
			#wait for flagfile
			while [ ! -f "$flagfile" ]; do sleep 1; done ; 
			#tcdemux -i "$fifo" -A $id > "subtitle_$id.vob"
			cat "$fifo" | tcextract -t vob -x ps1 -a $id > "subtitle_$id.ps1"
		) &
	done
	touch "$flagfile"
	pv -N "demuxing" "$IN" | tee $fifos >/dev/null
	rm -f "$flagfile"
	rm -f $fifos
	popd > /dev/null
 
	touch "$TMPDIR/demux_done"
	echo "+ demux: done" >&2 ;
}
 
function doall() {
	allcmd encode
}
 
function encodeall() {
	allcmd encode
}
 
function allcmd() {
	cmd="$1"
	echo "::: executing command $cmd for all in "`pwd` >&2 ;
	find -type f\
	| egrep -i '[.](vob|mpeg|mpg)$'\
	| xargs -I"FILE" -n1 vob2avi.sh FILE "$OPTS" $cmd 
}
 
function demuxall() {
	allcmd "demux"
}
 
function prepareall() {
	allcmd "prepare"
}
 
function info() {
	if [ "$DUMMYMODE" ]; then
		echo "ERROR: info called in DUMMY MODE" >&2
		exit -1
	fi
	init
	if [ -f "$TMPDIR/info" ]; then
		echo "+ info: done already" >&2
		cat "$TMPDIR/info"
		return
	fi
	echo "::: info: initializing" >&2
	echo "+ info: identifying $IN with mplayer" >&2
	res=`mplayer -ao null -vo null -frames 500 -identify "$IN"`
	echo "+ info: identifying $IN with tcprobe" >&2
	res="$res"`tcprobe -H 500 -i "$IN"`
	echo "+ info: saving info" >&2
	echo -e "$res" > "$TMPDIR/info"
	echo "+ info: done" >&2
	cat "$TMPDIR/info"
}
 
function prepare() {
	init
	if [ -f "$TMPDIR/prepared" ]; then
		echo "+ prepare: done already" >&2
		return
	fi
	if [ "$DUMMYMODE" ]; then
		echo "ERROR: prepare called in DUMMY MODE" >&2
		exit -1
	fi
	echo "::: preparing '$IN'" >&2 ; 
	demux
	setupaudio
	encode_real "-frames 500"
	audiomerge
	mplayer -vo xv -fs "$IN.avi"
	echo "prepared" > "$TMPDIR/prepared"
}
 
function encode() {
	init
	if [ -f "$TMPDIR/done" ]; then
		echo "+ encode: done already" >&2
		return
	fi
	if [ "$DUMMYMODE" ]; then
		echo "ERROR: encode called in DUMMY MODE" >&2
		exit -1
	fi
	echo "::: encoding '$IN'" >&2 ; 
	encode_real
	audiomerge
	echo "done" > "$TMPDIR/done"
	echo "::: DONE encoding '$IN'" >&2 ; 
}
 
function audiosync() {
	echo "::: syncing audio for '$IN'" >&2 ; 
	init
	setupaudio
	if [ -f "$TMPDIR/synced" ]; then
		echo "+ audiosync: done already" >&2
		return
	fi
	aids=`cat "$TMPDIR/aids" | xargs -I"##" -n1 basename "##" .ac3 | cut -f2 -d"_" `
	naids=`echo -e "$aids" | wc -l`
	info=`info`
 
	for x in `seq 0 $(($naids -1))`; do
		newaid=`echo $x`
		frametime=`echo -e "$info" | egrep -o 'frame_time=[+-]?[0-9]+' | cut -f2 -d'='`
		oldaid=`echo -e "$aids" | tail -n $(($naids - $x)) | head -n1`
		echo "+ oldaid:$oldaid newaid:$newaid" >&2
		syncms=`echo -e "$info" | grep "audio track.*-a $oldaid" -A2 | egrep -o 'av_fine_ms [+-]?[0-9]+' | cut -f2 -d' '`
		syncfr=`echo -e "$info" | grep "audio track.*-a $oldaid" -A2 | egrep -o '\-D [+-]?[0-9]+' | cut -f2 -d' ' | tr -d " \t\n\r"`
		echo "+ syncfr: $syncfr" >&2
		#sign=$(($syncfr < 0))
		if [ $(( 1 + $syncfr  )) -lt 1 ]; then
			sign="1"
		else
			sign="0"
		fi
		sync=`echo "scale=2 ; (($syncms ^ 2 / $frametime ^ 2)+0.5 >= 1) * (-1 * $sign) + $syncfr" | bc`
		echo "+ oldaid:$oldaid newaid:$newaid sync:$sync ($syncfr frames + $syncms ms [frametime:$frametime])" >&2
		if [ ! "$sync" -eq "0" ]; then
			echo "+ track needs syncing. Doing it as $TMPDIR/synced.avi" >&2
			avisync -i "$IN.avi" -o "$TMPDIR/synced.avi" -n $sync -a $newaid
			mv -f "$TMPDIR/synced.avi" "$IN.avi"
		fi
	done
 
	echo "done" > "$TMPDIR/synced"
	#echo -e "$aids\nn:$naids"
	#exit 0
}
 
 
function audiomerge() {
	echo "::: merging audio for '$IN'" >&2 ; 
	init
	vob="$IN"
 
	aids=`cat "$TMPDIR/aids"`
 
	echo "+ creating $TMPDIR/merged.avi with no sound" >&2 ; 
 
	#cp -f "${vob}.avi" "$TMPDIR/merged.avi"
	mencoder -ovc copy -nosound "$vob.avi" -o "$TMPDIR/merged.avi"
	#return
 
	track=0
	for aid in $aids; do
 
		if [ -f "$TMPDIR/$aid" ]; then
			echo "+ merging lang $TMPDIR/$aid as track $track" >&2 ; 
			if
				avimerge -i "$TMPDIR/merged.avi" -o "$TMPDIR/merged_tmp.avi" -p "$TMPDIR/$aid"
			then
				mv -f "$TMPDIR/merged_tmp.avi" "$TMPDIR/merged.avi"
			else 
				echo "+ ERROR merging lang $TMPDIR/$aid as track $track" >&2 ;
			fi
 
			track=$(($track + 1))
		fi
 
	done
 
	mv -f "$TMPDIR/merged.avi" "${vob}.avi"
 
	audiosync
}
 
function encode_real() {
	echo "::: encoding '$IN'" >&2 ; 
 
	addopts="$1"
	init
 
	crop=`cropdetect`
	scale=`scaledetect`
	vfopts=pp=de,crop=$crop,scale=$scale
	lavcopts=vcodec=mpeg4:vhq:vqmin=2:vbitrate=1800:threads=4:turbo
	opts="$addopts -vf-add kerndeint -oac copy -ovc lavc -vf-add $vfopts"
 
#	mencoder "$vob" $opts -oac copy -alang English -ovc frameno -o "${vob}_audio_en.avi"
#	mencoder "$vob" $opts -oac copy -alang Hungarian -ovc frameno -o "${vob}_audio_hu.avi"
	set -e
	pushd "$TMPDIR" > /dev/null
	echo "+ executing mencoder with args: $opts -lavcopts $lavcopts:vpass=1" >&2
	mencoder "$IN" $opts -lavcopts $lavcopts:vpass=1 -o '/dev/null'
	echo "+ executing mencoder with args: $opts -lavcopts $lavcopts:vpass=2" >&2
	mencoder "$IN" $opts -lavcopts $lavcopts:vpass=2 -o "$TMPDIR/final.avi"
	popd "$TMPDIR"
	set +e
 
	mv -f "$TMPDIR/final.avi" "$IN.avi"
 
#	unlink frameno.avi 2> /dev/null
	unlink $TMPDIR/divx2pass.log  2> /dev/null
}
 
function scaledetect() {
	echo "::: scale-detecting '$IN'" >&2 ; 
 
	vob="$IN"
	init
 
	scalecache="$TMPDIR/scale"
 
	if [ -f "$scalecache" ]; then
		echo "+ reading scale data from $scalecache" >&2
		scale=`cat "$scalecache"`
		echo "+ final scale parameters: $scale" >&2
		echo "$scale"
		return
	fi
 
	info=`info`
 
	#echo -e "$info"
	#exit 0
 
	res=`echo -e "$info" | egrep "^VIDEO:" | egrep -o "[0-9]+x[0-9]+"`
 
	case "$res" in
		720x576) 
			echo "+ video has PAL resolution, this is good" >&2
			aspect=`echo -e "$info" | egrep "^Movie-Aspect" | egrep -o "[0-9.]+:[0-9.]+" | tr ':' '/'`
			#aspect="4/3"
			ow="720"
			oh="576"
			;;
		*) 
			echo "+ video has $res resolution, this is BAD - UNIMPLEMENTED" >&2
			exit -1
			;;
	esac
 
	echo "+ video has $aspect aspect with $res resolution" >&2
 
	crop=`cropdetect "$vob"`
	w=`echo $crop | cut -f1 -d":"`
	h=`echo $crop | cut -f2 -d":"`
	echo "+ cropped video has resolution ${w}x${h}" >&2
 
	# original width:
	nw=$ow
	nh=`echo "scale=4;(1/($aspect))*($ow/$oh)*($w/$ow)*$h" | bc | cut -f1 -d'.'`
 
	# cropped width:
	#nw=$w
	#nh=`echo "scale=4;(1/($aspect))*($ow/$oh)*$h" | bc | cut -f1 -d'.'`
 
	scale="${nw}:${nh}"
 
	echo "+ storing final parameters to: $scalecache" >&2
 
	echo $scale > "$scalecache"
 
	echo "+ final scale parameters: $scale" >&2
 
	echo $scale
 
	echo "+ scale detect done." >&2
 
}
 
 
function cropdetect() {
	echo "::: crop-detecting '$IN'" >&2 ; 
 
	vob="$IN"
	init
 
	cropcache="$TMPDIR/crop"
 
	if [ -f "$cropcache" ]; then
		echo "+ reading crop data from $cropcache" >&2
		bestcrop=`cat "$cropcache"`
		echo "+ final parameters: $bestcrop" >&2
		echo $bestcrop
		return
	fi
 
#	info "$vob"
#	exit 0
 
	echo "+ crop detecting movie with mplayer..." >&2
 
	crops=`mktemp`
	for x in 2 5 10 20 30 50 100; do
		mplayer -vo null -ao null -frames 5 -ss $x:00 -vf cropdetect "$vob" 2>/dev/null | grep "\[CROP\]" | egrep -o "[0-9]+:[0-9]+:[0-9]+:[0-9]+"
	done > "$crops"
 
	cropss=`mktemp`
	cat "$crops" | while read crop; do
 
		x=`echo $crop | cut -f3 -d":"`
		y=`echo $crop | cut -f4 -d":"`
		xl=`echo $crop | cut -f1 -d":"`
		yl=`echo $crop | cut -f2 -d":"`
 
		echo $(( xl*yl))  $crop
 
	done > "$cropss"
 
	rm -f "$crops"
 
	bestcrop=`cat "$cropss" | sort -n | tail -n1 | cut -f2 -d' '`
 
	rm -f "$cropss"
 
	echo "+ best crop guessed by mplayer: $bestcrop" >&2
 
	inputconf=`mktemp`
 
	echo "W change_rectangle 0 1" >> "$inputconf"
	echo "w change_rectangle 0 -1" >> "$inputconf"
	echo "E change_rectangle 1 1" >> "$inputconf"
	echo "e change_rectangle 1 -1" >> "$inputconf"
	echo "S change_rectangle 2 1" >> "$inputconf"
	echo "s change_rectangle 2 -1" >> "$inputconf"
	echo "D change_rectangle 3 1" >> "$inputconf"
	echo "d change_rectangle 3 -1" >> "$inputconf"
 
	cropfile=`mktemp`	
 
	echo "+ fine tuning with mplayer... (use keys: wWeEsSdD)" >&2
 
	mplayer "$vob" -input conf="$inputconf" -vf rectangle=$bestcrop 2>/dev/null | egrep -o "[0-9]+:[0-9]+:[0-9]+:[0-9]+" > "$cropfile"
 
	rm -f "$inputconf"
 
	bestcrop=`tail -n1 "$cropfile"`
 
	rm -f "$cropfile"
 
	echo "+ storing final parameters to: $cropcache" >&2
 
	echo $bestcrop > "$cropcache"
 
	echo "+ final crop parameters: $bestcrop" >&2
 
	echo $bestcrop
 
	echo "+ crop detect done." >&2
}
 
#encode dvd.DVD_VIDEO.1216721459.01.vob 720:430:0:72 720:405
 
BANNER=`basename $0`" - GPLv3 - (c) 2008 by Erno Rigo <mcree_AT_tricon_DOT_hu>"
 
# try to get to our input file
if [ -z "$1" ]; then
	searchpath=`pwd`
else
	if [ -d "$1" ]; then
		searchpath="$1"
	else
		IN="$1"
	fi
fi
 
if [ ! -z "$searchpath" ]; then
	echo "::: searching for INPUT candidates in $searchpath... " >&2
	files=`find "$searchpath" -type f | egrep ".*[.](vob|mpeg|VOB|MPEG|Vob|Mpeg|mpg|MPG)$" 2>/dev/null`
	opts=`echo -e "$files" | while read file; do echo "'$file'" "'.'"; done`
 
	menutag=`mktemp`
 
	if
		echo whiptail --noitem --backtitle "$BANNER" --title "select INPUT file" \
		--menu "" 24 75 17 -- \
		"[Dummy mode]" "dummy"\
		$opts \
	 	2>"$menutag";
	then
		echo "::: selected tag: "`cat "$menutag"` >&2
		IN=`cat "$menutag"`
		rm -f "$menutag"
	else
		echo "::: exiting on user request" >&2
		rm -f "$menutag"
		exit 0;
	fi
 
fi
 
if [ ! -f "$IN" ]; then
	IN="Dummy mode!"
	DUMMYMODE="yes"
else 
	# calculate absolute path for IN
	wd=`dirname "$IN"`
	pushd "$wd" > /dev/null
	IN=`pwd`"/"`basename "$IN"`
	popd > /dev/null
fi
 
echo "WORKDIR: $wd, IN: $IN"
 
OPTS="$2"
 
if [ ! -z "$3" ]; then
	$3
	exit 0
fi
 
# MAIN MENU LOOP
while true; do
 
 
menutag=`mktemp`
 
echo "::: entering main menu" >&2
 
if \
whiptail --backtitle "IN: '$IN'" --title "main menu" \
--menu "OPTS: '$OPTS'" 24 75 16 -- \
"demux" "- Demultiplex INPUT" \
"setupaudio" "- Setup audio tracks to be included in OUTPUT" \
 2>"$menutag"; \
then
	tag=`cat "$menutag"`
	rm -f "$menutag"
	echo "::: selected tag: $tag" >&2
	$tag
else
	echo "::: exiting on user request" >&2
	rm -f "$menutag"
	exit 0;
fi
 
done
# END MAIN MENU LOOP
 
exit 0
 
opts=`cat<<EOF
encode:#encode vob
cropdetect:#try to detect crop parameters
scaledetect:#try to detect scale parameters
prepare:#prepare and preview encoding
ripaudio:#rip audio tracks
audiomerge:#rip audio tracks
help#print this help
deint#turn on deinterlacing [OBSOLETE - using kernel deint by default]
ripsubs:#rip subtitles from the vob
doall#search for prepared vobs in CWD (with the --test option) and do real encoding
EOF
`
 
# prepare long opts for getopt
optsf=`mktemp`
echo -e "$opts" | while read opt; do
	echo -ne ","`echo "$opt" | cut -f1 -d'#'`
done | cut -b 2- > $optsf
longopts=`cat $optsf`
rm -f $optsf
 
TEMP=`getopt -o "" --long $longopts -n "$0" -- "$@"`
 
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
 
# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"
 
showhelp=""
 
iopts=""
while true ; do
        case "$1" in
		--deint) iopts="-vf-add kerndeint" ; shift; ;;
                --encode) encode "$2" "$iopts"; shift 2 ; showhelp="false" ;;
                --cropdetect) cropdetect "$2" "$iopts" ; shift 2 ; showhelp="false" ;;
                --scaledetect) scaledetect "$2" "$iopts" ; shift 2 ; showhelp="false" ;;
                --prepare) testencode "$2" "$iopts"; shift 2 ; showhelp="false" ;;
                --ripaudio) ripaudio "$2" "$iopts" ; shift 2 ; showhelp="false" ;;
                --audiomerge) audiomerge "$2" "$iopts" ; shift 2 ; showhelp="false" ;;
                --ripsubs) ripsubs "$2" "$iopts" ; shift 2 ; showhelp="false" ;;
                --doall) doall; shift; showhelp="false" ;;
                --c-long)
                        # c has an optional argument. As we are in quoted mode,
                        # an empty parameter will be generated if its optional
                        # argument is not found.
                        case "$2" in
                                "") echo "Option c, no argument"; shift 2 ;;
                                *)  echo "Option c, argument \`$2'" ; shift 2 ;;
                        esac ;;
                --) shift ; break ;;
                *) echo "Internal error!" ; exit 1 ;;
        esac
done
#echo "Remaining arguments:"
#for arg do echo '--> '"\`$arg'" ; done
 
if [ -z "$showhelp" ]; then
	echo "usage: "`basename $0`" <OPT> <.VOB file>"
	echo "GPLv2 by Erno Rigo <mcree_AT_tricon.hu>"
	echo ""
	echo "OPT can be:"
	echo -e "$opts" | while read line; do
		opt=`echo "$line" | cut -f1 -d# | tr -d ':'`
		desc=`echo "$line" | cut -f2 -d#`
		helpline=`printf "%20s    %s" "--$opt" "$desc"`
		echo -e "$helpline"
	done
fi

Linkbacks

Use the following URL for manually sending trackbacks: http://rigo.info/lib/plugins/linkback/exe/trackback.php/en:blog:vob_to_avi_using_mencoder
en/blog/vob_to_avi_using_mencoder.txt · Utolsó módosítás: 2009-04-14 00:00 (külső szerkesztés)
CC Attribution-Noncommercial-Share Alike 4.0 International
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0