How do I make these pages?
I have made software to do so. It would be near impossible to hand craft these pages. The steps are:
Make the selection
I use GwenView to judge the pictures and with F7 I copy each (group of) picture file(s) to the final target directory.
Extract EXIF
I use a script for doing this:
#!/usr/bin/bash exiv2 *.jpg | grep 'Exposure' > props exiv2 *.jpg | grep 'Image size' >> props exiv2 *.jpg | grep 'Image timestamp' >> props exiv2 *.jpg | grep 'Camera model' >> props exiv2 *.jpg | grep 'Focal length' >> props exiv2 *.jpg | grep 'ISO speed' >> props exiv2 *.jpg | grep 'Aperture ' >> props sort <props >props.sorted mv props.sorted props echo 'Done'The script creates a file named 'props' that contains the most important EXIF data for each jpeg file. Beware, that some SONY pictures have superfluous EOF tokens in it and the script chokes on that. A simple way to check on this is:
Resize the images
Resizing is easy. I just use the standard Linux command 'convert' which is part of Image Magick:
convert -resize 900x600 DSC_0001.jpg DSC_0001k.jpgThe smaller file gets the letter 'k' added to its base-name (the name without the file extension). This way I keep the original files as long as possible.
Add a watermark
When the small image is ready, I add a watermark to it, using the 'composite' command, in the lower left corner. Why not in the center? That would ruin the pictures. OK, now users can just download an image, tinker out the watermark and have a free print. So be it.
Automate the last two steps
It is a lot of work to resize and watermark all the images. So I made some software to ease things up. The program is called 'Fprep' and it is used as follows:
ls -1 *jpg | Fprep >runThe 'ls' command lists all jpeg files. These are piped to the Fprep program, that does some magic on the filenames and outputs two lines of text for each picture file:
convert -resize 900x600 DSC_1519.jpg DSC_1519k.jpg composite -gravity southwest -dissolve 80% ../bottomleft.png DSC_1519k.jpg DSC_1519kw.jpgThese lines are redirected to the file 'run' and later, this file is executed with the command
bash run
The Fprep program that does all this was written in OBC oberon. The source is open and here it is:
MODULE Fprep; IMPORT In, Out, Err, Strings; CONST TAB = 09X; TYPE PictureName = ARRAY 32 OF CHAR; VAR image : PictureName; images : INTEGER; PROCEDURE InsertLetter (src : PictureName; char : ARRAY OF CHAR; VAR dest : PictureName); VAR pos : INTEGER; BEGIN dest := src; pos := Strings.Pos ('.jpg', dest, 0); Strings.Insert (char, pos, dest) END InsertLetter; PROCEDURE Process (pict : PictureName); VAR pictk, pictkf : PictureName; BEGIN Out.String ("convert -resize 900x600 "); (* Produce 600 x 900 copy *) Out.String (pict); InsertLetter (pict, 'k', pictk); Out.Char (" "); Out.String (pictk); Out.Ln; Out.String ("composite -gravity southwest -dissolve 80% ../bottomleft.png "); Out.String (pictk); InsertLetter (pictk, 'w', pictkf); (* Add watermark in bottomleft *) Out.Char (' '); Out.String (pictkf); Out.Ln END Process; BEGIN images := 0; Err.String ("Syntax: ls -1 *jpg | Fprep >outfile"); Err.Ln; LOOP In.Line (image); IF In.Done = FALSE THEN EXIT END; INC (images); Process (image); Out.Ln END; Err.String ("Processed "); Err.Int (images, 4); Err.String (" images. Have a nice day."); Err.Ln END Fprep.Compile with
obc -o Fprep Fprep.mod
Create the HTML code
The index.html file needs to know which pictures to display and the EXIF data must be in the right place. This is done with the file 'Fgallery' which also is a compiled program written in OBC oberon. The program is used as in:
Fgallery -i props -o fileThe props file, created with exivtract, is processed and after fully processing, for each picture around 11 lines of HTML code are produced that formats the pictures on the webpage. You can just import this file in the right place in the index.html file and you're done.
The source of Fgallery:
MODULE Fgall; IMPORT Args, Out, Err, Files, Strings; CONST AsciiLF = 0AX; AsciiCR = 0DX; AsciiHT = 09X; open = 0; close = 1; maxPict = 250; maxProps = 9; maxPropLength = 24; TYPE PictureName = ARRAY 32 OF CHAR; PropValue = ARRAY maxPropLength OF CHAR; Properties = ARRAY maxProps OF PropValue; VAR inF, outF : Files.File; InputFile, EOF, DebugMode : BOOLEAN; Fcode : ARRAY 40 OF CHAR; picture, pickw : PictureName; Pnames : ARRAY maxPict OF PictureName; Props : ARRAY maxPict OF Properties; PicturesFound, index : INTEGER; (* Pdates : ARRAY maxPict, 3 OF INTEGER; *) PROCEDURE Abort (nr : INTEGER); (* Abort program in a controlled way, with an *) (* error message and shutdown procedure *) BEGIN CASE nr OF 1 : Err.String ("Please supply arguments and options") | 2 : Err.String ("No input-file specified") | 3 : Err.String ("File cannot be opened") | 4 : Err.String ("Cannot create output file") | 5 : Err.String ("Cannot append to file") END; Err.String (", aborting."); Err.Ln; Err.String ("Syntax : Fgallery -i props-file -o output-file -a append-file -d (debug)"); Err.Ln; ShutDown; HALT (nr) END Abort; PROCEDURE StringCopy (src : ARRAY OF CHAR; VAR dest : ARRAY OF CHAR); VAR i, k, l : INTEGER; ch : CHAR; BEGIN k := Strings.Length (src); l := LEN (dest) - 1; i := 0; LOOP ch := src [i]; IF i < l THEN dest [i] := ch ELSE EXIT END; INC (i); IF i = k THEN EXIT END END; dest [i] := 0X END StringCopy; PROCEDURE Read (VAR c : CHAR); (* Read one token from file *) BEGIN IF Files.Eof (inF) THEN EOF := TRUE ELSE EOF := FALSE END; Files.ReadChar (inF, c) END Read; PROCEDURE UnRead (n : INTEGER); (* Move 'n' characters back *) BEGIN Files.Seek (inF, -n, Files.SeekCur) END UnRead; PROCEDURE SkipWs; (* Skip over WhiteSpace *) VAR ch : CHAR; BEGIN REPEAT Read (ch) UNTIL (ch > ' ') OR EOF; UnRead (1) END SkipWs; PROCEDURE SkipTo (token : CHAR); (* Skip all characters until 'token' is found *) VAR ch : CHAR; BEGIN REPEAT Read (ch) UNTIL (ch = token) OR EOF END SkipTo; PROCEDURE ReadString (VAR dest : ARRAY OF CHAR; delim : CHAR); (* Read a string from file, UNTIL the delimiter token *) VAR i, j : INTEGER; ch : CHAR; BEGIN i := 0; j := LEN (dest) - 1; SkipWs; Read (ch); REPEAT dest [i] := ch; INC (i); Read (ch) UNTIL (ch = delim) OR (i = j); dest [i] := 0X END ReadString; PROCEDURE InsertLetters (src : PictureName; chars : ARRAY OF CHAR; VAR dest : PictureName); VAR pos : INTEGER; BEGIN dest := src; pos := Strings.Pos ('.jpg', dest, 0); Strings.Insert (chars, pos, dest) END InsertLetters; PROCEDURE BackStrip (VAR str : ARRAY OF CHAR); (* Remove spaces from end of string *) VAR i : INTEGER; BEGIN i := Strings.Length (str) - 1; WHILE str [i] = ' ' DO DEC (i) END; INC (i); str [i] := 0X END BackStrip; PROCEDURE Store (name : PictureName) : INTEGER; (* Store or Find picturename index *) VAR i : INTEGER; BEGIN i := 0; LOOP IF Pnames [i] = '' THEN Pnames [i] := name; INC (PicturesFound); EXIT END; IF name = Pnames [i] THEN EXIT END; INC (i) END; RETURN i END Store; PROCEDURE DivHeader (mode : INTEGER; pict : ARRAY OF CHAR); (* Output HTML div header and footer code *) BEGIN IF mode = open THEN Files.WriteString (outF, '<img class="left" src="'); Files.WriteString (outF, pict); Files.WriteString (outF, '"> <p> <pre>') ELSE Files.WriteString (outF, '</pre> <script> ShowKofi () </script></p> <br clear="all"> <hr>'); Files.WriteLn (outF) END; Files.WriteLn (outF) END DivHeader; PROCEDURE Process (n : INTEGER); (* Take care of storing the EXIF data in an array *) VAR i : INTEGER; option : PropValue; BEGIN ReadString (option, ':'); BackStrip (option); IF option = 'Aperture' THEN i := 0; ReadString (option, AsciiLF) ELSIF option = 'Camera model' THEN i := 1; ReadString (option, AsciiLF) ELSIF option = 'Exposure bias' THEN i := 2; ReadString (option, AsciiLF) ELSIF option = 'Exposure mode' THEN i := 3; ReadString (option, AsciiLF) ELSIF option = 'Exposure time' THEN i := 4; ReadString (option, AsciiLF) ELSIF option = 'Focal length' THEN i := 5; ReadString (option, '('); SkipTo (AsciiLF) ELSIF option = 'ISO speed' THEN i := 6; ReadString (option, AsciiLF) ELSIF option = 'Image size' THEN i := 7; ReadString (option, AsciiLF) ELSIF option = 'Image timestamp' THEN i := 8; ReadString (option, ' '); SkipTo (AsciiLF) ELSE i:= -1 END; Props [n, i] := option END Process; PROCEDURE ShowProps; (* Dump the contents of the EXIF arrays to screen *) VAR i, j : INTEGER; BEGIN FOR i := 0 TO PicturesFound - 1 DO Out.String (Pnames [i]); Out.Ln; FOR j := 0 TO maxProps - 1 DO Out.String (Props [i, j]); Out.Char (AsciiHT) END; Out.Ln END; Out.Ln END ShowProps; PROCEDURE Init; VAR i, j : INTEGER; option : ARRAY 32 OF CHAR; BEGIN i := 1; DebugMode := FALSE; InputFile := FALSE; IF Args.argc # 5 THEN Abort (1) END; WHILE i < Args.argc DO Args.GetArg (i, option); IF option = '-i' THEN INC (i); Args.GetArg (i, option); inF := Files.Open (option, "r"); IF inF = NIL THEN Abort (3) END; InputFile := TRUE ELSIF option = '-o' THEN INC (i); Args.GetArg (i, option); outF := Files.Open (option, "w"); IF outF = NIL THEN Abort (4) END ELSIF option = '-a' THEN INC (i); Args.GetArg (i, option); outF := Files.Open (option, "a"); IF outF = NIL THEN Abort (5) END ELSIF option = '-d' THEN DebugMode := TRUE END; INC (i) END; IF InputFile = FALSE THEN Abort (2) END; PicturesFound := 0; FOR i := 0 TO maxPict - 1 DO Pnames [i] := ''; FOR j := 0 TO maxProps - 1 DO Props [i, j] := '' END END END Init; PROCEDURE ShutDown; BEGIN IF inF # NIL THEN Files.Close (inF) END; IF outF # NIL THEN Files.Close (outF) END; Out.Ln; Out.String ("Files closed, shutting down."); Out.Ln END ShutDown; BEGIN Init; LOOP ReadString (picture, ' '); IF EOF THEN EXIT END; index := Store (picture); Process (index) END; IF DebugMode THEN ShowProps END; FOR index := 0 TO PicturesFound - 1 DO picture := Pnames [index]; InsertLetters (picture, 'kw', pickw); DivHeader (open, pickw); Files.WriteString (outF, Props [index] [1]); (* Camera *) Files.WriteLn (outF); Files.WriteString (outF, Props [index] [3]); (* Exposure mode *) Files.WriteLn (outF); Files.WriteString (outF, Props [index] [5]); (* Focal length *) Files.WriteLn (outF); Files.WriteString (outF, Props [index] [4]); (* Shutter speed *) Files.WriteString (outF, " at "); Files.WriteString (outF, Props [index] [0]); (* Aperture *) IF Props [index][2][0] # '0' THEN Files.WriteString (outF, " and "); Files.WriteString (outF, Props [index][2]) (* Exp compensation *) END; Files.WriteLn (outF); Files.WriteString (outF, Props [index] [6]); (* ISO *) Files.WriteString (outF, " ISO"); Files.WriteLn (outF); Files.WriteString (outF, Props [index] [7]); (* Resolution *) Files.WriteLn (outF); Files.WriteString (outF, Props [index] [8]); (* Date *) Files.WriteLn (outF); Files.WriteLn (outF); StringCopy (Props [index] [8], Fcode); Fcode [4] := '/'; Fcode [7] := '/'; Fcode [8] := 0X; Strings.Append (Pnames [index], Fcode); Files.WriteString (outF, "<h2>"); Files.WriteString (outF, Fcode); Files.WriteString (outF, "</h2>"); DivHeader (close, Pnames [index]) (* picturename is not used here but the parameter must match the TYPE of the function *) END; ShutDown END Fgall.Oberon is a programmig language ANYONE can read and understand without being fluent in it..
Check the number of lines in the target file and compare it with the number of pictures:
jan@fluor:~/internet/knipser/galleries/tocht10$ wc file 1705 4495 35733 file jan@fluor:~/internet/knipser/galleries/tocht10$ ls *jpg | wc 155 155 2015 jan@fluor:~/internet/knipser/galleries/tocht10$ calc 155 11 * = 1705 DoneYes, calc is an obc program too.
MODULE calc; IMPORT In, Out; CONST maxSP = 64; VAR option : ARRAY 32 OF CHAR; digits : ARRAY 20 OF CHAR; stack : ARRAY maxSP + 2 OF LONGINT; operation : CHAR; radix, sp : INTEGER; (* number base, stackpointer *) Done : BOOLEAN; PROCEDURE Init; BEGIN sp := 0; Done := FALSE; radix := 10; digits := '0123456789ABCDEF' END Init; PROCEDURE GetOption () : CHAR; VAR i : INTEGER; ch : CHAR; sign : BOOLEAN; BEGIN i := 0; REPEAT In.Char (ch) UNTIL ch > ' '; IF ch = '-' THEN sign := TRUE; In.Char (ch) ELSE sign := FALSE END; IF ch = ' ' THEN RETURN ('-') ELSIF (ch < '0') OR (ch > '9') THEN RETURN (ch) END; IF sign THEN option [i] := '-'; INC (i) END; REPEAT option [i] := ch; INC (i); In.Char (ch) UNTIL ch <= ' '; option [i] := 0X; RETURN ('#') END GetOption; PROCEDURE Push (number : LONGINT); BEGIN INC (sp); IF sp > maxSP THEN Out.String ("Stack overflow; Aborting."); Out.Ln; HALT (3) ELSE stack [sp] := number END END Push; PROCEDURE Pop (VAR number : LONGINT); BEGIN IF sp = 0 THEN Out.String ("Stack underflow; Aborting."); Out.Ln; HALT (4) ELSE number := stack [sp]; DEC (sp) END END Pop; PROCEDURE Swap; VAR num1, num2 : LONGINT; BEGIN Pop (num1); Pop (num2); Push (num1); Push (num2) END Swap; PROCEDURE Add; VAR num1, num2 : LONGINT; BEGIN Pop (num2); Pop (num1); Push (num1 + num2) END Add; PROCEDURE Sub; VAR num1, num2 : LONGINT; BEGIN Pop (num2); Pop (num1); Push (num1 - num2) END Sub; PROCEDURE Mul; VAR num1, num2 : LONGINT; BEGIN Pop (num2); Pop (num1); Push (num1 * num2) END Mul; PROCEDURE Div; VAR num1, num2 : LONGINT; BEGIN Pop (num2); Pop (num1); Push (num1 DIV num2) END Div; PROCEDURE Power; VAR num1, num2, res : LONGINT; BEGIN Pop (num2); Pop (num1); res := 1; WHILE num2 > 0 DO res := res * num1; DEC (num2) END; Push (res) END Power; PROCEDURE Radix; VAR num1 : LONGINT; BEGIN Pop (num1); radix := SHORT (num1 MOD 17) END Radix; PROCEDURE Find (token : CHAR; text : ARRAY OF CHAR) : INTEGER; VAR i : INTEGER; BEGIN i := 0; WHILE text [i] # 0X DO IF token = text [i] THEN RETURN (i) END; INC (i) END; RETURN (-1) END Find; PROCEDURE Number; VAR num, sign : LONGINT; pos, i, j : INTEGER; ch : CHAR; BEGIN i := 0; j := 0; num := 0; IF option [0] = '-' THEN sign := -1; i := 1 ELSE sign := 1 END; REPEAT ch := CAP (option [i]); pos := Find (ch, digits); IF (pos < 0) OR (pos >= radix) THEN Out.String ("Illegal token in number : "); Out.String (option); Out.String (" using radix"); Out.Int (radix, 3); Out.Ln; HALT (5) END; num := num * radix + pos; INC (i) UNTIL option [i] = 0X; num := num * sign; Push (num) END Number; PROCEDURE Print; VAR num : LONGINT; res : ARRAY 66 OF CHAR; i, p : INTEGER; sign : BOOLEAN; BEGIN sign := FALSE; Pop (num); i := 0; IF num = -9223372036854775808 THEN Out.String ("-9223372036854775808") ELSIF num = 0 THEN Out.Char ('0') ELSE IF num < 0 THEN sign := TRUE; num := ABS (num) END; WHILE num # 0 DO p := SHORT (num MOD radix); num := num DIV radix; res [i] := digits [p]; INC (i) END; IF sign THEN Out.Char ('-') END; DEC (i); WHILE i >= 0 DO Out.Char (res [i]); DEC (i) END END; Out.Ln; Done := TRUE END Print; BEGIN Init; REPEAT operation := GetOption (); CASE operation OF '#' : Number | 'R', 'r' : Radix | '+' : Add | 'x' : Swap | '-' : Sub | '*' : Mul | '/' : Div | '^' : Power | '=' : Print ELSE Out.String ("Illegal parameter : "); Out.String (option); Out.Ln; HALT (1) END UNTIL Done; Out.String ("Done"); Out.Ln END calc.It mimics a small RPN calculator without floating point math
Cleaning up
At this point you are ready to clean up the no longer required files:
Make a caption image
After cleaning up, go through the resized and watermarked files and select one image. Resize it to 300x200 pixels and use KolourPaint to add some text in the lower right corner. Save the file as 'cap.jpg'.
Create the index.html file
Spoiler: I use a template file, stored in the system directory:
cp ../sy<TAB>/i<TAB> .If you come from Windows: do not even try to understand this line.
The template index.html file is as follows:
<!doctype html> <html lang="en"> <head> <script src="../../libs/knipser.js"> </script> <meta charset = "utf-8"> <title> </title> <link type="text/css" rel="stylesheet" href="../../knipser.css"> </head> <body> <h1 class="center"> </h1> <h2>Foto's of prints bestellen? Noteer het fotonummer en stuur een mail naar foto@knipser.nl</h2> <p> De foto's zoals ze hier staan zijn vrij te downloaden en gebruiken MITS het watermerk intact gelaten wordt. </p> <script> ShowKofi1 () </script> <p> </p> <div> </div> <script> ShowKofi1 () </script> </body> </html>Some text needs to be entered explaining what this collection is about. After that, the file output by Fgallery is inserted in the div section in the bottom of the file.
Make an entry in the main page
In the required section, add a line
Publish ('gallery')and save the index.html file
'Publish' is a piece of javascript code:
function Publish (name) { document.write ('<a href="galleries/' + name + '" target="_top"> '); document.write ('<img src="galleries/' + name + '/cap.jpg"></a>'); }that inserts one line of HTML to publish the cap.jpg file.
Upload to the webhost
The dialog is simple:
ncftp open knipser put index.html (overwrite) cd galleries put -R galleries/gallery exitThings may be different when using Windows.
Page created 28 Apr 2022,