Lorsque je formate ou que je change d'ordinateur, je fais une sauvegarde de mes marque-pages. C'est normal. Depuis plus de cinq ans, j'ai donc des fichiers de sauvegarde qui s'entassent. Certains sont au format HTML Netscape, un standard. Mais d'autres sont au format JSON. Peu commodes à utiliser, je souhaitais les convertir en HTML. Malheur à moi !
Le nouvel Opera n'importe que des fichiers HTML. Firefox m'explique que le fichier n'est pas bon. Bien, tant pis, je me lance dans la conception d'un petit script pour convertir ces JSON en HTML. Et quoi de mieux que Javascript pour faire le job ?
<script type="text/javascript">
function parseObject(obj, result) {
result += "\n";
if(obj.constructor == Object) {
// Folders
if(obj.hasOwnProperty("children")) {
result += "<DT><H3 ";
var tmp = new Object();
for(var p in obj) {
if(p === "name" || p === "title") {
tmp["value"] = obj[p];
} else if(p === "dateAdded" || p === "date_added") {
tmp["date"] = obj[p];
} else if(p === "lastModified" || p === "date_modified") {
tmp["modified"] = obj[p];
}
}
// WebKit doesn't know how to use POSIX time è.é
// tmp["date"] = (tmp["date"]/1000000-11644473600);
// tmp["modified"] = (tmp["modified"]/1000000-11644473600);
result += 'ADD_DATE="' + String(tmp["date"]).substring(0,10) + '" ';
result += 'LAST_MODIFIED="' + String(tmp["modified"]).substring(0,10) + '">';
result += tmp["value"] + "</H3>\n";
result += parseObject(obj["children"], "\n");
}
// Links
else if(obj.hasOwnProperty("url") || obj.hasOwnProperty("uri")){
result += '<DT><A HREF="';
var tmp = new Object();
for(var p in obj) {
if(p === "uri" || p === "url") {
tmp["url"] = obj[p];
} else if(p === "name" || p === "title") {
tmp["value"] = obj[p];
} else if(p === "dateAdded" || p === "date_added") {
tmp["date"] = obj[p];
} else if(p === "lastModified" || p === "date_modified") {
tmp["modified"] = obj[p];
} else if(p === "charset") {
tmp["charset"] = obj[p];
} else if(p === "annos") {
tmp["description"] = obj[p][0].value;
}
}
// WebKit doesn't know how to use POSIX time è.é
// tmp["date"] = (tmp["date"]/1000000-11644473600);
// tmp["modified"] = (tmp["modified"]/1000000-11644473600);
result += encodeURI(tmp["url"]) + '" ADD_DATE="' + String(tmp["date"]).substring(0,10) + '" LAST_MODIFIED="';
result += String(tmp["modified"]).substring(0,10) + '" LAST_CHARSET="' + tmp["charset"] + '" >';
result += tmp["value"]+ "</A>\n";
if(tmp.hasOwnProperty("description"))
result += "<DD>" + tmp["description"] + "\n";
}
// Others
else {
for(var p in obj) {
result += parseObject(obj[p], "\n");
}
}
} else if(obj.constructor == Array) {
for(var p in obj) {
result += parseObject(obj[p], "\n");
}
}
return result;
}
function jsonToHTML(input){
var output = "<!DOCTYPE NETSCAPE-Bookmark-file-1>\n";
output += '<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">\n';
output += "<TITLE>Bookmarks</TITLE>\n";
output += "<H1>Bookmarks</H1>\n";
output += "<DL>\n";
// We construct a valid JSON, thanks to Firefox's shitty code è.é
input = input.replace(",]", "]");
input = input.replace(",}", "}");
input = input.replace("[,", "[");
input = input.replace("{,", "{");
var data = JSON.parse(input);
console.log(data);
output += parseObject(data, "\n");
output += "</DL>\n";
output += "</DL>";
return output;
}
// Usage : jsonToHTML(someInputFromATextarea);
</script>
Voir une démo -
Voir le dépôt GitHub
Il s'avère que j'ai trouvé pourquoi Firefox me retourne une erreur avec ces fichiers. Au départ il les exportait avec un léger défaut : en fin de fichier, il inscrivait ,]}
, ce qui, bien entendu, n'est pas valide. Je ne sais pas quand le tir a été corrigé, mais dorénavant les vieux fichiers de sauvegarde sont incompatibles…
Oh, et tant que j'y suis… Chrome et dérivés (Opera, Chromium…) tendent à ne pas savoir calculer un timestamp Unix correct. J'ai d'abord pensé qu'ils ajoutaient systématiquement un 0
après les deux premiers chiffres. Un simple ajout javascript aurait alors fait l'affaire :
tmp["date"] = String(tmp["date"]).slice(0,2).concat(String(tmp["date"]).slice(3));
tmp["modified"] = String(tmp["modified"]).slice(0,2).concat(String(tmp["modified"]).slice(3));
Mais en réalité ils vont plus loin dans leur incompétence : les trois premiers chiffres sont toujours 130
!
Intrigué (l'export en HTML donnant un timestamp correct), j'ai voulu creuser un peu plus. Il s'avère que Chrome n'utilise pas un timestamp Unix Epoch mais ce qui est communément appelé webkit timestamp, un format 64 bit qu'il partage avec Windows. Au lieu de compter les secondes depuis 1970, il compte les microsecondes depuis 1601. Pourquoi 1601 ? Oh, sans doute le fameux Ballmer Peak…
Tout ce qu'il y a à faire, c'est de diviser par un million pour avoir les secondes, puis de soustraire le nombre de secondes entre 1970 et 1601 (soit 11644473600, ne me remerciez pas). Soit le code :
tmp["date"] = (tmp["date"]/1000000-11644473600);
tmp["modified"] = (tmp["modified"]/1000000-11644473600);
Pour fonctionner, il nécessite que les fichier ip.sh
et rhythmbox.sh
soient présents dans le dossier ~/.conky
et exécutables. Il ne faut pas oublier d'installer les polices Open Logos et StyleBats.
Copie de ip.sh
:
#!/bin/bash
# # Test de validité IPv4 de l'adresse entrée (expression régulière)
function isIPv4 {
if [ $# = 1 ]
then
printf $1 | grep -Eq '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-4]|2[0-4][0-9]|[01]?[1-9][0-9]?)$'
return $?
else
return 2
fi
}
RETOUR=$(wget -qO- whatismyip.org)
isIPv4 $RETOUR && echo $RETOUR || echo "Pas de connexion"
Copie de
rhythmbox.sh
:
#!/bin/bash
case "$1" in
progress)
curr=`rhythmbox-client --no-start --print-playing-format "%te" | grep -v "Pas de lecture en cours" | grep -v Inconnu`
tot=`rhythmbox-client --no-start --print-playing-format "%td" | grep -v "Pas de lecture en cours" | grep -v Inconnu`
a=`date +'%S' | sed 's/^0\+//'`
if [ "$a" = "" ]; then
a=0
fi
b=$(( ($a*100) / 30 ))
c=$(( 200-$b ))
if [ "$curr" = "" ]; then
curr=0
fi
#si pas de durée totale, augmente pendant les 30 premieres secondes puis diminue pendant les 30 suivantes...
if [ "$tot" = "" ]; then
if [ $a -le 30 ]; then
expr $b
else
expr $c
fi
else
#Si durée totale déterminée, il faut traiter le retour de la commande donnant $curr (pour courant et $tot pour total, logique) qui renvoit une donnée sous la forme hh:mm:ss
d="1"
nbcurr=`echo $curr | wc -m` #nbr de caractères permet de déterminer si il y a seulement m:ss ou si il y a h:mm:ss (au minimum, m:ss)
posm=$(( $nbcurr-5 )) #pour faire une commande cut générique, il faut définir l'endroit ou on coupe d'ou posx (position des minutes ici)
if [ $posm -lt $d ]; then
posm=`` #Si position inférieur à 1, renvoit variable vide pour ne pas bloque cut
fi
posm2=$(( $nbcurr-4 )) #Forcément 1 indication minute (minimum)= pas besoin de vérifier si >1
posh=$(( $nbcurr-8 )) #Idem pour les heures
if [ $posh -lt $d ]; then
posh=``
fi
posh2=$(( $nbcurr-7 ))
currs=`echo $curr | tail -c3 | sed 's/^0\+//'` # Récupération des secondes forcément à la fin et supression du premier 0 pour ne pas avoir de problème de base (08 en hexa ou base 10)
if [ "$currs" = "" ]; then
currs=0 #si uniquement des zeros, ils sont tous supprimé donc redonner la valeur
fi
currma=`echo $curr | cut -c$posm-$posm2 | sed 's/^0\+//'`
if [ "$currma" = "" ]; then
currma=0 #idem pour minutes
fi
currm=$(( $currma*60 )) #conversion en secondes
if [ $posh2 -lt $d ]; then
currh=0
else
currha=`echo $curr | cut -c$posh-$posh2 | sed 's/^0\+//'`
if [ "$currha" = "" ]; then #idem heures
currha=0
fi
currh=$(( $currha*3600 )) # conversion en secondes
fi
currt=$(( ($currh+$currm) + $currs )) #calcule du nombre de secondes total
nbtot=`echo $tot | wc -m` #Même schema pour la suite mais avec la durée totale
tposm=$(( $nbtot-5 ))
if [ $tposm -lt $d ]; then
tposm=``
fi
tposm2=$(( $nbtot-4 ))
tposh=$(( $nbtot-8 ))
if [ $tposh -lt $d ]; then
tposh=``
fi
tposh2=$(( $nbtot-7 ))
tots=`echo $tot | tail -c3 | sed 's/^0\+//'` ## OK
if [ "$tots" = "" ]; then
tots=0
fi
totma=`echo $tot | cut -c$tposm-$tposm2 | sed 's/^0\+//'` ## OK
if [ "$totma" = "" ]; then
totma=0
fi
totm=$(( $totma*60 )) ## OK
if [ $tposh2 -lt $d ]; then
toth=0
else
totha=`echo $tot | cut -c$tposh-$tposh2 | sed 's/^0\+//'` ## OK
if [ "$totha" = "" ]; then
totha=0
fi
toth=$(( $totha*3600 )) ## OK
fi
tott=$(( ($toth+$totm) + $tots )) ## OK
expr $currt \* 100 / $tott #Et finalement expression du pourcentage accompli
fi
;;
esac
Copie du .conkyrc
:
# set to yes if you want Conky to be forked in the background
background yes
cpu_avg_samples 2
net_avg_samples 2
out_to_console no
# X font when Xft is disabled, you can pick one with program xfontsel
#font 7x12
#font 6x10
#font 7x13
#font 8x13
#font 7x12
#font *mintsmild.se*
#font -*-*-*-*-*-*-34-*-*-*-*-*-*-*
#font -artwiz-snap-normal-r-normal-*-*-100-*-*-p-*-iso8859-1
# Use Xft?
use_xft yes
# Xft font when Xft is enabled
xftfont Bitstream Vera Sans Mono:size=9
own_window_transparent yes
own_window_type override
own_window_hints undecorated,below,sticky,skip_taskbar,skip_pager
own_window_colour hotpink
# Text alpha when using Xft
xftalpha 0.8
on_bottom yes
# mail spool
mail_spool $MAIL
# Update interval in seconds
update_interval 1
# Create own window instead of using desktop (required in nautilus)
own_window yes
# Use double buffering (reduces flicker, may not work for everyone)
double_buffer yes
# Minimum size of text area
#minimum_size 280 5
#maximum_width 150
# Draw shades?
draw_shades yes
# Draw outlines?
draw_outline no
# Draw borders around text
draw_borders no
# Stippled borders?
stippled_borders 10
# border margins
border_margin 4
# border width
border_width 1
# Default colors and also border colors
default_color white
default_shade_color black
default_outline_color white
# Text alignment, other possible values are commented
#alignment top_left
#minimum_size 10 10
gap_x 13
gap_y 34
alignment top_right
#alignment bottom_left
#alignment bottom_right
# Gap between borders of screen and text
# Add spaces to keep things from moving about? This only affects certain objects.
use_spacer no
# Subtract file system buffers from used memory?
no_buffers yes
# set to yes if you want all text to be in uppercase
uppercase no
# boinc (seti) dir
# seti_dir /opt/seti
# Possible variables to be used:
#
# Variable Arguments Description
# acpiacadapter ACPI ac adapter state.
# acpifan ACPI fan state
# acpitemp ACPI temperature.
# adt746xcpu CPU temperature from therm_adt746x
# adt746xfan Fan speed from therm_adt746x
# battery (num) Remaining capasity in ACPI or APM
# battery. ACPI battery number can be
# given as argument (default is BAT0).
# buffers Amount of memory buffered
# cached Amount of memory cached
# color (color) Change drawing color to color
# cpu CPU usage in percents
# cpubar (height) Bar that shows CPU usage, height is
# bar's height in pixels
# downspeed net Download speed in kilobytes
# downspeedf net Download speed in kilobytes with one
# decimal
# exec shell command Executes a shell command and displays
# the output in torsmo. warning: this
# takes a lot more resources than other
# variables. I'd recommend coding wanted
# behaviour in C and posting a patch :-).
# execi interval, shell Same as exec but with specific interval.
# command Interval can't be less than
# update_interval in configuration.
# fs_bar (height), (fs) Bar that shows how much space is used on
# a file system. height is the height in
# pixels. fs is any file on that file
# system.
# fs_free (fs) Free space on a file system available
# for users.
# fs_free_perc (fs) Free percentage of space on a file
# system available for users.
# fs_size (fs) File system size
# fs_used (fs) File system used space
# hr (height) Horizontal line, height is the height in
# pixels
# i2c (dev), type, n I2C sensor from sysfs (Linux 2.6). dev
# may be omitted if you have only one I2C
# device. type is either in (or vol)
# meaning voltage, fan meaning fan or temp
# meaning temperature. n is number of the
# sensor. See /sys/bus/i2c/devices/ on
# your local computer.
# kernel Kernel version
# loadavg (1), (2), (3) System load average, 1 is for past 1
# minute, 2 for past 5 minutes and 3 for
# past 15 minutes.
# machine Machine, i686 for example
# mails Mail count in mail spool. You can use
# program like fetchmail to get mails from
# some server using your favourite
# protocol. See also new_mails.
# mem Amount of memory in use
# membar (height) Bar that shows amount of memory in use
# memmax Total amount of memory
# memperc Percentage of memory in use
# new_mails Unread mail count in mail spool.
# nodename Hostname
# outlinecolor (color) Change outline color
# pre_exec shell command Executes a shell command one time before
# torsmo displays anything and puts output
# as text.
# processes Total processes (sleeping and running)
# running_processes Running processes (not sleeping),
# requires Linux 2.6
# shadecolor (color) Change shading color
# stippled_hr (space), Stippled (dashed) horizontal line
# (height)
# swapbar (height) Bar that shows amount of swap in use
# swap Amount of swap in use
# swapmax Total amount of swap
# swapperc Percentage of swap in use
# sysname System name, Linux for example
# time (format) Local time, see man strftime to get more
# information about format
# totaldown net Total download, overflows at 4 GB on
# Linux with 32-bit arch and there doesn't
# seem to be a way to know how many times
# it has already done that before torsmo
# has started.
# totalup net Total upload, this one too, may overflow
# updates Number of updates (for debugging)
# upspeed net Upload speed in kilobytes
# upspeedf net Upload speed in kilobytes with one
# decimal
# uptime Uptime
# uptime_short Uptime in a shorter format
#
# seti_prog Seti@home current progress
# seti_progbar (height) Seti@home current progress bar
# seti_credit Seti@hoome total user credit
# variable is given either in format $variable or in ${variable}. Latter
# allows characters right after the variable and must be used in network
# stuff because of an argument
#${font Dungeon:style=Bold:pixelsize=10}I can change the font as well
#${font Verdana:size=10}as many times as I choose
#${font Perry:size=10}Including UTF-8,
#${font Luxi Mono:size=10}justo como este texto que o google traduz fêz o português
# stuff after 'TEXT' will be formatted on screen
#${font Grunge:size=12}${time %a %b %d}${alignr -25}${time %k:%M}
TEXT
${shadecolor black}${font openlogos:size=100}${color 73d216}${alignc}Ut${font}${color}${shadecolor}
${font Arial:size=20}${color 73d216}${alignc}Marvin${color}${font}
${color 73d216}Système ${color}$hr
${font StyleBats:size=10}P${font} Uptime: $uptime
${font StyleBats:size=10}A${font} Processeur : ${cpu}% ${color 73d216}${cpubar}$color
${font StyleBats:size=10}I${font} Mémoire : $memperc% $mem ${color 73d216}${membar}$color
${font StyleBats:size=10}I${font} Swap :$swapperc% ${color 73d216}${swapbar}$color
${font StyleBats:size=10}U${font} Batterie : $battery_percent% $battery_time
${color 73d216}Disque dur ${color}$hr
${font StyleBats:size=10}C${font} Racine : ${fs_used_perc /}% ${fs_free /} ${color 73d216}${fs_bar 5,120 /}$color
${font StyleBats:size=10}C${font} ${if_mounted /home}Home : ${fs_used_perc /home}% ${fs_free /home} ${color 73d216}${fs_bar 5,120 /home}$color
${font StyleBats:size=10}C${font} ${if_mounted /media/donnees}Données : ${fs_used_perc /media/donnees}% ${fs_free /media/donnees} ${color 73d216}${fs_bar 5,120 /media/donnees}$color
${color 73d216}Réseau ${color}$hr
${font StyleBats:size=10}M${font} eth0 : ${addr eth0}
${font StyleBats:size=10}X${font} wlan0 : ${addr wlan0}
${font StyleBats:size=10}B${font} inet0 : ${texeci 120 $HOME/.conky/ip.sh}
#${if_running rhythmbox}${color 73d216}Multimédia ${color}$hr
#${font StyleBats:size=10}2${font} Artiste : ${exec rhythmbox-client --print-playing-format "%ta"}
#${font StyleBats:size=10}J${font} Album : ${exec rhythmbox-client --print-playing-format "%at"}
#${font StyleBats:size=10}4${font} Titre : ${exec rhythmbox-client --print-playing-format "%tt"}
#${font StyleBats:size=10}7${font} Durée : ${exec rhythmbox-client --print-playing-format "%te"} sur ${exec rhythmbox-client --print-playing-format "%td"}
#${color 73d216}${execibar 1 $HOME/.conky/rhythmbox.sh progress}${color}${else}
#$endif
Lancement au démarrage
Lancer la commande :
sh -c "sleep 20; conky;"
Un problème récurrent lors de l'installation de GNU/Linux sur un Asus, c'est la désactivation du touchpad. Il y a bien une combinaison de touches qui, sous Windows, le permet. Mais, étrangement, le kernel n'émet aucun keycode sous Linux. Il faut donc paramétrer une autre combinaison de touche, lançant la commande
sh /home/USER/Scripts/toggle-touchpad.sh. On n'oubliera pas de rendre exécutable le script, et, encore une fois, de lire les notes :
#!/bin/bash
# Enable/Disable touchpad
# $device is found by using "xinput list"
device=13
property=132
mode="$(xinput --list-props $device | grep $property | cut -d':' -f2)"
if [ $mode -eq "1" ]
then
xinput --set-prop $device $property 0
else
xinput --set-prop $device $property 1
fi
Le script
screencaster.sh final qui semble fonctionner sur toute Buntu et dérivée, pour peu que l'on se conforme aux notes :
#!/bin/bash
#
# screencaster.sh - script to make screencasts on Linux
#
# For information about using this script:
# http://okiebuntu.homelinux.com/blog/?p=175
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
# Nécessite libavcodec-extra-52
# Il faut peut-être ffmpeg depuis le dépôt Medibuntu. Il faudra aussi ajouter un peu de code pour faire en sorte que la taille et l'offset soit divisible par deux
# parec a besoin d'un monitor pour fonctionner : trouver un input avec pactl list | egrep -A2 '^(\*\*\* )?Source #'
# On peut arrêter l'enregistrement en configurant un raccourci clavier et en utilisant xdotool : xdotool search --onlyvisible --classname "gnome-terminal" key "Return"
#
# Config
monitor="alsa_output.pci-0000_00_1b.0.analog-stereo.monitor"
echo "set-source-mute ${monitor} false" | pacmd >/dev/null
threads=2
volume=0.25
# list of programs we depend on
progs="xdpyinfo grep head sed ffmpeg pacat parec sox libavcodec-extra-52"
# check for programs we depend on
result=0
for prog in $progs
do
type -p $prog > /dev/null
if (( $? != 0 )); then
echo "Error: Cannot find required program '$prog'"
result=1
fi
done
if (( $result != 0 )); then
exit 1
fi
screenSize="$(xwininfo -root | grep 'geometry' | awk '{print $2;}')" # default if we cant detect it
screenOffset="0,0" # default to top-left corner
frameRate="24" # default frame rate
baseName="`date +%Y%m%d`-`date +%H%M%S`-screencast" # default base filename for capture
# attempt to detect the dimension of the screen for the default
dimensions=`xdpyinfo | grep 'dimensions:' | head -1 | \
sed -e 's/^.* \([0-9]\+x[0-9]\+\) pixels.*$/\1/'`
if [[ "$dimensions" =~ [0-9]+x[0-9]+ ]]; then
screenSize=$dimensions
fi
# collect command line settings
while getopts 'hs:o:t:r:p:w' param ; do
case $param in
s)
screenSize="$OPTARG"
;;
o)
screenOffset="$OPTARG"
;;
t)
timeToRecord="$OPTARG"
;;
w)
INFO=$(xwininfo)
WIN_GEO=$(echo $INFO | grep -oEe 'geometry [0-9]+x[0-9]+' | grep -oEe '[0-9]+x[0-9]+')
WIN_XY=$(echo $INFO | grep -oEe 'Corners:\s+\+[0-9]+\+[0-9]+' | grep -oEe '[0-9]+\+[0-9]+' | sed -e 's/\+/,/' )
screenOffset="$WIN_XY"
screenSize="$WIN_GEO"
;;
r)
frameRate="$OPTARG"
;;
*)
echo ""
echo "$0 - records screencast"
echo ""
echo "$0 [options] [base-filename]"
echo ""
echo "options:"
echo " -h show brief help"
echo " -s <size> screensize to record as <width>x<height>"
echo " -o <offset> offset off recording area as <xoffset>,<yoffset>"
echo " -t <time> time to record (in seconds)"
echo " -w window to record"
echo " -r framerate (FPS)"
echo ""
exit 0
;;
esac
done
shift $(( $OPTIND - 1 ))
# determine basename of files
if [ -n "$1" ] ; then
baseName="$1"
fi
echo ""
echo "Size = $screenSize"
echo "Offset = $screenOffset"
echo "Rate = $frameRate"
echo "Filename = $baseName"
# get ready to start recording
echo ""
if [ -n "$timeToRecord" ]; then
echo "Preparing to capture for $timeToRecord seconds."
else
echo "Preparing to capture."
echo "Press ENTER when finished capturing."
fi
sleep 3
echo ""
# start playing silence to make sure there is always audio flowing
pacat /dev/zero &
pidSilence=$!
# starts recording video using x11grab to make mpeg2video
ffmpeg -f alsa -ac 2 -i pulse \
-f x11grab -r "$frameRate" -s "$screenSize" -i :0.0+"$screenOffset" \
-acodec pcm_s16le -vcodec libx264 -vpre lossless_ultrafast \
-threads "$threads" -y "$baseName.avi" &
pidVideo=$!
#ffmpeg -y -an \
# -s "$screenSize" -r "$frameRate" -f x11grab -i :0.0+"$screenOffset" \
# -s "$screenSize" -r "$frameRate" -aspect 4:3 -vcodec mpeg2video -sameq \
# -f mpeg2video "$baseName.mpeg" &
#pidVideo=$!
# starts recording raw audio from output
parec -d "$monitor" --format=s16le --rate=44100 --channels=2 "$baseName-audio.raw" &
pidAudio=$!
# starts recording raw audio from input
rec -b 32 -r 44100 "$baseName-voice.wav" &
pidMicro=$!
echo ""
echo "Video recording started with process ID $pidVideo"
echo "Audio recording started with process ID $pidAudio"
echo "Voice recording started with process ID $pidMicro"
echo ""
# wait for recording to be done, either by timer or user hitting enter
if [ -n "$timeToRecord" ]; then
sleep "$timeToRecord"
else
read nothing
fi
# stop recordings
echo ""
echo "Terminating recordings ..."
kill -15 $pidVideo $pidAudio
kill -15 $pidSilence
kill -15 $pidMicro
wait
# filter and normalize the audio
echo ""
echo "Filtering and normalizing sound ..."
sox --norm -s -b 16 -L -r 44100 -c 2 -v "$volume" "$baseName-audio.raw" "$baseName-audio.wav" highpass 65 lowpass 12k
sox -v "$volume" "$baseName-audio.wav" "$baseName-audio2.wav"
sox --norm -s -b 32 -L -r 44100 -c 2 -v 2.0 "$baseName-voice.wav" "$baseName-voice2.wav" highpass 65 lowpass 12k
# Récupère le son du microphone
#echo ""
#echo "Extract audio from avi"
#ffmpeg -i "$baseName.avi" -ar 44100 -ac 2 "$baseName-2.wav"
# encode video and audio into mp4 file
echo ""
echo "Encoding to final Youtube format..."
sox "$baseName-audio2.wav" "$baseName-voice2.wav" -m "$baseName-final.wav"
ffmpeg -i "$baseName-final.wav" -acodec libfaac -ab 320k -y "$baseName-final.aac"
ffmpeg -i "$baseName.avi" -an -s 1280x720 -aspect 16:9 -acodec libfaac -ab 320k -vcodec libx264 -vpre fast -crf 18 -threads "$threads" -y "$baseName.mp4"
ffmpeg -isync -i "$baseName-final.aac" -i "$baseName.mp4" -s 1280x720 -aspect 16:9 -acodec libfaac -ab 320k -vcodec libx264 -vpre fast -crf 18 -threads "$threads" -y "$baseName-final.mp4"
#ffmpeg -isync -i "$baseName.wav" -i "$baseName.mpeg" -acodec mp2 -ab 192k -vcodec copy "$baseName.avi"
# convert to ogg - to turn on uncomment next three lines
#echo""
#echo "convert to theora"
#ffmpeg2theora "$baseName.avi" -o "$baseName.ogv"
# convert avi to flv - to turn on uncomment next three lines
#echo""
#echo "convert to theora"
# ffmpeg -i "$baseNamee.avi" -ab 56 -ar 44100 -b 200 -r 15 -s 320x240 -f flv "$baseNamee.flv"
echo ""
echo "DONE! Final media written in file $baseName.mp4"
echo ""
exit 0
Voilà, prochaine étape : un peu de python pour donner une interface simple. À noter que sous Gnome 3, un outil de screencast est incorporé d'office, via un raccourci-clavier dont personne ne se souvient, et qui n'est pas paramétrable ; il prend tout l'écran. Ma solution permet au moins de lancer un screencast à distance, selon des paramètres (30fps pour une vidéo en HD, c'est un
must), et surtout de choisir une fenêtre d'un simple clic.
Une solution m'est apparue : ffmpeg, que j'avais dénigré jusqu'ici, est en réalité la meilleure façon de faire un screencast sous GNU/Linux. Qualité vidéo irréprochable, post-traitement (dont une conversion en mp4 HD pour Youtube…), enregistrement des sons et du micro en parallèle, et la cerise sur le gâteau : tout est en ligne de commande.
Je me suis servi du script screencaster disponible ici sous licence GPL en le mixant un peu à ma sauce. Il reste donc sous licence GPL, mais n'a été testé que sous Ubuntu et dérivées jusqu'à présent. À ce titre, il nécessite libavcodec-extra-52, mais aussi ffmpeg du dépôt Medibuntu. Le reste des dépendances s'installe d'un simple sudo apt-get install xdpyinfo grep head sed pacat parec sox
.
Quant à l'utilisation, il suffit de le lancer avec l'option -h
pour comprendre assez rapidement. L'option -w
fait apparaitre un curseur, il n'y a plus qu'à cliquer sur la fenêtre de votre choix pour lancer l'enregistrement. Terminez alors l'enregistrement en tapant Entrée. Il existe une petite astuce pour éviter d'avoir à revenir sur la console pour arrêter l'enregistrement, et donc devoir couper la vidéo en post-prod : on peut arrêter l'enregistrement en configurant un raccourci clavier et en utilisant xdotool : xdotool search --onlyvisible --classname "gnome-terminal" key "Return"
.
#!/bin/bash
#
# screencaster.sh - script to make screencasts on Linux
#
# For information about using this script:
# http://www.guillaume.litaudon.fr/blog/
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
# list of programs we depend on
progs="xdpyinfo grep head sed ffmpeg pacat parec sox"
# check for programs we depend on
result=0
for prog in $progs
do
type -p $prog > /dev/null
if (( $? != 0 )); then
echo "Error: Cannot find required program '$prog'"
result=1
fi
done
if (( $result != 0 )); then
exit 1
fi
screenSize="$(xwininfo -root | grep 'geometry' | awk '{print $2;}')" # default if we cant detect it
screenOffset="0,0" # default to top-left corner
frameRate="24" # default frame rate
baseName="`date +%Y%m%d`-`date +%H%M%S`-screencast" # default base filename for capture
# attempt to detect the dimension of the screen for the default
dimensions=`xdpyinfo | grep 'dimensions:' | head -1 | \
sed -e 's/^.* \([0-9]\+x[0-9]\+\) pixels.*$/\1/'`
if [[ "$dimensions" =~ [0-9]+x[0-9]+ ]]; then
screenSize=$dimensions
fi
# collect command line settings
while getopts 'hs:o:t:r:p:w' param ; do
case $param in
s)
screenSize="$OPTARG"
;;
o)
screenOffset="$OPTARG"
;;
t)
timeToRecord="$OPTARG"
;;
w)
INFO=$(xwininfo)
WIN_GEO=$(echo $INFO | grep -oEe 'geometry [0-9]+x[0-9]+' | grep -oEe '[0-9]+x[0-9]+')
WIN_XY=$(echo $INFO | grep -oEe 'Corners:\s+\+[0-9]+\+[0-9]+' | grep -oEe '[0-9]+\+[0-9]+' | sed -e 's/\+/,/' )
screenOffset="$WIN_XY"
screenSize="$WIN_GEO"
;;
r)
frameRate="$OPTARG"
;;
*)
echo ""
echo "$0 - records screencast"
echo ""
echo "$0 [options] [base-filename]"
echo ""
echo "options:"
echo " -h show brief help"
echo " -s <size> screensize to record as <width>x<height>"
echo " -o <offset> offset off recording area as <xoffset>,<yoffset>"
echo " -t <time> time to record (in seconds)"
echo " -w window to record"
echo " -r framerate (FPS)"
echo ""
exit 0
;;
esac
done
shift $(( $OPTIND - 1 ))
# determine basename of files
if [ -n "$1" ] ; then
baseName="$1"
fi
echo ""
echo "Size = $screenSize"
echo "Offset = $screenOffset"
echo "Rate = $frameRate"
echo "Filename = $baseName"
# get ready to start recording
echo ""
if [ -n "$timeToRecord" ]; then
echo "Preparing to capture for $timeToRecord seconds."
else
echo "Preparing to capture."
echo "Press ENTER when finished capturing."
fi
sleep 3
echo ""
# start playing silence to make sure there is always audio flowing
pacat /dev/zero &
pidSilence=$!
# starts recording video using x11grab to make mpeg2video
ffmpeg -f alsa -ac 2 -i pulse \
-f x11grab -r "$frameRate" -s "$screenSize" -i :0.0+"$screenOffset" \
-acodec pcm_s16le -vcodec libx264 -vpre lossless_ultrafast \
-threads 0 -y "$baseName.mkv" &
pidVideo=$!
#ffmpeg -y -an \
# -s "$screenSize" -r "$frameRate" -f x11grab -i :0.0+"$screenOffset" \
# -s "$screenSize" -r "$frameRate" -aspect 4:3 -vcodec mpeg2video -sameq \
# -f mpeg2video "$baseName.mpeg" &
#pidVideo=$!
# starts recording raw audio
parec -d alsa_output.pci-0000_00_1b.0.analog-stereo.monitor --format=s16le --rate=44100 --channels=2 "$baseName.raw" &
pidAudio=$!
echo ""
echo "Video recording started with process ID $pidVideo"
echo "Audio recording started with process ID $pidAudio"
echo ""
# wait for recording to be done, either by timer or user hitting enter
if [ -n "$timeToRecord" ]; then
sleep "$timeToRecord"
else
read nothing
fi
# stop recordings
echo ""
echo "Terminating recordings ..."
kill -15 $pidVideo $pidAudio
kill -15 $pidSilence
wait
# filter and normalize the audio
echo ""
echo "Filtering and normalizing sound ..."
sox --norm -s -b 16 -L -r 44100 -c 2 -v 0.33 "$baseName.raw" "$baseName.wav" highpass 65 lowpass 12k
# Récupère le son du microphone
echo ""
echo "Extract audio from mkv"
ffmpeg -i "$baseName.mkv" -ar 44100 -ac 2 "$baseName-2.wav"
# encode video and audio into mp4 file
echo ""
echo "Encoding to final Youtube format..."
sox "$baseName.wav" "$baseName-2.wav" -m -p gain | ffmpeg -f sox -i - -i "$baseName.mkv" -s 1280x720 -aspect 16:9 -vcodec libx264 -vpre fast -crf 18 -threads 0 -acodec libfaac -ab 320k -y "$baseName.mp4"
#ffmpeg -isync -i "$baseName.wav" -i "$baseName.mpeg" -acodec mp2 -ab 192k -vcodec copy "$baseName.avi"
# convert to ogg - to turn on uncomment next three lines
#echo""
#echo "convert to theora"
#ffmpeg2theora "$baseName.avi" -o "$baseName.ogv"
# convert avi to flv - to turn on uncomment next three lines
#echo""
#echo "convert to theora"
# ffmpeg -i "$baseNamee.avi" -ab 56 -ar 44100 -b 200 -r 15 -s 320x240 -f flv "$baseNamee.flv"
echo ""
echo "DONE! Final media written in file $baseName.mp4"
echo ""
exit 0