Kevin Decherf
Blog
FFmpeg: converting M4A files to MP3 with the same bitrate
14/01/2012
Hello World,
Today I show you a (really) tiny tip to convert M4A files to MP3 keeping bitrate with FFmpeg.
By using the command ffmpeg -i thefile we obtain data about all streams of the file (codec, bitrate, …), like this:
$ ffmpeg -i test.m4a
[...]
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test.m4a':
Metadata:
major_brand : M4A
minor_version : 1
compatible_brands: M4A mp42isom
creation_time : 2012-01-06 13:08:47
composer : Tiësto
title : clublife_episode249
artist : Tiësto
album : Tiësto
encoder : GarageBand 6.0.4
Duration: 00:59:04.87, start: 0.000000, bitrate: 324 kb/s
Chapter #0.0: start 0.000000, end 31.000000
Metadata:
title : Begin
[...]
Stream #0.0(eng): Subtitle: tx3g / 0x67337874, 0 kb/s
Metadata:
creation_time : 2012-01-06 13:08:47
Stream #0.1(eng): Subtitle: tx3g / 0x67337874
Metadata:
creation_time : 2012-01-06 13:08:47
Stream #0.2(eng): Audio: aac, 44100 Hz, stereo, s16, 319 kb/s
Metadata:
creation_time : 2012-01-06 13:08:47
Stream #0.3(eng): Video: mjpeg, yuvj444p, 300x300 [PAR 72:72 DAR 1:1], 2 kb/s, 0k fps, 600 tbr, 600 tbn, 600 tbc
Metadata:
creation_time : 2012-01-06 13:08:47
Well, the line we need to use for the bitrate is: Stream #0.2(eng): Audio: aac, 44100 Hz, stereo, s16, 319 kb/s. Now we can play with grep and awk to extract 319 (according to the example line):
$ ffmpeg -i test.m4a 2>&1 | grep Audio | awk -F', ' '{print $5}' | cut -d' ' -f1
319
This output will be used for the -ab argument:
ffmpeg -i test.m4a -ab `ffmpeg -i test.m4a 2>&1 | grep Audio | awk -F', ' '{print $5}' | cut -d' ' -f1`k test.mp3
Finally, we verify the new file:
$ ffmpeg -i test.mp3
[...]
Input #0, mp3, from 'test.mp3':
Metadata:
major_brand : M4A
minor_version : 1
compatible_brands: M4A mp42isom
creation_time : 2012-01-06 13:08:47
composer : Tiësto
title : clublife_episode249
artist : Tiësto
album : Tiësto
encoder : Lavf53.2.0
Duration: 00:59:04.93, start: 0.000000, bitrate: 320 kb/s
Stream #0.0: Audio: mp3, 44100 Hz, stereo, s16, 320 kb/s
Enjoy it !
Iftop : utiliser l’interface par défaut
10/09/2011
Je ne poste pas souvent ces temps-ci, mais en ce début de week-end je vous offre une petite astuce pour iftop, l’utilitaire qui permet d’afficher en détail le traffic entrant et sortant d’une interface réseau.
Venant de migrer sur tmux, je me suis réservé une fenêtre pour iftop. Le soucis c’est qu’il faut préciser à chaque fois quelle interface doit utiliser ce dernier au lancement et je ne suis pas tout le temps sur un réseau filaire. Voilà une petite astuce pour contourner ce problème :
iftop -i `ip route | grep -E "^default" | awk -F' ' '{print $5}' | sed -n 1p`
Cette commande va récupérer l’interface par défaut via la table de routage. S’il y a plusieurs routes par défaut, on prend la première. A noter que je pars du principe que la route par défaut précise toujours une interface et que vous avez installé ip, exemple :
% ip route default via 192.168.0.1 dev eth0 proto static
Enjoy it !
Java : faire des sommes SHA-512 comme un malpropre
23/07/2011
Ou comment perdre plusieurs heures sur un problème bien planqué et très con. Dans le cadre d’un projet je devais stocker la somme SHA-512 de chaînes de caractères. Je suis parti à la recherche d’un bout de code pour faire ce que je voulais et j’ai trouvé le code suivant :
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(myString.getBytes());
byte[] mb = md.digest();
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < mb.length; i++) {
hexString.append(Integer.toHexString(0xFF & mb[i]));
}
On calcule la somme d’un mot et on obtient ceci :
f4baf3aec5ea176f1e641bdfaa1fa8fc25b7d6275b2690df1da571d6dc8bc8293923f2245bdb57be5a20a274612b9ccb232d91e9d840db4a6c62709d80f92e
Et un sha512sum (dans un terminal) pour le même mot nous donne ceci :
f4baf3aec5ea176f01e641bdfaa1fa8f0c25b7d6275b2690df1da571d6dc8bc8293923f2245bdb57be5a20a274612b9ccb232d91e9d840db4a6c62709d80f92e
Avez-vous remarqué que bien qu’elles se ressemblent, ces chaînes ne correspondent pas ?
La première fait 126 caractères alors qu’une somme SHA-512 doit en faire 128 … Je vous laisse trouver les deux caractères manquants … Vous m’en voulez ? Ok, les deux caractères manquants sont … des 0.
Mais pourtant il y en a déjà dans les deux chaînes, non ? En effet. Après quelques heures de recherche je me lance la suggestion suivante sachant qu’on calcule la somme par pas de deux caractères hexadécimaux : et si le 0 de gauche n’était jamais ajouté ?
Je me penche sur la méthode Integer.toHexString() en regardant la Javadoc :
This value is converted to a string of ASCII digits in hexadecimal (base 16) with no extra leading 0s
TOUT S’EXPLIQUE. Du coup, on doit rajouter un zéro à la main quand il le faut :
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(myString.getBytes());
byte[] mb = md.digest();
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < mb.length; i++) {
Integer n = 0xFF & mb[i];
if (n < 16) {
hexString.append("0");
}
hexString.append(Integer.toHexString(n));
}
La morale de cette histoire : coder la nuit pour trouver ce genre d’erreur, c’est cool.
Enjoy it !
PS : j’offre une bière à la première personne qui trouve le mot correspondant à cette somme SHA-512
Java : un équivalent à Iconv//TRANSLIT
19/06/2011
Il y a deux ans j’avais publié un petit billet sur le nettoyage d’accents en PHP à l’aide d’Iconv. J’ai eu besoin de faire la même chose en Java récemment, seulement le mode //TRANSLIT n’existe pas.
Fort heureusement, une petite recherche m’a permis de trouver mon bonheur :
String decomposed = Normalizer.normalize(accented, Normalizer.Form.NFKD);
StringBuilder buf = new StringBuilder();
for (int idx = 0; idx < decomposed.length(); ++idx) {
char ch = decomposed.charAt(idx);
if (ch < 128)
buf.append(ch);
}
String filtered = buf.toString();
En résumé, ce bout de code décompose les caractères accentués en suite de caractères simples (exemple : è donne e`) puis ne conserve que les caractères ASCII (code ASCII < 128).
Enjoy it !
Java/Jersey: A CORS-Compliant REST API (die JSONP die)
19/06/2011
Cross-Domain AJAX request is the developer’s nightmare with the awful JSONP workaround. But we can use a simple standard to kick off this bad practice.
Reminder
When a developer needs to make cross-domain requests (AJAX requests on another (sub-)domain or non-standard port, limited by browsers), he often uses the JSONP workaround : we add a Javascript callback in the API response and we ‘eval’ it.
For a recent project I refused to use JSONP to make my REST API cross-domain compatible, so I looked for an alternative solution. This solution is Cross-Origin Resource Sharing (CORS), a W3C standard.
Synopsis
In the last revision of the document, new headers are added to the HTTP protocol (Not used by RFC 2616) and a special request (preflight request) was created for cross-domain rights access control during an AJAX request.
Browser side
- Origin: shows the request domain
- Access-Control-Request-Method: shows the request HTTP verb
- Access-Control-Request-Headers: shows additional headers used by browser and must be authorised by server to continue AJAX requests
Server side
- Access-Control-Allow-Origin: indicates authorised domains to make cross-domain requests (should contain at least value of ‘Origin’ header or ‘*’)
- Access-Control-Allow-Credentials: indicates if server allow credentials during CORS requests
- Access-Control-Expose-Headers: indicates allowed headers to be sent to the browser
- Access-Control-Max-Age: indicates how long a response to a preflight request can be cached
- Access-Control-Allow-Methods: indicates all allowed HTTP verbs for cross-domain requests (should contain at least the ‘Access-Control-Request-Method’ header value)
- Access-Control-Allow-Headers: indicates allowed custom headers to be used by browser during cross-domain requests (should contain at least the ‘Access-Control-Request-Headers’ header value)
In this post, I don’t use Access-Control-Allow-Credentials, Access-Control-Expose-Headers and Access-Control-Max-Age headers.
How does it work ?
For standard requests, the browser will add Origin and Access-Control-Request-Method headers. A preflight request will be executed before the actual request if it contains custom headers, if it uses another HTTP verb than GET or POST or also if the body isn’t in text/plain format (ie. application/json).
There is a preflight request made by Firefox :
OPTIONS /url HTTP/1.1 Host: 127.0.0.1:5555 User-Agent: Mozilla/5.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive Origin: http://127.0.0.1 Access-Control-Request-Method: POST Access-Control-Request-Headers: x-requested-with
We can see Origin, Access-Control-Request-Method and Access-Control-Request-Headers headers. After this request, Firefox waits a similar response:
X-Powered-By: Servlet/3.0 Server: GlassFish Server Open Source Edition 3.0.1 Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: x-requested-with
After this, Firefox can continue with its requests and adds a custom header:
X-Requested-With: XMLHttpRequest
And our API ?
Well, now we modify our API to be CORS-compliant using Java and Jersey. You can add a simple method like this:
private String _corsHeaders;
private Response makeCORS(ResponseBuilder req, String returnMethod) {
ResponseBuilder rb = req.ok()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
if (!"".equals(returnMethod)) {
rb.header("Access-Control-Allow-Headers", returnMethod);
}
return rb.build();
}
private Response makeCORS(ResponseBuilder req) {
return makeCORS(req, _corsHeaders);
}
Because of I didn’t find the catch-all @Path, we need to add methods as many as paths the API manage:
// This OPTIONS request/response is necessary
// if you consumes other format than text/plain or
// if you use other HTTP verbs than GET and POST
@OPTIONS
@Path("/myresource")
public Response corsMyResource(@HeaderParam("Access-Control-Request-Headers") String requestH) {
_corsHeaders = requestH;
return makeCORS(Response.ok(), requestH);
}
@GET
@Path("/myresource")
public Response myResource() {
// myResponse is a ResponseBuilder object
return makeCORS(myResponse);
}
These code snippets are given only as an example, you can change it to build Access-Control-Allow-Methods according to the API’s WADL scheme or add a restrictive Access-Control-Allow-Origin rule.
What about browser compatibility ?
With this standard you can miss Internet Explorer 6 and 7. Internet Explorer 8 is saved by a new XDomainRequest object replacing XMLHttpRequest but seems to be not compatible with preflight requests. Other browsers are globally compatible with their last versions.
More information:
- W3C Worksheet about CORS
- Using CORS with Firefox 3.5
- XDomainRequest object on MSDN
- RFC 2616: HTTP Protocol
Enjoy it !
Java/Jersey : une API REST Cross-Domain sans JSONP
02/06/2011
Ah les joies d’AJAX et du Cross-Domain … Ou plutôt le cauchemar des développeurs. Aujourd’hui, je vais vous présenter un concept pour rendre rapidement et simplement une API REST Java/Jersey compatible avec la norme W3C CORS pour faire du Cross-Domain sans utiliser JSONP.
Contexte
La plupart du temps, quand un développeur doit faire des requêtes Cross-Domain, il n’a pas d’autres choix que d’utiliser du JSONP : un callback est injecté dans une réponse en JSON puis le résultat est directement exécuté en Javascript. Cette méthode est lourde et relativement sale (et sur le coup j’avoue ne pas être assez sale pour l’utiliser).
Dans le cadre d’une mission où nous devions réaliser une API REST, j’ai décidé de trouver une autre solution que de faire du JSONP. La réponse se trouve dans la dernière révision de la norme Cross-Origin Resource Sharing (CORS) du W3C.
Principe
Cette dernière révision nous présente l’ajout d’en-têtes spéciaux (qui ne font pas partie de la RFC 2616) et de la preflight request exécutée par le navigateur avant d’envoyer sa requête AJAX pour vérifier les permissions d’accès.
Côté navigateur
- Origin: indique le domaine de la requête
- Access-Control-Request-Method: indique la méthode utilisée dans la requête
- Access-Control-Request-Headers: indique l’en-tête qui sera utilisé par le navigateur et devra être autorisé par le serveur pour terminer la requête (utilisé lors d’une requête preflight)
Côté serveur
- Access-Control-Allow-Origin: indique le(s) domaine(s) autorisé(s) à faire des requêtes Cross-Domain (doit contenir au minimum le résultat de Origin ou *)
- Access-Control-Allow-Credentials: indique si l’utilisation de credentials est autorisée lors d’une requête Cross-Domain (Cookie, HTTP Authentication, …)
- Access-Control-Expose-Headers: indique les en-têtes pouvant être exposés sans risque à un navigateur
- Access-Control-Max-Age: indique le temps dont une réponse à une preflight request peut être mise en cache par le navigateur
- Access-Control-Allow-Methods: indique la liste des verbes HTTP pouvant être utilisés pour une requête Cross-Domain (doit contenir au minimum le résultat de Access-Control-Request-Method)
- Access-Control-Allow-Headers: indique la liste des en-têtes personnalisés autorisés pour une requête Cross-Domain (doit contenir au minimum le résultat de Access-Control-Request-Headers)
Dans ce billet, je mets de côté Access-Control-Allow-Credentials, Access-Control-Expose-Headers et Access-Control-Max-Age.
Dans les faits
En condition normale, le navigateur va ajouter les en-têtes Origin et Access-Control-Request-Method lors de la requête. Une requête préliminaire sera faite (preflight request) si des en-têtes personnalisés sont présents, si la requête utilise une méthode autre que GET et POST ou encore que le client envoie des données qui ne sont pas au format text/plain (du JSON par exemple).
Voici un exemple de preflight request envoyée par Firefox :
OPTIONS /monurl HTTP/1.1 Host: 127.0.0.1:5555 User-Agent: Mozilla/5.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive Origin: http://127.0.0.1 Access-Control-Request-Method: POST Access-Control-Request-Headers: x-requested-with
On remarque bien les en-têtes Origin, Access-Control-Request-Method et Access-Control-Request-Headers. Maintenant, Firefox s’attend à recevoir une réponse de ce style de la part du serveur (exemple) :
X-Powered-By: Servlet/3.0 Server: GlassFish Server Open Source Edition 3.0.1 Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: x-requested-with
A ce niveau, Firefox sait qu’il peut faire des requêtes AJAX vers ce serveur, il continue donc avec ses requêtes normales en ajoutant son en-tête personnalisé :
X-Requested-With: XMLHttpRequest
Et notre API ?
J’y viens, côté code maintenant nous considérons avoir une API REST déjà faite (en utilisant Jersey). Il suffit d’ajouter une fonction similaire à celle-là :
private String _corsHeaders;
private Response makeCORS(ResponseBuilder req, String returnMethod) {
ResponseBuilder rb = req.ok()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
if (!"".equals(returnMethod)) {
rb.header("Access-Control-Allow-Headers", returnMethod);
}
return rb.build();
}
private Response makeCORS(ResponseBuilder req) {
return makeCORS(req, _corsHeaders);
}
Puis d’ajouter autant de fonctions comme celle-ci que de @Path à gérer (je n’ai pas réussi à trouver le @Path “catch-all”) :
// La méthode OPTIONS doit être gérée si vous faites des requêtes
// avec autre chose que du GET, POST ou que le client transmet
// des données dans un format différent de text/plain
@OPTIONS
@Path("/maressource")
public Response corsMaRessource(@HeaderParam("Access-Control-Request-Headers") String requestH) {
_corsHeaders = requestH;
return makeCORS(Response.ok(), requestH);
}
@GET
@Path("/maressource")
public Response maRessource() {
// Traitement de la requête, ResponseBuilder maReponse
return makeCORS(maReponse);
}
Bien entendu, ceci n’est donné qu’à titre d’exemple et libre à vous d’adapter. Entre autre renseigner dynamiquement Access-Control-Allow-Methods en fonction de l’API et de son WADL ou encore restreindre Access-Control-Allow-Origin.
Et la compatibilité dans tout ça ?
Point de vue compatibilité avec cette petite norme laissez tomber Internet Explorer 6 et 7, quant à Internet Explorer 8 on est sauvé par l’ajout d’un objet spécial XDomainRequest remplaçant XMLHttpRequest. A noter cependant que l’objet XDomainRequest ne semble pas être compatible avec les preflight requests. Pour les autres navigateurs ça tourne globalement partout avec les dernières versions.
Plus d’informations :
- Document de travail du W3C pour la norme CORS
- Présentation détaillée de l’utilisation de CORS avec Firefox 3.5
- Présentation de l’objet XDomainRequest sur MSDN
- RFC 2616 : Protocole HTTP
Enjoy it !
Magento : bloc page/template_links et classes CSS
22/01/2011
C’est au cours d’une mission sur Magento que j’ai remarqué un fonctionnement plutôt limitant du bloc page/template_links. En effet on ne peut pas assigner de classes personnalisées à la balise li. Voici la solution (pour les plus fainéants
).

Problème
Vous avez surement déjà utilisé un bloc de ce type, il vous permet d’ajouter des liens depuis les fichiers xml de configuration du thème :
<block type="page/template_links" name="top.links" as="topLinks">
<action method="addLink" translate="label title" module="customer">
<label>My Account</label>
<url helper="customer/getAccountUrl"/>
<title>My Account</title>
<prepare/>
<urlParams/>
<position>10</position>
<liParams>class="myaccount" id="topAccount"</liParams>
</action>
</block>
Ce bloc, si pratique, a une faiblesse. Du moins avec le template par défaut :
app/design/frontend/base/default/template/page/template/links.phtml
<?php $_links = $this->getLinks(); ?>
<?php if(count($_links)>0): ?>
<ul class="links"<?php if($this->getName()): ?> id="<?php echo $this->getName() ?>"<?php endif;?>>
<?php foreach($_links as $_link): ?>
<li<?php if($_link->getIsFirst()||$_link->getIsLast()): ?> class="<?php if($_link->getIsFirst()): ?>first<?php endif; ?><?php if($_link->getIsLast()): ?> last<?php endif; ?>"<?php endif; ?> <?php echo $_link->getLiParams() ?>><?php echo $_link->getBeforeText() ?><a href="<?php echo $_link->getUrl() ?>" title="<?php echo $_link->getTitle() ?>" <?php echo $_link->getAParams() ?>><?php echo $_link->getLabel() ?></a><?php echo $_link->getAfterText() ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
On remarquera qu’il n’est pas possible, du moins pour les premiers et derniers éléments de listes, d’avoir un attribut class assigné via le paramètre liParams du layout (cf. exemple du début) car cela fait doublon.
La solution
La solution consiste à traiter le contenu de la méthode getLiParams() afin d’y inclure les classes first et/ou last en fonction de la position du lien, évitant ainsi de générer un attribut html en double. En mettant le code PHP directement dans le fichier phtml, cela donne :
<?php $_links = $this->getLinks(); ?>
<?php if (count($_links) > 0): ?>
<ul class="links"<?php if ($this->getName()): ?> id="<?php echo $this->getName() ?>"<?php endif; ?>>
<?php foreach ($_links as $_link): ?>
<?php
$liparams = $_liparams = $_link->getLiParams();
// Is class exists in liparams ?
if (preg_match('`class="([^"]*)"`i', $_liparams, $rtn)) {
$orig = $rtn[0];
$classes = explode(' ', $rtn[1]);
if ($_link->getIsFirst())
$classes[] = 'first';
if ($_link->getIsLast())
$classes[] = 'last';
$rplc = implode(' ', $classes);
$liparams = str_replace($orig, 'class="' . $rplc . '"', $_liparams);
} else {
if ($_link->getIsFirst())
$liparams .= ' class="first ';
if ($_link->getIsLast()) {
if (!$_link->getIsFirst())
$liparams .= ' class="';
$liparams .= 'last';
}
if ($liparams != $_liparams)
$liparams = rtrim($liparams) . '"';
}
?>
<li<?php if($liparams): ?> <?php echo $liparams ?><?php endif; ?>><?php echo $_link->getBeforeText() ?><a href="<?php echo $_link->getUrl() ?>" title="<?php echo $_link->getTitle() ?>" <?php echo $_link->getAParams() ?>><?php echo $_link->getLabel() ?></a><?php echo $_link->getAfterText() ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
De manière plus propre, on pourra mettre ce bout de code en tant qu’Helper dans un module perso. L’appel devient donc :
<?php $_links = $this->getLinks(); ?>
<?php if(count($_links)>0): ?>
<ul class="links"<?php if($this->getName()): ?> id="<?php echo $this->getName() ?>"<?php endif;?>>
<?php foreach($_links as $_link): ?>
<?php
// Fix for li classes issue.
$fixhlp = Mage::helper('mymodule/myhelper');
$liparams = $fixhlp->liParamsHelper($_link);
?>
<li<?php if($liparams): ?> <?php echo $liparams ?><?php endif; ?>><?php echo $_link->getBeforeText() ?><?php if($_link->getLabel()): ?><a href="<?php echo $_link->getUrl() ?>" title="<?php echo $_link->getTitle() ?>" <?php echo $_link->getAParams() ?>><?php echo $_link->getLabel() ?></a><?php endif; ?><?php echo $_link->getAfterText() ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
Enjoy it !
Jdep-grapher: make a dependency tree of your Java projects
14/01/2011
Oh, my first english post … so amazing (isn’t it ?). Well, I present a little (and awful) bash dependency graph generator for Java projects.
One goal of my job is to maintain the Quercus project. It is currently highly dependent of Resin, another Caucho’s project, and a removal is needed. To know what I need to remove first, I wanted a graphical representation of all links between projects. We can consider that dependencies could be computed with ‘import’ Java keyword. Yes I know, it’s not a perfect dependencies representation but it’s currently the best way I found (and faster). Finally, we need a graph generation tool … I found GraphViz which can be directly used in CLI and we use DOT format file to make the graph specifications. One day after … Jdep-grapher is released.
How does it work ?
It will first get the list of files to parse (with find) and extract all lines beginning with ‘import’ and ‘package’ (we use temp files with mktemp) :
find $DIR -type f -name "*.java" > $FILES
grep -E "^package|^import" $(< $FILES) | awk -F':' '{print $2}' > $GREP
Next it creates links between packages and uses DOT format :
while read type name
do
name=`echo $name | tr -d ";"`
pkg=`echo $name | tr -d "."`
if [[ "$type" == "package" ]]; then
echo "$pkg [label=\"$name\", style = filled, shape = box];"
CPKG=$pkg
else
ALL=`echo $pkg | grep "\*" | wc -l`
SUP=""
LNK=""
if [[ $ALL -eq 1 ]]; then
pkg=`echo $pkg | sed s/"*"/"allpkg"/`
SUP=", color=red, style = filled"
LNK=" [color=red]"
fi
echo "$pkg [label=\"$name\"$SUP];"
echo "$CPKG -> $pkg $LNK;"
fi
done < $GREP | sort -u > $COMPUTE
sort -u at the end of loop will automatically remove duplicates and send the result to a new temp file. All-inclusion packages (with *) are filled in red.We can exclude useless links (eg. internal dependencies) with :
grep -vE "$EXCLUDE" $COMPUTE > $TMPDOT
To reduce the weight of graph, it removes single nodes :
CT=`grep -E "$rpkg( |;)" $TMPDOT | wc -l` if [[ $CT -gt 1 ]]; then echo $rpkg $rop $rchild >> $TMPDOT2 fi
Finally, the script closes the DOT file and launches graphviz …
echo "digraph G {" > $DOT
cat $TMPDOT2 >> $DOT
echo "}" >> $DOT
fdp -Tpng < $DOT > $GRAPHEnjoy
Example : Resin Dependency Graph of Quercus

Note : the script currently uses ‘fdp’ from graphviz which is not fully optimized for this kind of graph. Tell me if you have any other solution
More information on GitHub.
Clôner un répertoire et déplacer les éléments obsolètes avec rsync
09/01/2011
Depuis le temps que je devais le faire, hier j’ai mis en place ma petite stratégie de sauvegarde avec un script perso et rsync. Je voulais avoir un clône parfait de mes dossiers : ne pas garder les éléments qui ne sont plus dans la source. Et si jamais on supprime un élément par erreur et qu’il est définitivement perdu lors de la sauvegarde ?
La solution la plus simple est de simuler une première sauvegarde avec l’option –dry-run (-n) afin d’obtenir la liste des fichiers qui vont être supprimés du dossier de destination puis de les déplacer avant de lancer la sauvegarde. Ainsi, on arrive à un script ressemblant à ça :
#!/bin/bash echo "Generating list of files to be rejected..." # Préfixe de dossier à retirer lors du nettoyage (utile pour les chemins absolus) # La chaîne doit être échappée pour passer avec 'sed' RMPREFIX="\/prefixe\/" # Le slash de fin est important pour la source, ainsi rsync va copier le contenu du dossier (au lieu du dossier lui-même) SOURCE="/dir1/" DEST="/dir2" REJECTFOLDER="/dirr" # On va chercher la liste des fichiers et dossiers allant être supprimés rsync -avn --delete-after $SOURCE $DEST | grep ^deleting | sed s/"^deleting "/""/ | while read line; do if [[ -d "$DEST/$line" ]]; then echo "Deleting $line..." rmdir "$DEST/$line" else echo "Rejecting $line..." FDIR=`dirname "$DEST/$line"` if [[ ! -z $RMPREFIX ]]; then FDIR=`echo $FDIR | sed s/"$RMPREFIX"/""/` fi mkdir -p "$REJECTFOLDER/$FDIR" mv "$DEST/$line" "$REJECTFOLDER/$FDIR/" fi done # Ici on peut lancer le vrai rsync
Exemple de résultat :

Enjoy it !
Iptables : Utiliser un VPN avec une IP failover
08/01/2011
Allez, une petite astuce (très facile mais je la donne quand même) pour bien commencer le week-end. Considérons un serveur avec une interface réseau et plusieurs IP failover (un serveur chez OVH par exemple), comment pouvons-nous utiliser l’une de ces IP failover pour la sortie d’un VPN ? Avec Iptables, la table nat, la chaîne POSTROUTING et la cible SNAT :
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to-source ipfailover
Pensez à remplacer ipfailover par l’adresse IP publique à utiliser et 10.8.0.0/24 par le réseau de votre VPN. Et bon week-end !