Manchmal muss den Upload von Dateien testen. Nicht immer nutzt man etwas wie S3 sondern oft auch noch ganz klassische Umgebungen mit SCP oder sFTP. Wenn man schnell und unkompliziert den Upload auf einen sFTP-Server testen will, kann sich so einen ganz schnell und einfach mit Docker erzeugen.
docker run -p 2222:22 -d atmoz/sftp foo:pass:::upload
man kann sich dann auf localhost:2222 mit dem Benutzer foo und dem Passwort pass verbinden. Für Dateien steht das Upload-Verzeichnis bereit. Wenn man ihn nicht mehr braucht, wirft man den Container einfach weg und kann das nächste mal sauber mit einem neuen starten.
Da ich momentan das Blog-Modul noch mal überarbeite, wollte ich gerne meine momentane Upload-Methode für Bilder beibehalten und trotzdem den TinyMCE integrieren. Hier ist eine gute Anleitung, wie man ein img-Tag von außen in den Content des TinyMCE einbringen kann.
We have out data seperated in chunks now. Now we have to write a script to send this data to the PHP-script or servlet to save it in a file. This is the elementary part of the the javascript-function and also the simple part. Because we append every chunk to the final file, we can’t work with asynchron requests. If you want to, you have to send also the number of the chunk, and also the final filesize to create a dummy-file and replace the part of the sent chunk. For the javascript the changes are no this big, but the server-side script become more complex.
So.. we will use the synchrone style and work with a simple for-loop to run through our array with the data chunks.
var result=null;
for(var i=0;i<chunks.length;i++){
var last=false;
if(i==(chunks.length-1)){
last=true;
}
result=uploadFileChunk(chunks,filenamePrefix+file.name,url,params,last);
}
return result;
Only the result of the request of the last chunk is returned. Because it is the chunk where the magic (copy from temp-folder to real target folder and create and save entity to database, etc) happens.
I send add a prefix to the filename to create a filename like userid+prefix+filename+fileextension (like *.part or somethin like this). So a user can upload more than one file with the same filename, without cause problems on server side.
But you should also send the original filename as something like "clearname". In this example you can add it to die params-array (params["clearname"]=file.name;) and use it as filename in the db-entity to set this name in header-data on download of this file or in lists of the uploaded files.
function uploadFileChunk(chunk,filename,url,params,lastChunk) {
var formData = new FormData();
formData.append('upfile', chunk, filename);
formData.append("filename",filename);
for(key in params){
formData.append(key,params[key]);
}
Now we have a file-object, we need to split it in small chunks to upload this chunks one by one. JavaScript have the ability to work with files. It can’t access the filesystem directly, but the open- and save-dialogs will do every thing we need. To save data for more than one session you can use the indexeddb. But here we need only open,save and drag and drop.
function createChunksOfFile(file,chunkSize){
var chunks=new Array();
var filesize=file.size;
var counter=0;
while(filesize>counter){
var chunk=file.slice(counter,(counter+chunkSize));
counter=counter+chunkSize;
chunks[chunks.length]=chunk;
}
return chunks;
}
The method is very simple. The loop runs as long as the copied size is smaller as the size of the file. With slice(from,to) you have to set the start- and the endpoint, that is read (in bytes). If the endpoint behind the real end of the file no execption is thrown and only the existing part will be copied. That makes it very easy to us. With every run of the loop we add 250000 to the copied-size till our copied-size is greater than the file-size. In ervery run of the loop we copy/slice the bytes from copied-size to copied-size + 250000 and add this slice-chunk to the output array.
Finally we get an array with alle the sliced chunks of the file in correct order.
Yes.. you can calculate the needed count of chunks and than use a for-loop and calculate the start and end for every part. Works great too… but i did it the other way.
var chunks=createChunksOfFile(file,250000);
So we have all chunks each ~250KB. 1MB should have ~4 Parts. Yes.. it’s 1024Bytes per 1KB.. but we keep it simple :-)
Vor dem Upload ist es oft wünschenwerts ein Bild drehen/rotieren zu können.
Ich hatte mich damals doch sehr damit rumgeärgert, um den richtigen Mittelpunkt bei Rotationen heraus zu bekommen.
Hier ist meine Lösung für Rotationen um 90,180,270 Grad (select oder durch Auf- und Abrunden kann man das sicher stellen,
dass keine anderen Werte eingegeben werden).
Nun haben wir unsere Daten in entsprechend aufbereiteter Form. Was noch fehlt ist, dass wir die Daten an unser Script senden, das dann alles Speichert. Das ist an sich der elementare Teil hier und wie wohl erwartet an sich auch der einfachste Teil. Da wir einfach mit append immer hinten an die Datei ran schreiben, bleiben wir synchron. Sollte man asynchron werden wollen, müßte man die Nummer des aktuellen Teils und die gesamt Zahl der Teile mitsenden. Dann och die Größe der Teile und man könnte eine Dummy-Datei erzeugen mit der gesamten Größe und dann die Teile immer an den entsprechenden Bereichen einfügen. Sollte an sich auch nicht so kompliziert sein und am JavaScript-Code würde sich kaum was ändern, ausser den paar mehr Infos im Request.
Aber erstmal alles Synchron, weil wir dann auch einfach mit einer for-Schleife durch unser Array durch laufen können.
var result;
for(var i=0;i<chunks.length;i++){
var last=false;
if(i==(chunks.length-1)){
last=true;
}
result=uploadFileChunk(chunks,filenamePrefix+file.name,url,params,last);
}
return result;
Es wird nur das Result des letzten Teil-Uploads zurück geliefert. Weil dort dann meistens auch die Datei nochmal umkopiert wird und entsprechende Datenbankeinträge vorgenommen werden. Es ist gut die Datei erstmal in einem separaten Verzeichnis als *.part oder so zu speichern und erst wenn der letzte Teil (last Variable) angekommen und gespeichert die DB-Einträge und an einen entsprechenden Ort zu kopieren. Über den Ordner mit den *.part Dateien kann dann ein Cron-Job laufen der alle dort vorhanden *.part Dateien entfernt die länger als 20min nicht geändert wurden.
Ich übergebe noch einen Prefix für den Filename, dann kann am Server userid+filenname+prefix+parterweiterung als Dateiname verwendet werden. Damit ist es auch für einen User möglich Dateien mit selben Dateinamen hochzuladen ohne dass es am Server zu Verwechselungen kommt.
Idealer Weise sollte noch mal der eigentliche Dateiname zusätzlich noch mal mit übergeben werden. Ist hier im Beispiel nicht so hat direkt ersichtlich, weil der Name mit in den "params" steht wo auch noch alle möglichen anderen Daten für das Request mit drin stehen können.
function uploadFileChunk(chunk,filename,url,params,lastChunk) {
var formData = new FormData();
formData.append('upfile', chunk, filename);
formData.append("filename",filename);
for(key in params){
formData.append(key,params[key]);
}
Hier der eigentliche Uplaod-Code. Ansich entspricht es einer Form nur eben rein in JavaScript. Die Daten der Form werden dann per AJAX-Request an das Script geschickt.
Wenn man nicht das tollste Smartphone mit dem schnellsten Internet hat, hat man öfters das Problem, dass man ein Foto macht und es dann gerne irgendwo hin hochladen möchte. Dann dauert es ewig, bricht manchmal dann mittendrin ab und am Ende hat es viel Volumen verbraucht. Nur damit die doch einiger Massen gute Kamera des Smartphones ein belangloses Foto gemacht hat, dass die volle Auflösung nicht gebraucht hätte und am Ende auf Serverseite sowie so noch mal kaputt komprimiert wird. Es wird aber selten die Auflösung noch angepasst als eher die JPEG-Qualität runter gedreht (auf etwas mal man klassisch so bei 70% oder weniger schätzen würde).
Hoch aufgelöste Bilder wo alle Details durch die Kompression kaputt gemacht wurden. Wenn es nicht in der vollen Auflösung angezeigt wird merkt man es weniger (Super-Sampling.. auch gut um Rauschen in Bildern zu entfernen). Man könnte natürlich auch das Foto schon auf dem Client/Smartphone schon so weit verkleinern, dass es nicht unnötig Volumen und Bandbreite verbraucht. Aber auf welche Auflösung sollte man das Bild runter rechnen?
Wenn der Benutzer super Qualität mit vielen Details und guten Farben am Ende erwartet.. also im Grunde genau das was er hochgeladen hat und mit etwas Glück aus einer modernen DSLR stammt.. ja.. dann ist 6MP schon Minimum. Mit minimalen Vergrößern sollte es sogar auf 4K noch hinnehmbar aussehen. Sonst muss es leider schon 10MP sein.
Aber im normal Fall wird man eine Website/Anwendung haben, die eine Reihe kleinerer Bilder anzeigt und dann bei einem Klick darauf das Bild vergrößert anzeigt. Entweder auf Vollbild, wobei oft Ränder noch da sind, sowie Bereich für Titel, Beschreibung und Datum. In vielen Fällen auch noch Platz für Kommentare.
Kaum jemand hat eine Auflösung über 1920x1200 oder 1920x1080. Also die Breite auf max 1920 oder die Höhe auf max 1080 zu skalieren wird für normale Anwendungen, die nur zum Betrachten da sind und nicht um die Bilder nochmal runter zu laden und zu bearbeiten, vollkommen reichen.
Seiten die viele Bilder halten im Zusammenhang mit Texten (Foren und Imageboards) brauchen noch sehr viel geringere Auflösungen. Selbst Seiten wie 9gag haben bis auf wenige Ausnahmen Bilder in hohen Auflösungen. Wir reden hier nur von Bildern und nicht von GIFs.. das wäre nochmal ein Thema für sich.
Außer bei diesen sehr langgezogenen Bildern wäre eine Breite über 1000 Pixeln kaum nötig. Also 1MP reicht für die meisten Zwecke. Die Vorschaubilder sind noch bedeutend geringer aufgelöst. Und auch bei Facebook reichen Bilder mit 1000px in der Breite eigentlich immer aus, wenn man nicht detaillierte Landschaften zeigen möchte.
1000x1000 Pixel sind schon mal eine ganz andere Größe. Wir gelangen da von 2MB auf gut einige 100KB. Das macht auch beim Upload extreme Unterschiede.
Ideal wäre eine Check-Box, die man setzen kann, wenn es ein Bild mit vielen Details ist. Das Problem wären normale Benutzer, die Abends in der Cocktailbar ihr Essen im Kerzenschein fotografieren müssen und keine ruhige Hand haben und nun glauben, es wäre ein hoch detailreiches fotografisches Kunst entstanden. Wenn man genug solche Benutzer hat, würde die Checkbox immer angeklickt werden, weil kein Wissen darüber besteht, wann was von Vorteil ist und dann lieber die "bessre" Qualität gewählt wird.
Am Ende bleibt die Frage, wie kann ich Bilder vor dem Upload verkleinern? Früher wurde das doch immer auf dem Server erledigt.
Mit dem Canvas-Element auf HTML5 geht es extrem einfach. Man braucht nur das Bild am img-Element vorliegen.
Beispiel (wie man an $scope sieht ist es AngularJS-Code). $scope.longestSide gibt die max Breite hier an.
var canvas=document.createElement("canvas");
var factor=1;
if($scope.newPostPreviewImage.width>$scope.longestSide){
factor=($scope.newPostPreviewImage.width/$scope.longestSide);
}
canvas.width=$scope.newPostPreviewImage.width/factor;
canvas.height=$scope.newPostPreviewImage.height/factor;
var ctx=canvas.getContext("2d");
ctx.drawImage($scope.newPostPreviewImage,0,0,canvas.width,canvas.height);
blob=dataURItoBlob(canvas.toDataURL("image/jpeg",0.7));
Wie man hier sieht ist eines der großen Geheimnise, wie man die DataURL vom Canvas wieder in
ein Binär-Object zurück wandelt, so dass es wieder wie ein normal File-Upload gehandhabt werden kann.
Den Code haben ich nach langer Suche im Internet gefunden und er funktioniert!
function dataURItoBlob(dataURI) {
// convert base64 to raw binary data held in a string
var byteString = atob(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to an ArrayBuffer
var arrayBuffer = new ArrayBuffer(byteString.length);
var _ia = new Uint8Array(arrayBuffer);
for (var i = 0; i < byteString.length; i++) {
_ia = byteString.charCodeAt(i);
}
var dataView = new DataView(arrayBuffer);
var blob = new Blob([dataView], { type: mimeString });
return blob;
}
Damit haben wir dann alles um es hochladen zu können. Wie man etwas hochlädt erkläre ich später vielleicht nochmal, aber da gibt es sonst genug Erklärungen auf anderen Seiten, die einfach und gut verständlich sind.
Aber hier kommt nochmal auf die schnelle der Code um ein Image aus einem Input des Types "file" heraus zu bekommen. Die Methode wird über das onchange-Event des Input aufgerufen.
Hier wird die Datei in eine Object-URL umgewandelt und einmal ein img-Element erzeugt und diese Object-URL als src gesetzt. Zusätzlich wird nochmal eine DataURL von der Bild-Datei erzeugt, um ein kleines Vorschau-Bild anzeigen zu können. Die DataURL wird, wenn sie fertig
geladen ist, bei einem bestimmten img-Element als src gesetzt. Da wird über CSS skaliert. Alle Verkleinerungsoperationen werden aber auf dem internen separat gehaltenen img-Element ausgeführt.
<img ng-src=""/>
Blog-entries by search-pattern/Tags:
Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?