document.title = 'Jirafeau - 100%';
 }
 
-function show_upload_progression (p)
+function show_upload_progression (percentage, speed, time)
 {
-    document.getElementById('uploaded_percentage').innerHTML = p;
-    document.title = 'Jirafeau - ' + p;
+    document.getElementById('uploaded_percentage').innerHTML = percentage;
+    document.getElementById('uploaded_speed').innerHTML = speed;
+    document.getElementById('uploaded_time').innerHTML = time;
+    document.title = 'Jirafeau - ' + percentage;
+}
+
+function hide_upload_progression ()
+{
+    document.getElementById('uploaded_percentage').style.display = 'none';
+    document.getElementById('uploaded_speed').style.display = 'none';
+    document.getElementById('uploaded_time').style.display = 'none';
+    document.title = 'Jirafeau';
 }
 
 function upload_progress (e)
 {
     if (!e.lengthComputable)
         return;
-    /* Show the user the operation do not reach 100%, the server need time
-     * to give a response before providing the link.
-     */
+
+    // Init time estimation if needed
+    if (upload_time_estimation_total_size == 0)
+        upload_time_estimation_total_size = e.total;
+
+    // Compute percentage
     var p = Math.round (e.loaded * 100 / e.total);
-    if (p == 100)
-        show_upload_progression (' ');
-    else
-        show_upload_progression (p.toString() + '%');
+    var p_str = ' ';
+    if (p != 100)
+        p_str = p.toString() + '%';
+    // Update estimation speed
+    upload_time_estimation_add(e.loaded);
+    // Get speed string
+    var speed_str = upload_time_estimation_speed_string();
+    // Get remaining time string
+    var time_str = upload_time_estimation_time_string();
+    
+    show_upload_progression (p_str, speed_str, time_str);
 }
 
 function control_selected_file_size(max_size, error_str)
 
 function classic_upload (url, file, time, password, one_time, upload_password)
 {
+    // Delay time estimation init as we can't have file size
+    upload_time_estimation_init(0);
+
     var req = new XMLHttpRequest ();
     req.upload.addEventListener ("progress", upload_progress, false);
     req.addEventListener ("error", pop_failure, false);
     if (upload_password.length > 0)
         form.append ("upload_password", upload_password);
 
+    // Start time estimation
+    upload_time_estimation_init(async_global_file.size);
+
     req.send (form);
 }
 
 {
     if (!e.lengthComputable && async_global_file.size != 0)
         return;
+
+    // Compute percentage
     var p = Math.round ((e.loaded + async_global_transfered) * 100 / (async_global_file.size));
-    if (p == 100)
-        show_upload_progression (' ');
-    else
-        show_upload_progression (p.toString() + '%');
+    var p_str = ' ';
+    if (p != 100)
+        p_str = p.toString() + '%';
+    // Update estimation speed
+    upload_time_estimation_add(e.loaded + async_global_transfered);
+    // Get speed string
+    var speed_str = upload_time_estimation_speed_string();
+    // Get remaining time string
+    var time_str = upload_time_estimation_time_string();
+    
+    show_upload_progression (p_str, speed_str, time_str);
 }
 
 function async_upload_push (code)
 {
     if (async_global_transfered == async_global_file.size)
     {
+        hide_upload_progression ();
         async_upload_end (code);
         return;
     }
             );
     }
 }
+
+var upload_time_estimation_total_size = 42;
+var upload_time_estimation_transfered_size = 42;
+var upload_time_estimation_transfered_date = 42;
+var upload_time_estimation_moving_average_speed = 42;
+
+function upload_time_estimation_init(total_size)
+{
+    upload_time_estimation_total_size = total_size;
+    upload_time_estimation_transfered_size = 0;
+    upload_time_estimation_moving_average_speed = 0;
+    var d = new Date();
+    upload_time_estimation_transfered_date = d.getMilliseconds();
+}
+
+function upload_time_estimation_add(total_transfered_size)
+{
+    // Let's compute the current speed
+    var d = new Date();
+    var speed = upload_time_estimation_moving_average_speed;
+    if (d.getMilliseconds() - upload_time_estimation_transfered_date != 0)
+        speed = (total_transfered_size - upload_time_estimation_transfered_size)
+                / (d.getMilliseconds() - upload_time_estimation_transfered_date);
+    // Let's compute moving average speed on 30 values
+    var m = (upload_time_estimation_moving_average_speed * 29 + speed) / 30;
+    // Update global values
+    upload_time_estimation_transfered_size = total_transfered_size;
+    upload_time_estimation_transfered_date = d.getMilliseconds();
+    upload_time_estimation_moving_average_speed = m;
+}
+
+function upload_time_estimation_speed_string()
+{
+    // speed ms -> s
+    var s = upload_time_estimation_moving_average_speed * 1000;
+    var res = 0;
+    var scale = '';
+    if (s <= 1000)
+    {
+        res = s.toString();
+        scale = "o/s";
+    }
+    else if (s < 1000000)
+    {
+        res = Math.floor(s/1000) + ((Math.floor(s/100) - (Math.floor(s/1000) * 10)) / 10);
+        scale = "Ko/s";
+    }
+    else
+    {
+        res = Math.floor(s/1000000) + ((Math.floor(s/100000) - (Math.floor(s/1000000) * 10)) / 10);
+        scale = "Mo/s";
+    }
+    if (res == 0)
+        return '';
+    else
+        return res.toString() + ' ' + scale;
+}
+
+function milliseconds_to_date_string (milliseconds)
+{
+    function numberEnding (number) {
+        return (number > 1) ? 's' : '';
+    }
+
+    var temp = Math.floor(milliseconds / 1000);
+    var years = Math.floor(temp / 31536000);
+    if (years) {
+        return years + ' year' + numberEnding(years);
+    }
+    var days = Math.floor((temp %= 31536000) / 86400);
+    if (days) {
+        return days + ' day' + numberEnding(days);
+    }
+    var hours = Math.floor((temp %= 86400) / 3600);
+    if (hours) {
+        return hours + ' hour' + numberEnding(hours);
+    }
+    var minutes = Math.floor((temp %= 3600) / 60);
+    if (minutes) {
+        return minutes + ' minute' + numberEnding(minutes);
+    }
+    var seconds = temp % 60;
+    if (seconds) {
+        return seconds + ' second' + numberEnding(seconds);
+    }
+    return 'less than a second';
+}
+
+function upload_time_estimation_time_string()
+{
+    // Estimate remaining time
+    var t = (upload_time_estimation_total_size - upload_time_estimation_transfered_size)
+            / upload_time_estimation_moving_average_speed;
+    if (t == -1)
+        return ''
+    else
+        return milliseconds_to_date_string (t);
+}
+