Category: Dev

  • Flux/LoRA Prompts for business photos

    In the previous post I explained how to train Flux/LoRA to create images of yourself. This is a quite straightforward process and after that we can create via prompts the images we want to. In my case I did my first try outs with some business portraits for myself. The results a good, sometimes a bit to blurry and smoothened out. But there is an issue that the Flux AI tends to add you at least a 2nd time into the picture if you place yourself into a typical scenery with more then just 1 person. I also found a simple solution to overcome this. Here are my example prompts and the outcomes:

    Professional business portrait of erich wearing a dark grey suite, sitting confidently at a modern office desk. Background shows a contemporary office with glass windows and city views. Well-lit with soft, natural light, highlighting a friendly, approachable smile, wearing a formal suit.


    Professional business portrait of erich, standing in a modern office meeting room with a table and screen in the background. Wearing business attire with a confident, relaxed posture, arms crossed and smiling warmly. Soft, professional lighting enhances a welcoming expression.


    Professional portrait of erich, seated behind a modern executive desk, surrounded by minimalistic office decor like a laptop, notebook, and pen holder. Well-lit room with large windows and subtle artwork in the background. Dressed in a formal suit or business casual, with a focused, thoughtful expression.


    Professional yet relaxed business portrait of erich, standing in a collaborative office space with colleagues visible in the background, blurred slightly. Wearing business casual attire, arms relaxed, with a warm, approachable expression. Modern office setting with plants and glass walls, lit with natural light.

    Here we have the case, that I have been put a 2nd time into the picture.


    Professional business portrait of erich, caught in a natural conversation with a colleague in a modern office lounge area. Wearing business attire, seated on a stylish office sofa with hands gesturing slightly as if explaining something. Background features office decor with plants, well-lit with ambient lighting.


    How to overcome the multiple images of yourself in scenes

    This is actually quite simple, you just need to tell the AI via prompt that only one person is yourself and the others should be given random faces:

    erich is sitting in a high class restaurant and having dinner with a lovely woman wearing an elegant black dress. erich is smiling into the camera. he is wearing smart casual clothings. in the background we see the typical scenery of a restaurant with tables, people etc. only the person sitting on the table with the woman is erich. add random faces to all the others

    And after the prompt piece added:

  • Howto: train Flux-LoRA for custom images of yourself

    The first AI apps on the mobile I remember were some fun apps creating from an given image of you, different scenario pictures like a funny background. The makers charged quite some money for like 5 custom images. However with Flux LoRA there is a even better outcome for less money possible.

    Flux is the image generator model by German AI company Black-Forest-Labs and is at the moment the hottest and best image generator for photo realistic images. The image generator itself can be easily for free on their website.

    To achieve custom pictures of us we need to go a step further and train the model. This is not complicated and cost only 2$. I use the AI workspace fal.ai for this where you can work with many different model – and also Flux and Flux SoRA.

    Step 1: train the model

    Go to: https://fal.ai/models/fal-ai/flux-lora-fast-training

    You will see the form pictured above. Add face shots, selfies, close up pictures of you into the uploader. I used about 25 pictures of me. Then select a “Trigger Word” – to reference the training data in your future prompt. I simply use my name “Erich” as the trigger word. Then let the training start – it will last around 1 minute to finish.

    Step 2: Run the model with your trigger word

    To create an image prompt based on the trained data with the Flux SoRA model simply klick on the Run button of the training form:

    Or you can also directly chose the model prompt interface: https://fal.ai/models/fal-ai/flux-lora/playground

    Enter your prompt referencing the “Trigger word” you have set before and let the magic happen. I generated some examples using very simple 1 line prompts:

    The business picture is really great and it looks very much like me when in use in small scale. When getting closer you can see blurry areas somehow. The 2nd picture was to put me to Oktoberfest with interesting result being put twice into the picture 🙂 The black t-shirt and the yellow backbag is actually taken from one of my training pictures. Overall I am quite happy with the outcomes and with some more detailed prompts, you definitely will get even better results. Total costs ~2$.

    Advanced prompt examples

    Here are some more examples I generated today with a more advanced promoting:

    erich is captured mid-speech.  His expressive face, adorned with a salt-and-pepper beard and mustache, is animated as he gestures with his left hand. He is holding a black microphone in his right hand, speaking passionately. The man is wearing a dark, textured shirt with unique, slightly shimmering patterns, and a green lanyard with multiple badges and logos hanging around his neck. The lanyard features the "Autodesk" and "V-Ray" logos prominently. Behind him, there is a blurred background with a white banner containing logos and text, indicating a professional or conference setting. The overall scene is vibrant and dynamic, capturing the energy of a live presentation. 

  • My AI Image generator tryouts

    This is a summary of my progress with AI image generators over the past months. From first try outs with creating new movie posters to very advanced prompting showing really cool results.

    Alternative movie posters

    Those are my first try-outs with AI image generators and also already over 1 year old. I used Dall-E for the generating (except the 2 alien pic – those came from Flux. Dall-E still has huge problems putting text 1:1 into the image, as you can clearly see. Flux is comparable great at this task.

    Comic strips

    Pixel Art

    Advanced prompting results

    This are the my best images and most advanced prompts I have been using in the past. Most images are generated with Dall-E 3 and Flux1.0. The more realistic looking image have been generated with flux, which as at the moment for me the reference when it comes to image generating. If you need not the super realistic look, Dall-E will also do. Plus: those 2 you can still be used for free.

    Video

  • RSS Feed mit PHP bauen

    Disclaimer: This is a new version of my RSS Feed Turorial from 2008, as it still gets a decent amount of traffic from Google. Hope it still helps 15 years later.

    Einen RSS Feed kann man kinderleicht für seine dynamische Website, zum Beispiel ein News Script, erstellen. Dafür ist nur die Kenntnis vom Aufbau einer RSS Datei plus eine einfache Datenbankabfrage notwendig. Hier wird gezeigt wie man einen solchen dynamische generierten RSS Feed manuell zusammenstellt.

    Einleitung

    Als Vorwissen für dieses Tutorial benötigt man Kenntnisse in php und mysql. Ziel ist es ein kleines php Script zu schreiben, das auf Basis von Daten aus einer mysql o.ä. Datenbank dynamisch einen RSS Feed generiert.

    Zuerst muss man sich entscheiden auf welchen RSS Standard man zurück greifen will. Es gibt diverse Standards, die man auch gängig bei anderen Seiten finden kann – zB. RSS0.91, RSS1.0 und RSS2.0 (mehr dazu hier). Wir werden in diesem Tutorial einen RSS2.0 Feed erstellen.

    Dokument Definition

    Als erstes erstellen wir in einem Editor eine leere Datei und fügen folgenden Header Code in die ersten Zeilen ein:

    <?xml version="1.0" encoding="ISO-8859-1" ?> 
    <rss version="2.0"> 
    <channel> 

    Hier wird erstmal definiert um was für ein Dokument es sich hier eigentlich handelt bzw. die XML DTD dazu bestimmt. Die Tags “<rss>” und ” <channel>” sind die Basis Element eines RSS Feeds. Da wir einen Dynamischen RSS Feed haben wollen und die erstellende Datei eine php Script beinhaltet, muss die erste Zeile, die XML Definition, mit einem echo ausgegeben werden, da sonst der php Interpretor auf das “?” mit einem Parse Error reagiert.

    <?xml version="1.0" encoding="ISO-8859-1" ?>" 

    Weiters muss hier dem Browser der content type mitgeteilt werden, da dieser sonst den Inhalt als Html und icht als xml ausgibt. Die ganze veränderte Zeile sieht dann wie folgt aus:

    <?php header("Content-type: text/xml"); 
    echo "<?xml version="1.0" encoding="ISO-8859-1" ?>"; ?>

    Meta Daten

    Nachdem wir nun die Stammelemente plus die xml Definition erstellt haben machen wir uns an die Meta Daten des Feeds. Die sehen bei RSS2.0 folgendermaßen aussehen und sind meistens statisch. Die Elemente hier sind selbsterklärend. Beim Tag “lastBuildDate” könnte man zum Beispiel mit <?php $now = time(); echo $now; ?> zur Script Laufzeit das aktuelle Datum einsetzen.

    <title>Website name</title>
    <link>http://www.beispiel.de</link>
    <description>Beschreibung von Website</description>
    <language>de-de</language>
    <pubDate>Datum der Erstellung</pubDate>
    <lastBuildDate>Datum des letzten Outputs, in vielen Fällen die Ausführzeit des Scriptes</lastBuildDate>
    <docs>http://www.beispiel.to/rss.php</docs>
    <generator>Rss Feed Engine</generator>
    <managingEditor>info@beispiel.de</managingEditor>
    <webMaster>info@beispiel.de</webMaster>

    Elemente

    Nun da die Meta Elemente ausgefüllt sind ist der nächste Schritt die einzelnen News Elemente des z.B. News Scipts in RSS konforme Daten zu konvertieren. Die Struktur eines Items sieht dabei so aus. Ein Feed kann beliebig viele Items beinhalten aber es macht Sinn diese auf ca. 10-20 Elemente zu beschränken.

    <item>
    <title>Beispiel Titel</title>
    <link>http://www.beispiel.de/link</link>
    <description>Beispiel Beschreibung, Text, Bilder etc. Die Description kann auch einen Excerpt (Auszug) des vollen Beitragtextes enthalten.</description>
    <pubDate>Datum der Veröffentlichung des Beitrags</pubDate>
    <guid>http://www.beispiel.de/link</guid>
    </item>

    Dieses Template eines Items füllen wir nun mittels einer Datenbank Abfrage o.ä. mit Daten. Der dazu passende Code könnte z.B. so aussehen:

    <?php require('mysql_connect.php'); 
    $SqlSelect = "SELECT link, pic, titel, text FROM news LIMIT 0,20"; 
    $result = mysql_query($SqlSelect); 
    if (!$result) { die('Invalid query: ' . mysql_error()); } 
    
    while ($row = mysql_fetch_assoc($result)) { ?> 
    <item> 
    <title><?php echo $row['titel']; ?></title> 
    <link> <?php echo $row['link']; ?> </link> 
    <pubDate> Wenn vorhanden Timestamp des Beitrages, ansonst einfach weg lassen </pubDate> 
    <description> <?php echo "<img src=\"".$row['pic']."\"></img></a>"; ?> <?php echo $row['text']; ?> </description> 
    </item> <?php } 
    mysql_free_result($result); 
    mysql_close($dbh); 
    ?>

    Zum Schluss schließen wir noch die beiden Stammelemente channel und rss am Ende des Dokumentes.

    </channel>
    </rss>

    Um jetzt den erstellten Dynamischen RSS Feed auf der Hauptseite einzufügen und ihn für die Browser erkennbar zu machen, muss man foolgende Zeile in den Kopf der Hauptseite schreiben:

    <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.beispiel.de/rss.php" />

    Hier die fertigen Datei:

    <?php header("Content-type: text/xml");
    echo "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>"; ?>
    <rss version="2.0">
    <channel> 
    	<title>Website name</title>
    	<link>http://www.beispiel.de</link>
    	<description>Beschreibung von Website</description>
    	<language>de-de</language>
    	<pubDate>Datum der Erstellung</pubDate>
    	<lastBuildDate>Datum des letzten Outputs, in vielen Fällen die Ausführzeit des Scriptes</lastBuildDate>
    	<docs>http://www.beispiel.to/rss.php</docs>
    	<generator>Rss Feed Engine</generator>
    	<managingEditor>info@beispiel.de</managingEditor>
    	<webMaster>info@beispiel.de</webMaster>
    
    <?php
    require('mysql_connect.php');
    
    $SqlSelect = "SELECT link, pic, titel, text FROM news LIMIT 0,20";
    	$result = mysql_query($SqlSelect);
    
    	if (!$result)	{	    die('Invalid query: ' . mysql_error());	}
    	
    	while ($row = mysql_fetch_assoc($result))	{
    	
    ?>
    	<item>
    	<title><?php echo $row['titel']; ?></title>	
    	<link>
    	<?php echo $row['link']; ?>
    	</link>	
    	<pubDate>
    	Wenn vorhanden Timestamp des Beitrages, ansonst einfach weg lassen
    	</pubDate>
    	<description>
    	<?php echo "<img src=\"".$row['pic']."\"></img></a>"; ?>
    	<?php  echo $row['text']; ?>
    	</description>
    	</item>
    
    <?php
    	}
    mysql_free_result($result);
    mysql_close($dbh);
    ?>
    
    </channel>
    </rss>
  • How to create an AI driven content machine for your website

    Simple AI tools and the connection of them makes it very easy to create large amounts of content (here in the example text) for your blog, website etc. All tools I used here are free and easy to handle.

    Step 1: ask chatgpt to create a list of popular items of any category you are interest in. In my example I asked it for the most popular DOS Games with some data of additional information:

    Chatgpt will return you a handy table that you can easily further process. The data is nice, but we need some more text and therefore we are going to query chatgpt for each of the game for a summary. You can do this also in the normal prompt screen, but this would take very long. I am using for this a Google Sheet with the GTP for work plugin, that allows you to use the chatgpt als normal Google Sheet functions.

    Step 2: Add some more content in a Google Sheet. When you copy/paste the table it will look something like this:

    I already added another column Summary where then we want chatgpt to generate our text for each game. To do that we use a simple function:

    After some time the request will be filled with a text comparably to mine:

    You can add the details or specifics you want to have in your text. Quick hint on the prompt: chatgpt tends to just end longer texts in the middle of a sentence, so to trick it here tell in the prompt you want a 1.500 word summary and the only the first 750 words should be displayed. With this little tweak you will avoid having cut off texts.

    As we are in a Google Sheet, the only thing we have to do now is to extend the function to all other rows below and let chatgpt produce the 100 texts. Having our complete texts we can go the next step and get the data into our wordpress Blog. Doing so we use zapier.

    Step 3: Import the data als new content to your wordpress blog. Create a new transfer with your source “Google Sheets” and destination “WordPress Blog”.

    In the next step choose the Google Sheet File from the dropdown:

    Your WordPress also needs some preparation and you need to install the zapier plugin there, so that the app can communicate properly with the blog system:

    Next we need to connect our WordPress blog and enter the credentials:

    And then we can put everything together and tell zapier which data to use from the Sheet to create our new blog post (you can also create pages or attachments with images):

    You can also use HTML in some fields like the Content to create more advanced entries. For the game example here we could create a little table at the top showing all the additional data we have. In the next step you can then review the full dataset and choose which one zapier should transfer.

    Final result: the post in WordPress:

    If you have chosen the full 100 dataset you will have 100 new posts in your blog. Some constraints from the zapier/Wordpress connection: As you can also see in the screenshot the plugin uses the old classic editor and not the new block editor. Also there are no links in the posts, which are to add in another step.

    To fully automate the flow, create a new Zap from the transfer in zapier and let the process run each time a new row has been added to the Sheet. So all you have to do then, is add the row with the name of a new game and the chatgpt function will create your text and the zapier zap will import it automatically to your blog.

  • WordPress: Blog unter gleichen Domains

    WordPress greift für seine URL-Generierung auf 2 Konstanten zurück die über die Optionen eingestellt werden können. Man kann aber auch die URL zur Laufzeit in der wp_config.php ändern. Warum will man das? – Der Usecase bei mir ist, dass 2 Domains auf das selbe Blog zeigen, aber es keine Weiterleitung geben soll. Sprich ich will 2 Seiten mit eigenen URLs und identischen Content.

    Umsetzung: Man untersucht den Host der Anfrage und setzt je nach dem die 2 Konstanten neu. Ist die Bedingung nicht erfüllt macht man gar nichts und es greifen die Einstellungen aus den Optionen.

    if ($_SERVER['HTTP_HOST'] == "mobilepulse.de") {
    define('WP_HOME','http://mobilepulse.de');
    define('WP_SITEURL','http://mobilepulse.de');
    } else if ($_SERVER['HTTP_HOST'] == "mobilepulse.at") {
    define('WP_HOME','http://mobilepulse.at');
    define('WP_SITEURL','http://mobilepulse.at');
    }

    Surft man jetzt die Seite an bleibt die generierte URL gleich der ursprünglich angefragten Domain und wechselt nicht auf die Hauptdomain (mobilepulse.at -> mobilepulse.at).

  • Wordpres 2.7 Pingback Probleme

    WordPress 2.7 hat durch einen schweren Softwarefehler Probleme andere Blogs anzupingen. Aus irgendeinem Grund wurde der Timeout für einen Pingback auf 0.01 Sekunden anstelle 1 abgesenkt. Das führte praktisch dazu, dass keine Trackbacks und Pingbacks mehr funktionierten. Es gibt aber eine einfache Lösung für das Problem:

    wp-includes/cron.php Zeile 200:

    wp_remote_post($cron_url, array('timeout' => 0.01, 'blocking' => false));

    ändern zu:


    wp_remote_post($cron_url, array('timeout' => 1, 'blocking' => false));

    Im Bugfix Release 2.71 wird der Fehler dann auch gefixt sein.

  • phpbb3 mysql4 zu mysql5 Update

    In den letzten Tagen habe ich mir Zeit genommen einige meiner Domains/Webseiten auf einen neuen Ubuntu Server zu transferieren. Sie waren auf einem opensuse. Dank der tollen Migrations-Funktion von Plesk kann man 1:1 seine gesamte Konfig von einem Server zum anderen übertragen. Probleme gabs damit soweit nicht. Beim check der Webseiten kam aber bei einer phpbb3 Installation böses Erwachen: Durch den Umstieg von mysql4 auf mysql5 gabs hier einige Probleme. Dazu einige Hilfestellungen aus den Support Foren:

    Default Values Queries
    Spaltenlänge bbcode_uid

    Man beachte: beide Datenbanken SIND utf8!!. Zum einen hat der DB Layer von phpbb3 Probleme mit Querys, da MySQL5 keine Default Values mehr hat und alle Querys alá “insert into xx value (”, 2, “uuu”)” aussahen. Dafür gibts aber ein offizielles Script. Drüber laufen lassen -> Beiträge können erstellt werden. Oder doch nicht..? Fehlermeldung “Data too long for column ‘bbcode_uid’ at row 1” -> Lösung: Einfach die Spaltenlänge von bbcode_uid auf 8 erhöhen. Dann noch die Probleme mit der fehlerhaften Encodierung (komischerweise ist nur die Tabelle phpbb_posts betroffen) mit folgenden SQl Script fixen:

    UPDATE phpbb_posts SET post_text = REPLACE(post_text, 'ß', 'ß') WHERE post_text LIKE '%ß%';
    UPDATE phpbb_posts SET post_text = REPLACE(post_text, '´', '') WHERE post_text LIKE '%´%';
    UPDATE phpbb_posts SET post_text = REPLACE(post_text, 'ä', 'ä') WHERE post_text LIKE '%ä%';
    UPDATE phpbb_posts SET post_text = REPLACE(post_text, 'ö', 'ö') WHERE post_text LIKE '%ö%';
    UPDATE phpbb_posts SET post_text = REPLACE(post_text, 'ü', 'ü') WHERE post_text LIKE '%ü%';
    UPDATE phpbb_posts SET post_text = REPLACE(post_text, 'Ä', 'Ä') WHERE post_text LIKE '%Ä%';
    UPDATE phpbb_posts SET post_subject = REPLACE(post_subject, 'ß', 'ß') WHERE post_subject LIKE '%ß%';
    UPDATE phpbb_posts SET post_subject = REPLACE(post_subject, '´', '') WHERE post_subject LIKE '%´%';
    UPDATE phpbb_posts SET post_subject = REPLACE(post_subject, 'ä', 'ä') WHERE post_subject LIKE '%ä%';
    UPDATE phpbb_posts SET post_subject = REPLACE(post_subject, 'ö', 'ö') WHERE post_subject LIKE '%ö%';
    UPDATE phpbb_posts SET post_subject = REPLACE(post_subject, 'ü', 'ü') WHERE post_subject LIKE '%ü%';
    UPDATE phpbb_posts SET post_subject = REPLACE(post_subject, 'Ä', 'Ä') WHERE post_subject LIKE '%Ä%';
    UPDATE phpbb_users SET user_sig = REPLACE(user_sig, 'ß', 'ß') WHERE user_sig LIKE '%ß%';
    UPDATE phpbb_users SET user_sig = REPLACE(user_sig, '´', '') WHERE user_sig LIKE '%´%';
    UPDATE phpbb_users SET user_sig = REPLACE(user_sig, 'ä', 'ä') WHERE user_sig LIKE '%ä%';
    UPDATE phpbb_users SET user_sig = REPLACE(user_sig, 'ö', 'ö') WHERE user_sig LIKE '%ö%';
    UPDATE phpbb_users SET user_sig = REPLACE(user_sig, 'ü', 'ü') WHERE user_sig LIKE '%ü%';
    UPDATE phpbb_users SET user_sig = REPLACE(user_sig, 'Ä', 'Ä') WHERE user_sig LIKE '%Ä%';
    UPDATE phpbb_topics SET topic_title = REPLACE(topic_title, 'ß', 'ß') WHERE topic_title LIKE '%ß%';
    UPDATE phpbb_topics SET topic_title = REPLACE(topic_title, '´', '') WHERE topic_title LIKE '%´%';
    UPDATE phpbb_topics SET topic_title = REPLACE(topic_title, 'ä', 'ä') WHERE topic_title LIKE '%ä%';
    UPDATE phpbb_topics SET topic_title = REPLACE(topic_title, 'ö', 'ö') WHERE topic_title LIKE '%ö%';
    UPDATE phpbb_topics SET topic_title = REPLACE(topic_title, 'ü', 'ü') WHERE topic_title LIKE '%ü%';
    UPDATE phpbb_topics SET topic_title = REPLACE(topic_title, 'Ä', 'Ä') WHERE topic_title LIKE '%Ä%';

    also noch mal im Detail die Reihenfolge (angenommen man hat einen kompletten Dump in der neuen DB eingespielt und realisiert dass einiges nicht funktioniert):

    1. mysql4 -> mysql5 Upgrade Script Ausführen und SQL am Server exekutieren
    2. Die Länge der Spalte “bbcode_uid” in der Tabelle phpbb_posts auf 8 erhöhen
    3. komplette Spalte phpbb_posts trunkieren
    4. Dump nur für diese Tabelle wieder einspielen
    5. Encoder Fix SQL ausführen
    6. Fertig
  • Wechsel zu Netbeans 6.5

    Brandneu ist das Final Release der neuen major Netbeans Version. Netbeans 6.5 bringt viele Neuerungen – vor allem für nicht Java Entwickler, welche die IDE auch für andere Sprachen nutzen wollen. Ich bin ja ein Anwender, der neben Java, Java Mobile Projekten vorrangig Dinge in php – neuerdings auch groovy – codet. Für meinen Anwendungsbereich bietet mir als das neue Netbeans alles was ich brauche in einer IDE. Hat den riesigen Vorteil, dass ich mich nicht permanent, wenn ich beispielsweise ein php Projekt mache neue Hotkeys merken muss und ständig falsche Funktionen in den unterschiedlichen IDEs suche.

    War bis dato so, dass ich für meine php Dinge das Zend Studio für Eclipse verwendet hab. Das war auch ganz nett. Was mir aber Netbeans 6.5 in Bezug auf php bieten kann hat mich überzeugt. Einzig das profilling mach ich noch im Zend Studio. Die Grails Unterstützung ist die beste, die man momentan für eine IDE bekommen kann.

    Also war der Wechsel beschlossene Sache. Verlief eigentlich alles reibungslos. Alle alten Projekte wurden perfekt übernommen. Das alte Problem bestand noch immer, dass meine Web Projekte die Server Zuordnung verloren haben (Wechsel von Glassfish 2 -> 3). War aber kein Problem das im Nachhinein zu konfigurieren. Nun hab ich endlich eine IDE für fast alles – danke Netbeans.

  • Google Ajax Lib API

    Google hat ja gestern ihre AJAX Libary API vorgestellt. Im Prinzip ist diese API ja nichts anderes als ein Ersatz für die Speicherung der Libs am eigenen Server. Die API bietet dabei die bekanntesten Vertreter an:

    • jQuery
    • prototype
    • script.aculo.us
    • MooTools
    • dojo

    Sie erlaubt den Zugriff auf die jeweiligen JS File einerseits über ein URL:


    als auch über den Google AJAX Loader:
    google.load("jquery", "1");

    Den Vorteil den die AJAX Lib API mit sich bringt ist mit der oben angeführten Syntax, dass automatisch immer die letzte Version der Libary eingebunden wird. Ein manuelles Updaten der Files am eigenen Server entfällt dadurch völlig. Natürlich ist auch das einbinden einer bestimmten Version und ein komprimiertes File möglich:

    google.load("jquery", "1.2", {uncompressed:true});

    Für mehr Infos zur Funktionsweise der sehr simplen API gibts eine kleine Doku.