Installation

Install via CLI

npx @vector-labs/skills add generative-media

Or target a specific tool

npx @vector-labs/skills add generative-media --tool cursor
View on GitHub

Skill Files (12)

SKILL.md 5.0 KB
---
name: generative-media
description: >-
  Gera e edita imagens usando modelos de IA via OpenRouter (FLUX.2 Pro,
  FLUX.2 Flex, Nano Banana 2). Suporta geracao texto-para-imagem, edicao,
  variacoes em lote e pos-processamento. Use para criar ilustracoes, arte e
  graficos para redes sociais.
license: Apache-2.0
compatibility: Requires Python, uv, OPENROUTER_API_KEY
allowed-tools: Bash Read Write Glob
metadata:
  author: vector-labs
  version: "1.0"
tags: [images, ai, media]
complexity: intermediate
---

# Generative Media

Generate and edit images using AI models via OpenRouter. Supports multiple models with shared prompting knowledge and dedicated per-model scripts.

## Setup

All scripts use `uv run` with inline deps — no install needed.

```bash
export OPENROUTER_API_KEY="your-key-here"
```

All script paths below are relative to this skill's directory. Run as:
```bash
uv run --with requests python3 scripts/SCRIPT.py COMMAND [args]
```

## Model Selection

| Need | Model | Why |
|------|-------|-----|
| Edit existing image | Nano Banana | Only model with editing |
| Text in image | Nano Banana | Best text rendering |
| 2K/4K resolution | Nano Banana | Only model with size tiers |
| Artistic/illustration | FLUX.2 Pro | Strongest creative styles |
| Fast cheap exploration | FLUX.2 Flex | $0.02/img, 2-5s |
| Default / general | Nano Banana | Most versatile |

Full comparison: [references/models/index.md](references/models/index.md)

## Workflow

### 1. Understand the Request

Clarify with the user:
- **What**: Subject, scene, or concept
- **Style**: Photo, illustration, painting, flat vector, etc.
- **Where it goes**: Social media, hero banner, print, icon (determines aspect ratio + resolution)
- **Edit or new**: Generating from scratch or modifying an existing image?
- **Reference**: Any images or styles they like?

### 2. Choose Model

Use the model selection table above. When unsure, default to Nano Banana.

### 3. Craft the Prompt

Build the prompt using this structure:
```
[Subject] + [Style] + [Composition] + [Lighting/Color] + [Constraints]
```

Key differences by model:
- **Nano Banana 2**: Natural language, conversational prompts. "Create a cozy scene with..."
- **FLUX**: Keyword-driven, comma-separated descriptors. "cozy cafe, warm lighting, 35mm film..."

Full prompting guide: [references/prompting.md](references/prompting.md)

**Quick prompt examples:**

Product shot:
> Minimalist ceramic vase with dried eucalyptus, product photography, soft studio lighting, white background, centered, no text

Social media graphic:
> Abstract geometric pattern with navy (#1a2332) and coral (#ff6b6b) shapes, modern minimalist style, 1:1 square composition, clean edges

Hero banner:
> Aerial view of a winding mountain road through autumn forest, golden hour lighting, cinematic wide shot, warm color palette, 16:9

### 4. Generate

**Single image:**
```bash
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/nano_banana.py generate \
  --prompt "YOUR PROMPT" --output-dir ./output --aspect-ratio 16:9 --size 2K --prefix hero
```

**Batch variations (for exploration):**
```bash
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/nano_banana.py batch \
  --prompt "YOUR PROMPT" --output-dir ./output --count 6 --prefix explore
```

**Edit existing image:**
```bash
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/nano_banana.py edit \
  --prompt "EDIT INSTRUCTIONS" --input source.png --output-dir ./output --prefix edited
```

**FLUX generation:**
```bash
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/flux.py batch \
  --prompt "YOUR PROMPT" --output-dir ./output --model pro --count 4
```

### 5. Review & Iterate

Build a preview gallery to compare results:
```bash
uv run --with Pillow python3 scripts/preview_gallery.py \
  --input-dir ./output --title "Project Name"
```

Open `preview.html` in browser. Ask user which to keep, iterate, or discard.

**Iteration tips:**
- Keep what works, describe what to change
- Add constraints to remove unwanted elements: "no text, no watermarks"
- Try a different model if current one struggles with the style
- Use `--prefix v2` for iteration rounds

### 6. Post-Processing (if needed)

For post-processing (background removal, format conversion, SVG vectorization): [references/techniques/upscaling.md](references/techniques/upscaling.md)

## Script Reference

| Script | Purpose | Key Commands |
|--------|---------|-------------|
| `nano_banana.py` | Gemini 3.1 Flash generation + editing | `generate`, `edit`, `batch` |
| `flux.py` | FLUX.2 Pro/Flex generation | `generate`, `batch` |
| `preview_gallery.py` | HTML comparison gallery | `--input-dir`, `--title` |
| `utils.py` | Shared utilities | (imported by other scripts) |

## Model-Specific Guides

- [Nano Banana details](references/models/nano-banana.md) — Prompting tips, editing, resolution tiers
- [FLUX details](references/models/flux.md) — Pro vs Flex, keyword-driven prompting
- [Editing techniques](references/techniques/editing.md) — Background removal, color changes, style transfer
generative-media.skill 22.3 KB
PK��M\'generative-media/generative-media.skillPK&�M\vB�	�generative-media/SKILL.md�X�r۸�ϧ�cw[卝d��i;���z۪�4�I3DA"V$�@���G�O�'�)ю����E������4M-JyLs��^�ʴ�S%��t�U�WF��۸-I�)aߓ*�\:���s:9��Le�H鬨��v)���B�?����Ҋ�hh�~�޼����!?����M!��V	����6��v@��VZG^��ԛ4ܻQ��~�$(�k�4>��VX�]��2Χ�5�t��>8I�\j�Ғp\cZ�lk�ݧ�ʰ`���E�J��U*��~��s���mU���b#��Z#h"467'�ɔ((��VT��𲁎z����^j�@y0���s8옞}cȳ>=kMi�絨���1��|���xt�ք���^���/���DU��cv�z.��^�(M�̓�7�
�
�.�ܤ������b*+g��/l-�[���N�"��]z�Il�����\~��������ʺ�*d��R��
+�h֔g"-�Yr:�MqS�[�i8H���z��H��J��b��iI���l�����%�U����JLi�i)qd���py"�X[��]^_}�9��r2<�����?�LmӅ\����݋�>w4��Y"���,�Q!���9�*+38}5��Z���7�G�)M��V�\K���ύ>jM<�^�oՊN�..N.��O�����r<5�.B�%ɚ.a)���5}�W�N�i���i�b��8��N�����{账+]�b0��0a�t����8�Vl��z*m{�����wXs��Cu��+��
�*�:>b=k�tkǶȉ���F�Y����BFoo�RT0�*�Cm�������y���G��3�����oԿ�3���I�.��
��i|
*5 � ʁ��������{�!�Q�3�?��s�5{��RⒿ�	�$��[,?NR��>���zǨ��OȞ>��xf������F��z0#���e�"�ɜ�@|�
�K`!��F�p[������
�5n�����+"Z��
�W��:Y����\�-�!�f.����\	_���~��)��n�΂N�L�c܌���P�?D΍�zKn������D�� ��z��Dg���q�dУ�D�(��� s�3I^ת�q�p�@s�o�й�$���~��>�P�_�8f�b���{5����м�9�I7�`:�f&M�
���ѝ�w)�]=�#K0����Uw�9m{nf��b�|�OA4W$˄hn�tjQ˚%��H�DM��ђ2cYl����^
["j��>�,K���7���6�y���~}n6gw�����V٢
��%��;���d���������bB–�ܷ�E�	�Yg�XU�v\�x Q��P���<�阷��9�bnM��p�T�/Ĉ�8IF��
V�d�Q�EsiJ�-�B��i5��vE{�������~�ݙ�@���f�&�&���+��
Ɂc���PD}z~����5��l��P��\��.I~�BC����*�$3C�,���&z�)Y#�(`�<'Q���43@`��S�\���qTx)���Ly�L�+�/$L�¯���Š��A�:�G�m
N6��}Ud�L"��o�����&�v~��pM�뫋����êڧ`48�X��F�dC���� J��]��X���������+ZǓ��̌Ӏ^mM�
�֪GxίhF����8�����/G7�Noί.Gl�Ҭ�CZfrPA���k��\9mmb\��z�O{fE}�?�#�?���EKZw�%���[:�M5Ů&xr	[s�viW�#ob���)���Cpp�4�4٬hP��CŽ���5�M���xءq#m���3�X�t<��E��p�^HY��D��*�	�sH�-�@U�I�hq��yP)�\��i�9:�ī'S��M��=+K�0�����>J@?�ݝ�CX
�V����b��}�\Ԍ�چ�e��93�
�`���xƛ�=3�6F�q���W���S��8����4��[�m;Y4P}���r��=Fy��O����{M��,׊������~��;�t����!�<l5kkֶb�f�B��L`�v���^�_�6��c��~�9��f��q��}�2�
%;�ʸ)���� �6�u|[;,뇛���afS�kN�MU����i`�kd��^�9<�!J�ɱ
ji�c���}��q7��:�2UCj���;av��1���6~F\�W���q����:�i=��������d�ua����uK8Y�Y�m�=���r�+^?R��D��=�P	0�$&�PK��M\�;��(generative-media/references/prompting.mdmY]o�F}ׯ(���Jr��m���c'�'
l���(�9"�"9\�P�yا�����%{�����!!ř;���s'/��ʮL�u�>5����2����W��?]�ua�W�)]ʖ:3*3�it��R�KM�g�����$val항ԗ����Jmc����l4z�;Q]U:�r�s���u��k�.T�7Pp�>� ��ƨʘԤ�@�
�)�����S�I��-̓
*r���K���˗�/��7������!l
#O��㼥5�~g���8�v�k�%�Ӷ
�W5::z�y�������J%pH	��X��6S��VkTPc^�	�K�$�:w�e���
���Sb�`�l�Qkr�s���*55��B-�)҉Z�T�+(?wm��M�*:��=����N�Y��
;*��<yXkW�f�%'3չG��S��h�ڨ>�35�R������������	�e,T��5��A���>�'�pn�_tP�.��x4���x��'$�rr6<�xco������o��7�D��̰�����s��%�I�)|}��<�*�b�d��m��&�
�5�)�g1�UR��WI?X]�3-�k��Y�Y�*\��i}���-mՖ��8a��`+> �\4��=+`�Gܛ�ЉQ+���޵���
|e<r
��(VP;��F,�1lAoF/T��@S�1�X+����d�XB{��2�$��H�A�!(|����5^�Hc�-A�!��] 4m��0Sw,F��T�t�Ú�������)��y�Þ��08}�gԘ1{K)�5,=�F���!�1\)Ђ�o�y�����ڢd�Y��'W��Y���EYB�mEy�8va*&b�ChS�v������d��h��K�H��Z�##�3@$�ʈ���}���'�`�����B�c	M����@�$��#�����K���:�2J!*l��(=@!,f�; ��]Yo�߬��g���$y����(ej��
�����Uu������	���z"-���2$F�>0q�
���B@c�M�o��ϖ`c���~[-��Et|Մ�T{��Q+i2��,ي�&�Ĝ�r�%�#��J�Y�O����+���6��
T�y|�ڕ��(�溅�ڥݸ)�ܱ�3&6��<���$y��b��%��Ek��LP��n��ޛ�㳛=K񊌃WqZ��F�]��
�|��C�T�e�\��ir3Q�Y�5����yf�i��~5�Q�K�Y�&��
HP�^*�ix���>�d@n<���H��<b�8�i=�J���N'�
K=�p���
u����o�٠��	6@�b(,�CeC����5�ڭM�h����
��qDZ��&��+������t��d+15nZ���Dv�&�)S8��'�w'|e�ZV�5hS�zQF�Bۚ.��c�����'ⴭ����χ"*5A#��p���q��w�t!���M�,��rt\�Q^���!��"sGh7��!N�8f����@ �)*#� S�:��r�	�d=c�ݲ#�T+�g�t�I��R⾥�4+�o�ܔ1M�"z�ybt�s	^e��7�&8�j��A��	�T���u�:׬���X�n��!Ɏɴt_ù�� ]둮�_���D
��X�� 7�Зʡ�#�(��OY�:����'���ؘ��F�!�D.x\ڴ{�D��s4��	��(��tt�[���ѽ)�
XPZ&�AR�#
n����	�v�ܘ�xݒ�,�'�	���I(�2U
�	�%rv��Z�r��R�)Ϥ�K��Ĕ\�
"d��~�h���a!%��5y����-���=24�����9p2M;e���@%Ѷ|Uo<��0�a�X��Ew�I۸=<���fE���<��-�5����thݩ�����"�;���x�?�ߠ��BҖ-ɀA_�#� �]Y�Z�q�KW�$��%	� R�wo~�� ��ˑ�GV�/��ww�8ѧgg�����"�uX&�N�
�&���Mt򦶚2��u%)��n�nZ$�h��vd�*r�@�d,����}8A�Ep���#E<�ƿF&�HhH�#�`�F�a��z�f	���$�7��ʑj�@d�� C����E�vr�6�<*�7�u?���1d����qpB��s��<$�����`�u�H�^U�ZI��K^�k�Vx|�/��D��M c^E�^!.<O(cw�@X��� 3����ĺ�Fn��.�e�����b#��9t#N��KD�#G^e"�Kϥ����֦(��� D�{��$%=:����2��^�ܙ*�(bS��s�8y9={��~^�k2я3�+e�E�ѕ��7s����۝�u�1��1U]1�W=I7voD�e�L��9Aăo_NO.�R>���B�_�,��(l"@Lk�V�rHm�q[�/�w�ĝ����t�b୍��������'�cZ�nC�@�o��ht2��D��q:�]\٥!9+F��Fs���۔�d�cQn0t	�Og��wT#u�޳����B�E��AHꌯ�	���\���	�Ӵ��Lv�OA=������|&�w�if�C'���Ew���:Œ)lLMN7m�r/(�:'��."z��S�\�uG|f�E�P�b�Ѡ��3���1> f@[�Đ�y4��?��ҟ����i�'>���\w]r0�a""��i׍=��g;<�r">�Vn��	"�Ë`�v~�>Yě�w��,�$Y��i��
p9��
n���*ރ]R`6P7?|����uB7#��|�>B�>����o�>�
|F��NϤ����L�&��H��*�����L��!�ܙ�_C���ݕK���@	�L0˯�k�<��^U��ܿ����jH��Q����¦�&�q���nٱ0c�
�Sb�)��Z����7o`�_���z��'�2����^�0��jj��DF
d@3�"*p���>&K�BC�	}��x[h#�;uԟ5���6�b8������#���^�Gn+d[)R$'�'Xw����4�<�ɍ/�4�+)�-�_����AIy���h�����-P�Ė,'$��g���T��?�<g��|�i�]UnJ��4�������9�>z5��«��J�|�2��dk4����+�x�%"��{�4Q�v����ȴ�����u��Q�Sُ��\������WO�[�n��Ul��4�#�^2���\�l��+v���������vnbS�P�^#��uU�ݨ�e���h"�8$����Y�k����;FP3����Z������:�?PKM\�c�Am+generative-media/scripts/preview_gallery.py}SMo�0��Wp�Eg�n|�ЮЭE���APb:`˞D�
�����v��.6I���z�n�;��k�Bs��B��|b��/��KP����Bg������^��mG4ha	�QGt9c?��Y3�'a��r�̻,��t=-Km�j��_�eۓ�������`��a��y;�aL7]k	�=v�:mwq,���j����M�|4�\��ć8�"����Q��߰h(�PO�v#�>� �N�Ԍ�+h�6":
�,���=��Ǽ�Q�;XݑnM1U���Ԗg3�\��T��3��,��Ų��pº+�����3:��<Km��&�v�4_�7��������Ҥ"�  ���&n���<��],NzV��L\7��X�fؽ��@=�����Н�X�GJ2,ZWB�`>y�����|7�G�ȍ���t+~���m��,rH��ޔkx����	+��H|(%ueX��H%��l��ᬍ�<B�?��zYŔ�H�Q��%���jBI�H"���{uw��B-<�RC��qV �Q�NP���CJ�L���PK��M\́����'generative-media/scripts/nano_banana.py�Yms�6��_�c>��J�e;�+�t3N��:'�Gv����p(	��R$����{w_$;M���͹�R���b�>�S�T�?Y�gw�x��<;�|��3.�Y._��&Yq��D�.�,g/��a�[��`'�R�l�D��2/Wk6)x6�K�e�y�E��g�b��{Ú�8����[9�^�����̿�N�_^�0�R�B���)��-�L�zV�����"[��(2�3����~xu=�\\�0��,6~�v��+#g����]"EB�P��~����</3͞7厲;!�l�3m<=�]L'�G���r����zl��S
�!��Ħȥf�\�T܍g��/N��G�g�zP��-AWV$z����0�<-�vV�d�����9��4=�2����"Ӂ?�N'ӈ}�迡��3���z��I�z8�x;�{6C�CB@���A��Y%�Z'���Z��*�p��s�ˇ��{��Pd�Ku��2@3�8^���q($���1.1���+���O��&w<6�	�]����\�a<{q
S�*%�M�cP�[�I�@G�ø~��cC毵.T���w��]"� �7��׉��U)'�������[��*���r���#=������q�n�:��A*\��
�.���D��s��S3y=��s3�]4x�����;�9��e|5����=�E���9xނ/�<IS�_��
��X
9�e֧�F������<��?�z����� �s�)�
�@F�f���E��q]�L1�ܳ�_M.x���	Y�y��R��-���Y	���g�~Ė�Kh�lk-��ݚ�U�i@R����@�'E��9��c*ZڝI��!͓E[�&_��R����`���v%�Ђ�o|FB<�����<ϖb��`Zm�V��n�����~��]�Ft2�<,r������~�T���/]�R����G��[(� �r+��Rn
�kH�
LV���$Iy`J�^�6e�`DT
�VW_
�Y��\��%aF#s����%��S�|��������e��J�k깁Lw����� +��	h�0^��;�\��*�dY��8�f�����?Ϧ㋷�ȍ�Ld�^H�m8>��%��7I�x3��8���`��݁%i�ڂf�����$�^�C�$�;�9U��F��mOH<!5�a��1�DF"�]BW���P�x2�3XųMhlȕ��f>��_�	U<�C�*�p�i�l�@ڣ�H�~��|4�!D�˥���=�A�
�6$2�}?|�{��S��v��u&ݴ�=(��u�$F)S��b4����.�E��h����b\��Zg��]�ˮ��zP�D�w��ͻ�z��m��jݠN�q�C��|���Y@W�l�X[��F/�u�f��M��f�7��.����lr����-�C0��=��{�`�����fK3;
�]��.��	ɏZ�i\h�N�B�����.;P�N0��0�if������+�����\L�Y����h�c�/;�2��}$�o���F�Pi�#��#��+a�졋�����*���x� 8���_.�S���Z��C�н�����7���]�⏊2
����ۮT>�1vn�z�$��l���Ʒl�����[���AӀ���O���C��f+v��On�����拡���2-�z�
����w��U�������%����!��I�h2� �q��}�nxfL}���ח�Hm���w��<�.��^cK�$6V�������nw��0r�?Kq�h��dm �����J9/(���^���[��6�,�I�R�� �Dd�G�G<��A/<���/i%Xp5����O��vl�da�����D�)a�X�0aF
Y�!���M�- 9ܫ]3?��+�҆��Z��pܷ��e�$$ր�w����	 -�UM�������qjD�P@����S����O�3��3O!]�5)Sp����׹�X��k��F%M.�L�)ͩ:8�c��pz�I=M�l�����quגU�tU��0h��E�;/H��la�k��u�P�!h5S�����f�]���6g��Mp����1p�_أ}q�0jG���cL�gi1N�ۂ�UvP;`.@�~�Z?t�X�\Z��6�ގc}``��!ԥϝ��r3��/� �ǭ#��y��u	���n�T�p�i���AM�c�_�%=���űo
��~ޯPK��M\���	�!generative-media/scripts/utils.py�X�o�6��ś����Vl'�����z]�5-�ÐZ�e.���T�����#)YvҢw7�b �������>��T�x.�c��C���<;iy��q�$���"Zp�\B�3.�����`�")
��V�g��)WC���%��5H����b��݋,��B�<c+^�#�B�{� ai��:$��X��0g��pZ��zRk�Z�|1�\�6�w�[0�Lż������t�]��-�����r����3V���/d~/b.����;��a���{^��l��x@�To�G���i)yH�$h�ي0��y7r�g2/5�����W��?�^�P����7���M�Jd���L�]^]�lk-�L��/�#�G�X@��tXk(�ȴ����﯇�9����٠���m/���C!�S�}�F��CT d�N�$���c؂���\l�b(�?J��~�五)jU��F&�H�@m��S>�"m���Fws|F���ڬ�!�"K�'����Y��sJ�J���n��t�m�����*{�xnP�S� ��3�-3�t��0�Ê�T�zF14�`��<���Qs�s.uI�]�й�b�2�d�)�UE\�:��l8�>j�V���KwXr8>)taM�K^�C�B
�ޣ�j�\=���B�#[��M�v'l���k�%l�"SlNY�g&
�R��
�_�ّ:���m~=F�И��E��)�O��ı�����[��X���-������[/V��Y;��d���G:�k�]��<�T*�
���HP�
��X�ct�b���z%zK����B�Y~g^�R9���Y��(U������,�6^g�_:�!�t�B�/��3^qj��	TS��c��6��%��"��-����t�L��5ݍ-s������no<G�ݶ���c?�H0)��+���e��S�S4"��+��zv���6hf�]�Ogh�e�Y+�2E���z�`���4UM�%���AP���B��=o"zVi�E�w4H1:�}��I/%���'x*��;.�ѻ���2;Ƃ�;�Sq
k�7��:6n��������]��P���׀h�&|�l�֘�9h�!ʻf��qd�J�cC��Nd�k��'�ƇY�W�a�om��?���25���T�;
�0Fƍ�������x������lR;��"��TVn
�"�n��pk��Ah��ؓ3�n���3���s��Fy@r-�l@YPٮ�����[î��K��3w��-�*�m�c���O��)���j0����'��K�����?�����b,�
:���znnu�Wx��P������f��`o>,���Gt��h���ɨ�\��E^fq8:.&�c�{lH=����	
�E:!��
L<3R�8[�n*
��MR�@7:BV`�q�q���Q,�!J�Rc��<�TDwcO/�
��/СΓ$�~[����v�M��nD���Ʀf

�c�߅E�� �,�coC�i��
i�}o�#��ɕ��"���~�_3Z�c���ð���ۗb��UE���=o�����>��a
�a-��%_L�N6������L�y6�g����zH�W�R\���DRh���s�% �1�����8Ʊl�^�x�mk�'���]��$Ù�V��/;'��!���<U_�Z1���*�C�y�5Nf�Z9G������)��sin�}$�{���������''''�l]�bQ�!�Q)QX��g@��{���=
���!��U-��D"8��N��.��.��4'�r�!"��}V꼋���8[�أ�&.�Z��A�ܬ:���G��ʠ�
v�L�Y�������\����"�y��A�Ac��)�^8P�;(�%	���b\TQ�U^?C���cW-YL�z�1q#�9��~�韝wgg�^x4�@E%>��nF�^������4��,����I���]L�S2���z����*�M�7�*)�R������V�<�FK�9��F��;�����D�M�}�N��G轀�ًN�մv�{4��P��M��x��=���vQE2��L�}���2:v5	�.ۣ���w��[{��e�+�#W
�核�'~�@�asқ��V�{�a�:����N^��of5�"�_����l���wL�-4�@U���=M6���L?
��4���NZ�2��u��t���8�jH�{�yT�oc4�MSN�/ח��&
��'��^QJ��lA���n�2�(q��h�6�?���>��� ābʢ�?��捆.�
�߶�kN9�'�<�
jL��Eˆ�;��\��ſPK��M\�Qw42u generative-media/scripts/flux.py�W�n�6}�WLՇJX[��I����i�M��MzA6h��ٕ%��Ҹ���ÛL;٠��_dJ�3�3�����{V�h��J.��u�ᷴ��H
lI�T�#p~y�3,�)-Dj��5�:`���1��U3_����j$�I�
DI����bV��B3��h�zs'��ּZ�������� t���q8��]UKV��>�Z��/��pF��L���@�v�)%�z���U���4����W����p��^_d@����q:�(\U��;a$�:/�[��
��2!:eC����8Q�	�T+�;�	ԭ�J���-i0C��&rQ�	�׸�W�`����TH�ǜb.��!�7r5g����x<�����B�����)�w&)4�q���e�����>I�0��hxB���q��2:a��\F�G��(�f��Y�ǨDZq��ci��S���e�
���M��'G��i��$�f�MN��U)N3��s�i��5����ގ/a�B�Z��^����Pk���D�r$TA5S0cߏΆ�?�εv8D��)�������c�����Ρ�c�1�3,�e�@'�����g����b�t݅��v <L_�����<���ȼ<J���ؼ|��OԳ���;��D�S:����ld�%Uy�
�]iZ�k*�
QS���y�I�C�k���V��T�5S.���r0Q��"1�M0�Q��c��i���dJ���6Z�i����]}�Y��"�8��[�-�MUJd]�fUS�R����ޯ��ƔY����&�V����T�)���:�U��,�����W&���$)�M�͡f��$��/h��X36GѭU��� ��/��A$b�޹�N�J���HDž{`�Pؠtt����_�-Z�	Xqτ$���&^��
#�"5�KĴ��:K��`g�����<���%��6H텀�P�3H4y�io���nAx��Z_��ڵ��	{n~��"�l���J*�
��������շ)\�C��[�05��B��θ�m�K�9)�s��G��zP.��r���FxĉؘiC�z
>
��ZQ�����5\�]��
��m�a/Xk���{�$u9�m�ɔ�
H��҆��8�#lɡNu}<��`�t6F��`c3�F�;�P2.���a=�c��ٷ���!ؖ+��A����`������=���Dk�&�
�{[�p����O/.�g�vf��0z�����)�oR��ʍ7�~2R(�7�KG	�l���V��ti���om6C�+�{�v����Py��M�G��MS�̸[�M��=���F��jJ�B�?8���@�Q���3����U�_��TŎ.��W��|�-�gKh{����ߞ�f���|wss�ox���3�A�i⎈Ĝ�8gL���}Q�]zxpp�yƆ�S���i�VS��+�»�հ����Zw
�}yV�x'[���r�]~�Nښ
]�Y�����{��"�N���y�.���K4�"�L�7�Dk���O߰�c��5�Z>!�i�/�J(L9��p�$�������P��mJ
��cp��3
�-xI�rE=h��j������n��?�����E8S�]3�v�[�)�{s����
��Khf�`̌�`���ܽ�?2)��CgSwp�

D6_�o���W1`	) �fD��'�:~�Y�O}��r����~l�l��I����3�B�ý,����(�/v���#Ԛ"�YS�H���~F�>��*_"���w�=����I�t�"�Ļ�ky�c���YN�(��S-
�y����%�	�է�w-эBm4���C�>��x?�2�$�,4Ug:F�PKj�M\�:/��	�*generative-media/references/models/flux.md�Xmo����_18��ܝl�Nm
 ˒mX~��v�A�G.�-wi�R�B�O�Ea~I��%u'�I�6�a�;����3�ܠ�_�k_hh�U�9�V�H'j����B��VӅQ�����wQ��ل��,0)E`�]a����twҴ��~�ǿ�YT��S��嘰\ty4�M�*�o�(��JJ��Q� �{�k�+�eoc�[�P�Q.�x�#y�f�־x7ݥg��6=^L.5�c�:��Æ��6K��2�j����Ѧe�$�5Ͳ�
��V�E�B���hB4�����U�!���:lo���U�^S�T��G]�)o5�^h��M�h{�������.׬�n�T*���=:"/�@V��琷FIZf���J���d֘thtn��K:F����+�	�+�"�����@ٴ^S�B��C�E��d���E�����J���[�U̫5뒖G_C���j~o܂�ͺ��-R��T�
�K	DH�7�t1��}���Ʉ~���]=��G[r�{}�+t�+���@t!dӻg-.�6Ւ6�:`+�86[��iu�t�G�O�VM�0�?�����*�3�s���/L��sT)�a���"!�t��;W�q����Lp�;�P��gX���ҩn��c2�����<L�S���>I����wch�1�&P
��\�M��/���"}bj�Ida�wCL֗�[�Z؍%�%,������ɽ��Z���{)a]��EX�S�]��?ҵ��N�ή'���'X��&&J��h,�f�+
�0mz�=�T?����������Mc�v'����l�O�?��h�ԋ�a7=`�m�=s0����)[�jƝ��
DZ��[���	��د��Z��R�Rwӂ
;�jP}��p<�y�}¸>���1������ҷ��������!��<�b��?�����eND)�{e��?�f�6v��i/m��fwz��+!��-(`?B_sPM��`��K�
�ry���(�)�tȨB�ۡB��b�a�̻{P�3�k��:�V8[�m�<�m׎ 6
�j�&�ح�w�
�Ѱ�h����PN�����o�,RN%�%����\Dm��sё[��J<�B8�"c�_�֪�`R��t���v�ԡa\h�*nn5r�r���ص��r��O��^�%�S ��4�Ƌ�К�W߯.Py Y�b�B�MV�����*w���f���los���W�W[h��bk`5�Y���(Ց�bW;BT��1��ր��i,@�,����Vm���Gu�߶N�n(��Kp�?�Y~�@Rv륞Xo���<-`&m��]
�V���~�� FS�^���1��Bа�#S�UiZ.���2Z�|����c�lȍT5'3���βog������ݘ�������󊘄��o0K���9�rp���;�,��p(��05�H��S[��z��;,����m2^��k`����FІ*��,I<om�0�M��n�E����78�2ϩ]!��<P�{{����C�H�X�U>7�Ly�G��dZu�y����������w��{o���G�i�jx\�N{<�Jf�%5�ۥ��&JE]{�ʇ���g�9�ǥ�W�slXp�T�G4��pb��3f��@��A{�r�������
��ŝ����9f�ϢǕ�T����*�՘��h"�CO E�Sg�Z`3IM�1���y���?���Y^�VX>�T,��Y=hQ�
f��^��uN�hp�����5�0n �s�"z��吻s����
���]
�Sc�D�M�A��K��%��i�X�&|ж�'7��}���hw��d���&�xo1BɡrP���E���c!��Ӈ�ƨ=���T�#d��Ә�>(�5ʨ��P����(�)EU�$q�P��&��r��b��Cӱ+�����
c�-�F;܄#n��wxP�K��r�r'6B�0���Z�z|��\Ј�,��2sk��Һ'
$��J���IN�n� � ���F_�W>��l�p2'�1�o`x�׊+�շ<x������F}�B�%1�7�v�9}K��@*��'�����X3˞��ۯhf�A9�q����:��i��MXs� �>TOtY��>�^���5��
��|xs�������#��S,�Q\0k������ڔ��`�"��*��L��B��q&m��ˣ5�K���AQ���y $�K�<�%!r�����d�巉�j�u�L��z098Az�ֽi�֍�Ȇ�_�u���~�z���"��2��V����d1���
s�lc�����ӷ��~���������h:������s4���t����|�Y��Q�=�oF���g�4N���������4�M��;��5�]���b9�j��v�	�I�R��2���a��"�����9S=f�W��^������d�CQ�KsŃ��2�PKn�M\^�͠�+generative-media/references/models/index.md�UmO9��_1*�!�BW�tjK�U��*����;ٵpl����)�������%{�YRH���V���3�g����NQ�-JL��
��H���
�<��)ZT	�T[Hr��P���,�0�m8Gυ����@(�O�\���L�����[�|�g�[�bl�}aV0�J�[��C�	%��V7i����O�^XՋ�X�U���o����A��>AS�h��)�P�L"��k��F-}+9�s�-:����n�
-�ǀ����g4�����C����ဨv��|.p4��G�����Ӳ��_B�����,��c�j�Y!�0T#�S8:��FĽ5�i��u[�.%�pΈ����;�� Hǭ�p�^�:�;�|�~�����[�8�'Z;�^��et�Å�c)*TE�*��$Э�ĩ�!r���A90'�q��s��Z\
7���E�R��?�G��罔�_ׯ��e���$Z�^��[-w�<y��#�(��X���
��r����`�b�=��m=��ו�	�_J�`��~Ԭ��� �C�D��g��\��iˊ$?�RsԲ�O�b�7������z<�\��|��_.����8?�1.�;�4��ڑ!��5 �
�q!\虲K������w{�h%��M{_�ϣ/�4Y�F�uF+�ka���V���jd��0��7��&F�CY��ȸ0R$t6'�F��*�d�
.x�ab9ͅ�0W���SfA��*�9θO�x�
�>��t�y�Zy��0��BQ�ў�<�����}7�5o�Cm��h�_7���N�#7��e�?�X��!�a�BMvP�q�i���M$��3�S���ͨ� gR<ݚ�0����;�AA�X��6��t��#7H@K�~��������Q�ԙ/���WK]�9�r�߫���I���k�q�;M1m��m:�PKX�M\|����
1generative-media/references/models/nano-banana.md�Xo�����b@���H��CN@�l_b�gm_��{�W�Jژ�2�K�J�������ofIY�\Q�hm�N����3o޼�
����;U�V�ӥ��Х�t\��^K�3�낎�t?�vR草<������
?�O����==E���l�$ɹ
zH7S�4)���}��rFU��>��/}��N���Vۃ���P~��_���4V{�(�jU�����:ʝ��E>ֲ�/ Z��	����M���60�Ǫ)B��L�'�`���=}�5U���
'��X�����NW�0��x'A&��`�����.�bN��\B�*��2]O�"S�M 񙾦��pQ���,[�#��]l�k�Z}�=�����T�:��05U4-\io��-��릮���['=�Ɵ�r�=AP��?h�U0,^�=�@���`{�H5�Q���FM�*���<�_"�f�(�ځ��A[�����g�p��U��Y
�*�g
UM�*�N?a;z��u�_��~� Rv����)�-]I�ߐ�uf�&��9���덬w����:&LDQ�Ϋ�55{���5c��tg��Q�u��˦4q���ֿ�zЈYМ��L�)WAI�}'UI��WE�&����CD�t�7(�*�Z��#PE��*Ӟ�J9���|L�s�h��\�s�0��j8�~c���s���0opv�H��ɐ1�u�V�1mQxX�ߦ���:g[ۛ��M.*W����<h	�7��k��;�8-V��"˔�0B�
��ǐkݡ�L@:T
�Vy5�baos���^F�hKt���W��X�����'Г)��䍋���I�8�!�
냘�.� /3���6�r�@kx�d��ł8���9�'��U��Ƹ�6���*L�i7;����v���[�!�TY���G�
����u$�Kl�$���W�����ٔF��b��bɰ����@f@>|�E*�3�)�?�?}quvtx��������������}̉D���j��ӆ$����/�o�b��܂iђ:�U(���G��+��t	�:�
��Ho��a���F1�����;	��
=b�����];��������q���}F�Wb�N,��Oua2�)��d����=�ʭ}�y����vq,�����{s�G;�	&M�p�v(M�IJ��!v����S��+yaJy������&]]��hks�k|:�n��	��-����J��>ߘZ���)�����@���$��I��釿�j��}�ʣ��z�:�f����ЗL]�qll0L��uk>\_O���.1�2�?�1��S[����'����������L�R����֋�`�9F�.�PXM��
�C-�Z��rd�=%0��mb2E�r
�Vng��}l<����.��Y��e)p��C@�C);�7Yn��1�r�O�A`���v%�dA9ei��L���:m�õ�����Н����w��7�샞F<�4u�84OMhg�$����&5B�F�t$�`t{3���c;.�D9�g���o)]仰+�Z�&��L����	���
�2���!_)}������bG4�;�P�\��V|zsH�~�EL����
���;����Xb�Ӿ��#K�mCq$�%�G9�_IDdp@~�6��̠N��,�K�C-!���]�s�u��c�-#�+Uy��{u���O�a+��*���V����ζ�keW�|:#����p�$<m�R���?:=���W��_�W^U��Qxc�ȗ=� X���ŀ]�q��;��q���Ir6��T�P��8s-nƱ��~Q�Q�!Es�V�ѕSPB�T-�>B�d5���Ĝ�G�����V�9���(�vvPq�F���w�����
��ҶDq�
 '��q��N,���-Q�5��#~�€���|��t^=R�C��G�k���� d�zͅ��p�V�{��X_ћ�;��k�ݜ�Ľ��SE��A���A�X�g,q�B���Zh*PZ�vv޼A�:S�{ŃM��5粈�Ct�$��Sq�3� �*@�,s�@�y�QM��ÓhR�U$[��6��c�`�_��@y��x���j�$�N�'�.�4��)eA鼭6J=�P@�L��ò+ݏ��g����#j����h��`+Q��a2�8m^�L���y���4[$��ʵ�"cً�|֑�P�]P��_8��b��4Z]j�Z����tƉ�܏�]�����o�z�K�\��cNL|�sI����x-k�؅Q,n
Y�JY׺�R[I��<�~��e��=>�+�gD��9y��O�����>�i��X鵽[?�ӡ��^���=�#8ܔ�J��,�.����7w����V�*�y�����zHvL�3�ᙦ#�bdw��1r��,��e�]��Vzxy�B,2g��+�#�~�N����	��&���O6v�C<1��jn�� dk�0��]k�Іt��I7��A'���}rۊ��VT|�h��0�53���${޲�`9�)��nF������C��?C+D��	��R��h�"�ي+앙��%c��ibc1�b����}�e�%��݂���BQ^��a��R���&08�Z��Bs7\O蕦Ɗ3���~(���%�;*m;�EA�F���$���K�#����d���2�
�C�\\�_]|�9��ۿ<�;9����`0H�y$�T��KL��1T���j�b��4�������KKkۑ(��_�ŋ��[�~n
6��z�[�G�g�~�����`҆��VJ�_��	���mw~���|C(��p�'b���;���5�;�;,���Xg8J�od��'!���]vs���6����R�\�����:����LXr�k�l�y1�ڥ=�+o�V��o�z�6<0ü@tg|dy*�C��.�l��8�X�8�/NR��-�-��lݵ�b��1�wz�3��v1����p�G{|�v���'(œ[�j4Ļ���d��PK��M\�Bt��/1generative-media/references/techniques/editing.md�X�n�F}�W�K��Ql��Y��/�cx��`�5��8l�d3��H����j�CY��O�`[�tWW�:U�z�um6Lg��l��+�����sH����tՎ\�B߶�w��zG��O�q��4�Y,��׶����yw���]ɞj�s�x�
7�Mg]�k�$98�W�KچчSz�;za��q|�I�X�.����UN�w5��������3�욉��w]	[+����s��bG|k�F{g<��!79��tL���t�9j=�[��K��Rz��F�ɞ��
�ZW=�7)�G�Lb8��W�rXF�t=�Z��Rc��h��������o�}tp��&+M��|u�(���h���k��Q�� W�ym��w}��{����Qk�5|� ��x��
���+:2EǾq�������B�L8�G�yI
]�Z����M.�s�&$�>}J�^�$�����dL7�+��A���vW��.1�|a_mj�d���"n�ֻ�FM��� ���[C���Y� P�U>��!3�1���~>�[X��8(�ȏ�퀿�l ��r>��fZ
J��Ji}G��T��}�OS���i��U����)��@�Zc� �ߊ��
�Z.(����"4y��PP�?-��o��qZ���*�p��m_i���0��
�'��`sF�􍕎���A�3ڏ�)���q|���i.Yɐm��};�Й5��Z0H�pJe�ε_����n�즉�>x���4���L��
�N	2�)/�ʛ&��
��PZ�t@O[�
ړ�E% 58d��;f�)ۘ
�wi�K��{OYY9w��ƣ������o�
΀�m�,lU�m3Ԟ[xS�H|DE(�mE1T Ŵ����C7�ADb{�������Y	���5��D�u�!�!Ȁ�7!��ǚT[Z�n�أ�_�b�h_X3�F��
�f��I�
��u|��_��$���ɭwd֑!�u�
���^l��Wtfz�A~Aɤ�>\A���\-g�Ȭ��e�l��@�e���y�jzL���ft�|����q��,��a(�V���~�~�$U�R��%�H�Dz�m�#�Ⱥ��֬���-ˈO��H���Iފ�fH]�^%�/���Q��%�����IL�4o�X�&O���m���=�ޫ��/%)��ҩ�NT��dV�c�A�B�|/�\�da$U)������O@۴��ٮ��
5�9�ʐ�z2t���^��,�x�yA�X�N`��8F���z*��b�/�~?���iDE}��@��wC�	9�Z��Jl���(J���	�n�e·F:�v����4����X䱪���v/�6��"��(����vw@�OgB�[gW�8�,�qRN~��FNW����G'����U����j%#Zg��1F�2����ݔ����~�:���l �ݸz8f/�-��V�Y�aŧT�m�"ە�ܦ�1tv����(3�R���J�Fs��K�O$}-�EZ񭠵J��}У��&'2.H4v�C�z)���
��|��>x&z�<��fu<����:����'�ɌK'QM��"9��m�/]ad��{��/���x̯g�'YO�=*�}'�9�xk]�y?IJ]ѕ��wy�v=��u�djU�Y�n�WcK�W�QJ�,y!M���ڛJjVL#uH��P��X-����d��.w�8�:%	��~�v�H
��P:U**^�;���x�n�c2\����kݯ�j�Dcƒ��P���ʑ��a��髱ؗ���lK_�p0S;�༠y���.&��k��#��W"3����Wg��{���ٿ��jE�[�}C�eT!x�-r��rE��p��������Ś�OB�7Hݷ^v�{�n�<|��0�5dL�����2rw�[O�������֛�V��� �qɡ�<��?�مu
J��o�(t�>L�_eB�T����Fnѐ�x	qq����<Q��;�&�D�ϣ���W�ތIC	�����=F�ćS��r��X:�V����Yj��?썌"fƙ	��r���}
Z��j���H�һ�ɹs�y����f
!��j���N�_H�u)L@��ƀ����s��Q�p��V�m��>�����G1�t��C�PF@#�v�X蛙�ϊ2��	�!��M�v&���-m\�&/���E&�3�����0�E�&&� �۬;�:�p�aH��tG�![��p}�_F�k��K��+�PK	�M\ε�%�v3generative-media/references/techniques/upscaling.md�U�n�F}�W�����XI�.�����q�N/(P���1���]ꒇ��/��,IK��<�(P$���3g�]�6.�T9}A3�|2�:c��&��]�^������+�4�
��r�1�zG��Zv���Ԋ�d뾊����x4��m����O����*����yv�I�LFӧw�ɖ~����!:
���ٓ����eU�z2Dϋ��9�Y��I���RZ�i�-��0�_��c�����R*Q8�Y�V(��B��V�"�~�xyy�tЕ7d@��T*Ŷ�
�Zq�Y�Z���LF�Q��c�^�8�e	MMXbK ����֥qJ�V��-;���k��ԮaK^��f��ވ�	������,Y9��777s�VQ���_�doD.�[�咤r^/�Q�՚��+H��oeII�<��ONG�X���v~��p&t4��d+EI��~E�K25䔒�:���f�.Q]0`�+�eNg�}�
���'��Q\ӆQJ���]u��������ԉ5ǽ]��E�')�Av�[]�D/�Z�K�PPײ�4�C܀*���B�:�,�mϡ��̬4�oIb-d!����H�^���$����<�}�Jm�ƣ�xըG��xD�F�W��0�
Q-x�n��
��
NnhCu��<O����4�Q�uW���=J�����TG��]]�}?���s{�ɱ��E�?;�3H[�ƽ�/�x��L��Q8{6
�>����3�]��D�T�:̌
�����asD���{V�������M��Q;14S	���܅A�RVT��{`�u������F�3���L��L<��H�[ch$��?����G�i��R/�껻�q�|���+�]��"
ZT?�]h�=��A�W�ik2�C���aBHE��3<	侦�Y���Q�����<�C�G�K?�
��s��Ay|E8Zh�@�oPK��M\'��generative-media/generative-media.skillPK&�M\vB�	���Ggenerative-media/SKILL.mdPK��M\�;��(���	generative-media/references/prompting.mdPKM\�c�Am+���generative-media/scripts/preview_gallery.pyPK��M\́����'��generative-media/scripts/nano_banana.pyPK��M\���	�!���!generative-media/scripts/utils.pyPK��M\�Qw42u ��K+generative-media/scripts/flux.pyPKj�M\�:/��	�*���2generative-media/references/models/flux.mdPKn�M\^�͠�+���<generative-media/references/models/index.mdPKX�M\|����
1���@generative-media/references/models/nano-banana.mdPK��M\�Bt��/1���Lgenerative-media/references/techniques/editing.mdPK	�M\ε�%�v3���Ugenerative-media/references/techniques/upscaling.mdPK
�Y
references/models/
flux.md 5.2 KB
# FLUX Models (Black Forest Labs)

Available via OpenRouter:
- `black-forest-labs/flux.2-pro` — High quality, production-grade
- `black-forest-labs/flux.2-flex` — Fast and economical

Other variants (not on OpenRouter): FLUX.2 Dev (open-weight, local), FLUX.2 Max (flagship), FLUX.2 Klein (lightweight).

## Strengths

- **Artistic/illustration styles**: Exceptional at stylized, creative outputs
- **Prompt adherence**: Very faithful to detailed visual descriptions
- **Speed**: Fast generation, especially Flex variant
- **Photorealism**: FLUX.2 Pro produces highly realistic photos
- **Consistency**: More consistent aesthetic across batch generations
- **HEX color matching**: Supports exact color specification (e.g., "The vase is color #02eb3c")
- **Text rendering**: FLUX.2 Flex is actually strong at typography (better than Pro)

## Weaknesses

- **No image editing**: Generation only — cannot modify existing images
- **No negative prompts**: Fundamental difference from Stable Diffusion. Rephrase negatives as positives.
- **Resolution**: Max 4MP, dimensions must be multiples of 16
- **Limited control via OpenRouter**: Fewer parameters exposed than BFL's direct API

## FLUX.2 Pro vs FLUX.2 Flex

| Aspect | Pro | Flex |
|--------|-----|------|
| Quality | Production-grade, highest fidelity | Good, adjustable via steps |
| Speed | 3-8s | 2-5s |
| Cost | ~$0.05/img | ~$0.02/img |
| Text rendering | Moderate | Best among FLUX variants |
| Best for | Final outputs, hero shots, faces/hands | Exploration, typography, branded content |

**Recommended workflow:** Prototype with Flex → Finalize with Pro for maximum quality.

## Prompting Tips

### No Negative Prompts

This is the single most important difference from Stable Diffusion. FLUX does not support negative prompts. Instead, rephrase as positives:

| Don't say | Say instead |
|-----------|------------|
| "no blur" | "sharp focus throughout" |
| "no extra fingers" | "anatomically correct hands with five fingers" |
| "no watermark" | "clean image without text overlays" |
| "no dark areas" | "well-lit, evenly illuminated scene" |

### Natural Language + Keywords

FLUX responds well to descriptive, structured prompts. Front-load the most important elements:

**Good:**
> Golden retriever puppy sitting in autumn leaves, warm sunlight, shallow depth of field, 35mm film photography, soft bokeh background, eye-level shot

**Also good (more natural):**
> A golden retriever puppy sits in a pile of autumn leaves, bathed in warm sunlight. Shot on 35mm film with shallow depth of field and soft bokeh.

### Prompt Structure for FLUX

```
[Subject description], [style keywords], [lighting], [composition], [technical specs]
```

### HEX Color Control

FLUX supports exact color specification — critical for branded content:

- Object color: "The vase is color #02eb3c"
- Gradients: "starting with color #02eb3c and finishing with color #edfa3c"
- Background: "on a background of color #1a2332"

### Text Rendering

FLUX.2 Flex is strong at typography. For best results:
- Use quotation marks: `The text 'OPEN' appears in red neon letters`
- Specify placement relative to other elements
- Describe font style: "elegant serif," "bold industrial lettering"
- Include color and sizing information

### Photography/Film Stock References

For photorealistic results, reference specific equipment:
- Modern: "shot on Sony A7IV, clean sharp, high dynamic range"
- Vintage: "shot on Kodak Portra 400, natural grain, warm skin tones"
- Commercial: "shot on Hasselblad, medium format, tack sharp"
- Cinematic: "shot on Arri Alexa, cinematic color grade, 2.39:1"
- Specific lens: "Canon 5D Mark IV, 85mm f/1.4, shallow depth of field"

### Examples

Product shot:
> Minimalist ceramic vase with dried eucalyptus branches, product photography, studio lighting, white background, centered composition, soft shadows

Illustration:
> Mountain village at sunset, Studio Ghibli style, hand-painted watercolor, warm color palette, panoramic view, detailed architecture

Portrait:
> Young woman reading in a library, Vermeer lighting, oil painting style, warm golden tones, medium close-up, bokeh lights in background

Branded content:
> Sleek smartphone on a desk, the screen shows color #4a9eff, minimalist product photography, soft side lighting, the text 'LAUNCH' in bold sans-serif below

### Tips Summary

- Front-load the most important elements (FLUX pays more attention to the beginning)
- Use commas to separate concepts cleanly
- Reference specific art styles, photographers, or artistic movements
- Specify camera settings for photorealism
- Use HEX codes for exact brand colors
- Use FLUX.2 Flex when text rendering matters

## Script Usage

```bash
# Generate with Pro
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/flux.py generate \
  --prompt "Your prompt" --output-dir ./output --model pro --aspect-ratio 16:9

# Fast exploration with Flex
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/flux.py batch \
  --prompt "Your prompt" --output-dir ./output --model flex --count 8

# Production batch with Pro
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/flux.py batch \
  --prompt "Your prompt" --output-dir ./output --model pro --count 4 --prefix final
```
index.md 2.0 KB
# Model Selection Guide

Quick reference for choosing the right model. Detailed guides in individual model files.

## Model Comparison

| Feature | Nano Banana 2 (Gemini 3.1 Flash) | FLUX.2 Pro | FLUX.2 Flex |
|---------|---------------------------|------------|-------------|
| **Provider** | Google via OpenRouter | Black Forest Labs via OpenRouter | Black Forest Labs via OpenRouter |
| **Generation** | Yes | Yes | Yes |
| **Editing** | Yes (send image + instructions) | No | No |
| **Max resolution** | 4K (~4096px) | ~4MP (multiples of 16) | ~4MP (multiples of 16) |
| **Speed** | 20-40s (can spike 180s+ at peak) | 3-8s | 2-5s |
| **Cost** | ~$0.04-0.08/image | ~$0.05/image | ~$0.02/image |
| **Text rendering** | Good (best overall) | Moderate | Good (best FLUX variant) |
| **Photorealism** | Excellent | Excellent | Good |
| **Illustration** | Good | Excellent | Good |
| **HEX color control** | Good | Excellent | Excellent |
| **Negative prompts** | Rephrase as positives | Not supported | Not supported |
| **Prompt style** | Natural language, conversational | Keyword-rich, structured | Keyword-rich, structured |
| **API key** | OPENROUTER_API_KEY | OPENROUTER_API_KEY | OPENROUTER_API_KEY |

## Decision Tree

1. **Need to edit an existing image?** → Nano Banana (only option with editing)
2. **Need text rendered in the image?** → Nano Banana (best overall) or FLUX.2 Flex (good + fast)
3. **Need 2K/4K resolution?** → Nano Banana (explicit size tiers)
4. **Need exact brand colors?** → FLUX.2 Pro/Flex (strongest HEX matching)
5. **Need artistic/illustration style?** → FLUX.2 Pro (strongest for creative styles)
6. **Need photorealistic image?** → Nano Banana or FLUX.2 Pro (both excellent)
7. **Need fast/cheap exploration?** → FLUX.2 Flex ($0.02/img, 2-5s)
8. **Default choice** → Nano Banana (most versatile)

## API Key Setup

All models use OpenRouter. Single key for everything:

```bash
export OPENROUTER_API_KEY="your-key-here"
```

Get a key at https://openrouter.ai/keys
nano-banana.md 6.0 KB
# Nano Banana 2 (Gemini 3.1 Flash Image)

Model ID: `google/gemini-3.1-flash-image-preview` via OpenRouter.

Note: There are two Nano Banana variants:
- **Nano Banana** (`gemini-2.5-flash-image`) — legacy, good for drafts
- **Nano Banana 2** (`gemini-3.1-flash-image-preview`) — higher fidelity, our default

This skill uses Nano Banana 2 unless noted otherwise.

## Strengths

- **Image editing**: Only model here that accepts an input image + text instructions
- **Text rendering**: Best at placing readable text within images
- **Resolution**: Supports 1K, 2K, and 4K output
- **Versatility**: Handles photo, illustration, diagram, and mixed-media well
- **Conversational prompts**: Responds well to natural language (not just keywords)
- **Instruction following**: Excellent at following specific layout/composition requests
- **Multi-image input**: Supports up to 14 reference images for composition
- **Search grounding**: Can integrate real-time data via Google Search (Gemini 3.1 Flash)

## Weaknesses

- **Consistency across batches**: Faces, characters, and styles may vary between images
- **Hands/fingers**: Can still produce anatomical errors (common across all models)
- **Speed**: 20-40s normal, can spike to 180s+ during peak load
- **Occasional refusals**: May refuse prompts it considers unsafe
- **503 errors**: Preview model gets lower priority — ~45% failure rate at peak hours. Retry on 503.
- **Character drift**: Features may drift after multiple editing turns

## Known Quirks

- **SynthID watermark**: All generated images include invisible SynthID watermarks. Cannot be disabled.
- **"Remove watermark" blocked**: Requesting watermark removal in edit prompts triggers a `MALFORMED_FUNCTION_CALL` error — intentional safety mechanism.
- **Prompt rewriting**: The model has built-in prompt enhancement. Leave it on — disabling reduces quality.
- **Aspect ratio enforcement**: Sometimes defaults to 1:1 despite prompt. Always set `aspect_ratio` explicitly in `image_config` rather than relying on prompt text.
- **Size parameter**: Must use uppercase "K" (1K, 2K, 4K). Lowercase rejected.
- **Rate limits**: ~10-50 RPM, 100+ RPD on preview tier. No free tier.

## Prompting Tips

### Natural Language Works Best

Unlike FLUX (keyword-driven), Nano Banana excels with conversational, narrative prompts:

**Good:**
> Create a cozy coffee shop interior. The scene should feel warm and inviting, with wooden furniture, hanging plants, and soft ambient lighting coming through large windows. A few books are scattered on a table. Style: editorial photography, shallow depth of field.

**Less effective (too keyword-y):**
> coffee shop, cozy, warm lighting, wooden furniture, plants, editorial photography, bokeh

### Include Purpose Context

Adding context about the intended use significantly improves results:

> "Create a logo for a high-end, minimalist skincare brand" >> "create a logo"

The model uses context to set tone, polish level, and appropriate style.

### Be Explicit About What You Want

Nano Banana follows instructions literally. Be precise:

- "Place the logo in the top-left corner" (not "add a logo")
- "Use exactly these colors: #1a2332 and #4a9eff" (not "blue tones")
- "The text should read 'HELLO WORLD' in sans-serif bold" (not "add some text")

### Semantic Negatives

Rather than listing exclusions, describe what you want positively:
- Say "a clean, well-lit studio" instead of "no clutter, no dark areas"
- Say "sharp focus throughout" instead of "no blur"

### Image Editing Prompts

When editing, describe the change with preservation instructions:

**Good:** "Remove the background and replace it with a soft gradient from #f5f5f5 to #e0e0e0. Keep everything else unchanged."
**Good:** "Change the color of the car from red to dark blue (#1a3366). Maintain identical composition and lighting."
**Bad:** "Make it look better" (too vague)

For identity preservation across edits:
- "Maintain identical facial features, eye color, hairstyle, and expression"
- Use "this exact person/character" to anchor identity
- Re-specify preservation details each turn — the model drifts without reinforcement

### Handling Character Consistency

- Restart with detailed character descriptions if features drift
- Use "this exact [character]" with specific feature callouts
- For multi-scene work, establish a "character anchor" image first
- Make small changes per turn rather than large overhauls

### Size Selection

| Size | Actual Resolution | Use Case | Cost |
|------|------------------|----------|------|
| 1K | ~1024px | Drafts, exploration, web thumbnails | Lowest |
| 2K | ~2048px | Social media, blog images, presentations | Medium |
| 4K | ~4096px | Print, hero images, high-DPI displays | Highest |

Start with 1K for exploration, regenerate at 2K/4K for finals (don't upscale 1K images).

### Optimal Prompt Template

```
[Purpose/Context]: Create a [type of image] for [use case]
[Subject]: [Detailed description with specific attributes]
[Setting/Environment]: [Detailed scene description]
[Style/Aesthetic]: [Art style, photography style, or rendering approach]
[Technical]: [Camera angle, lens, lighting setup]
[Mood/Atmosphere]: [Emotional tone, color palette]
```

## Script Usage

```bash
# Generate single image
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/nano_banana.py generate \
  --prompt "Your prompt" --output-dir ./output --size 2K --aspect-ratio 16:9

# Edit existing image
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/nano_banana.py edit \
  --prompt "Remove background" --input photo.png --output-dir ./output

# Batch variations
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/nano_banana.py batch \
  --prompt "Your prompt" --output-dir ./output --count 8 --prefix hero
```

## API Parameters

```json
{
  "model": "google/gemini-3.1-flash-image-preview",
  "messages": [{"role": "user", "content": "prompt"}],
  "modalities": ["image", "text"],
  "image_config": {
    "aspect_ratio": "1:1",
    "image_size": "1K"
  }
}
```

Valid aspect ratios: 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9
Valid sizes: 1K, 2K, 4K
references/
prompting.md 6.4 KB
# Universal Prompting Best Practices

Applies to all image generation models. Model-specific tips in `models/` directory.

## Prompt Anatomy

A strong prompt has 5 layers. Not all are needed every time — match complexity to the task.

```
[Subject] + [Style] + [Composition] + [Lighting/Color] + [Constraints]
```

**Example:**
> A ceramic coffee mug on a wooden table, product photography style, centered composition with shallow depth of field, warm golden hour side lighting, white background, no text, no watermarks

## 1. Subject — What

Be specific. "A dog" → "A golden retriever puppy sitting on grass, looking at camera."

- Name the exact object, person type, animal breed, etc.
- Describe pose, action, expression, and relationship between elements
- Include material/texture when relevant: "brushed aluminum", "hand-knitted wool", "frosted glass"
- Replace vague descriptions with precise ones: "ornate elven plate armor, etched with silver leaf patterns" not "a knight in armor"

## 2. Style — How It Looks

Style descriptors dramatically shift output. Layer them for precision.

**Photography styles:**
- Product photography, editorial photography, street photography
- Portrait, macro, aerial/drone, architectural
- Film grain, Polaroid, 35mm, medium format, tilt-shift
- Studio lighting, natural light, golden hour

**Film stock references** (especially effective for FLUX):
- "Shot on Hasselblad" — high-end commercial photography look
- "Shot on Kodak Portra 400" — warm, natural skin tones with gentle grain
- "Shot on Fuji Velvia 50" — saturated colors, high contrast
- "Shot on Sony A7IV" — clean, sharp, modern digital
- "Polaroid" — vintage, washed-out, square format feel

**Illustration/Art styles:**
- Flat vector illustration, line art, watercolor, oil painting
- Isometric, pixel art, low poly, paper cut-out
- Art nouveau, bauhaus, ukiyo-e, comic book, storybook
- Technical drawing, blueprint, infographic

**Digital/3D styles:**
- 3D render, clay render, wireframe
- Unreal Engine, Cinema 4D, Blender
- Glass morphism, neumorphism

**Abstract keywords:** minimalist, maximalist, geometric, organic, surreal, hyperrealistic, photorealistic

## 3. Composition — Where Things Are

**Camera angles:**
- Eye level — neutral, natural
- Low angle — makes subject powerful, imposing
- High angle / bird's eye — makes subject small, shows layout
- Dutch angle — tilted, creates tension
- Worm's eye view — extreme low angle looking up

**Shot types:**
- Extreme close-up (ECU) — detail of eyes, texture, small object
- Close-up — face, single object fills frame
- Medium shot — waist up, subject in context
- Full shot — entire body or object
- Wide/establishing shot — subject in environment

**Composition rules:**
- Rule of thirds, centered, symmetrical, asymmetrical
- Leading lines guiding eye toward subject
- Frame within a frame (doorway, window, arch)
- Negative space: "generous negative space on the right" (useful for text overlay)
- Shallow depth of field (bokeh), deep focus, layered foreground/midground/background

## 4. Lighting & Color

**Lighting:**
- **Rembrandt lighting** — dramatic triangle on shadowed cheek
- **Butterfly lighting** — front-facing, glamour shadow under nose
- **Rim lighting / backlighting** — halo effect from behind
- **Split lighting** — half face lit, half in shadow
- Golden hour, blue hour, overcast, harsh midday sun
- Volumetric light, god rays, studio softbox
- Chiaroscuro — extreme contrast between light and dark
- High key (bright, minimal shadows) vs low key (dark, dramatic)

**Color:**
- Specify palette: "muted earth tones", "vibrant neon", "monochromatic blue"
- HEX codes for exact colors: "#4a9eff", "#1a2332"
- Mood via color: "warm palette", "cool desaturated tones", "sepia"
- Color schemes: monochromatic, complementary (orange/blue), analogous (blue/teal/green)
- Contrast: "high contrast", "pastel", "washed out"

## 5. Constraints — What to Avoid

**For models that support negative prompts (Stable Diffusion):**
- "no text", "no watermarks", "no borders"
- "no people", "no hands"
- "no gradients", "no drop shadows"

**For FLUX and Nano Banana (no negative prompts):** Rephrase as positives:
- "clean image without text overlays" instead of "no text"
- "sharp focus throughout" instead of "no blur"
- "anatomically correct hands with five fingers" instead of "no extra fingers"
- "well-lit, evenly illuminated scene" instead of "no dark areas"

## Prompt Length

- **Short** (10-30 words): Creative freedom, exploration
- **Medium** (30-80 words): Balanced control. Best for most use cases.
- **Long** (80-150 words): Maximum control. Risk of conflicting instructions.

Rule of thumb: start short, add specificity only where the model needs guidance.

## Iteration Strategy

1. **Start broad** — Short prompt, many variations. Identify what works.
2. **Lock the good** — Keep elements that work, modify what doesn't.
3. **Add specificity** — Layer in composition, lighting, color constraints.
4. **Refine constraints** — Add negative/positive constraints to eliminate artifacts.
5. **Change one variable at a time** — Understand what each element controls.

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| Too vague ("nice image") | Add specific subject, style, composition |
| Contradictory styles ("minimalist and maximalist") | Pick one direction |
| Too many subjects | Focus on one hero element |
| Describing only what you DON'T want | Lead with what you DO want |
| Overloading with adjectives | Use 2-3 key descriptors, not 10 |
| Ignoring aspect ratio context | Match ratio to content (portrait for people, landscape for scenes) |
| Front-loading quality tags ("8K ultra-detailed masterpiece") | Camera/composition terms steer realism more reliably |
| Ignoring lighting | Lighting is the #1 underutilized element that separates amateur from pro results |
| Changing too many variables when iterating | Change one element at a time |

## Aspect Ratio Guide

| Ratio | Use Case |
|-------|----------|
| 1:1 | Icons, avatars, social media posts (Instagram feed), logos |
| 4:5 | Instagram feed (max screen space), portraits, posters |
| 3:4 | Portraits, phone wallpapers, print photography |
| 2:3 | Tall portraits, 4x6" prints, book covers, Pinterest pins |
| 9:16 | Stories, Reels, TikTok, mobile-first vertical content |
| 3:2 | Standard landscape photography, 6x4" prints, DSLR-native |
| 4:3 | Presentations, tablets, compact camera native |
| 16:9 | YouTube thumbnails, hero banners, desktop wallpapers, cinematic |
| 21:9 | Ultra-wide cinematic, website hero banners, panoramic |
references/techniques/
editing.md 5.0 KB
# Image Editing Techniques

Currently only supported by **Nano Banana 2** (Gemini 3.1 Flash). Other models are generation-only.

## Generation vs Editing: Key Difference

**Generation**: Build from scratch — describe everything.
**Editing**: Modify existing — describe the desired end state + what to preserve.

- Bad: "Turn the red dress into a blue dress" (describes action)
- Good: "The dress should be deep navy blue (#1b2838). Keep everything else unchanged." (describes result)
- Bad: "Remove the person from the background"
- Good: "Empty park bench on a gravel path, soft afternoon light" (describes what should be there)

## Edit Types

### Background Changes
```
"Remove the background and replace with pure white (#ffffff). Keep everything else unchanged."
"Replace the background with a blurred office environment. Maintain the subject's position and scale."
"Make the background a soft gradient from #f5f5f5 to #e0e0e0."
```

### Color Modifications
```
"Change the shirt color from red to navy blue (#1b2838). Maintain all other details."
"Convert to black and white while keeping the roses red."
"Shift the entire color palette to warmer tones."
```

### Object Manipulation
```
"Remove the person on the right side of the image. Fill the area with the surrounding background."
"Add a coffee cup on the table in front of the laptop."
"Replace the text on the sign with 'OPEN' in the same font style."
```

### Style Transfer
```
"Convert this photo to a watercolor painting style while maintaining the original composition and subject placement."
"Make this look like a vintage 1970s photograph with film grain. Keep the framing identical."
"Apply a flat vector illustration style. Preserve the layout and color scheme."
```

### Enhancement
```
"Increase the contrast and make the colors more vibrant. Do not change the composition."
"Add dramatic lighting with rim light from the left."
"Make this image look more professional and polished."
```

## Edit Prompt Best Practices

1. **Be specific about location**: "in the top-right corner", "on the table", "behind the subject"
2. **Reference colors by hex**: "#ff6b35" not "orange-ish"
3. **One change at a time**: Multiple edits in one prompt may conflict. Chain them.
4. **Describe the desired result**: "The car should be blue" > "Change the car"
5. **Explicitly preserve**: "Keep everything else unchanged" or "Maintain identical facial features"
6. **State aspect ratio preservation**: "Do not change the input aspect ratio" when needed

## Identity Preservation

When editing images of people or characters:

- **Lock features explicitly**: "Maintain identical facial features, eye color, hairstyle, and expression"
- **Anchor identity**: Use "this exact person/character" with specific feature callouts
- **Reinforce each turn**: Re-specify preservation details — the model drifts without reinforcement
- **Use granular verbs**: "Change the clothes to..." (targeted) not "Transform" (wholesale change)
- **Avoid vague transforms**: "Convert to pencil sketch with natural graphite lines" preserves structure better than "make it a sketch"

## Chaining Edits

For complex modifications, chain multiple edit calls sequentially:

1. **First pass**: Major structural change (e.g., background removal)
2. **Second pass**: Color/style adjustment on the result
3. **Third pass**: Final polish (lighting, contrast)

Each pass uses the output of the previous as input. This produces better results than cramming everything into a single prompt.

## Handling Drift

If quality or identity drifts after multiple editing turns:
- **Restart from the original** with a more comprehensive prompt
- **Establish a "character anchor"** image for multi-scene work
- **Make small changes per turn** rather than large overhauls
- **Re-describe** critical preservation details in each prompt

## Script Usage

```bash
# Single edit
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/nano_banana.py edit \
  --prompt "Remove the background and replace with soft gray gradient. Keep everything else unchanged." \
  --input original.png --output-dir ./edits --prefix bg-removed

# Chain edits (run sequentially)
OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/nano_banana.py edit \
  --prompt "Remove background" --input photo.png --output-dir ./edits --prefix step1

OPENROUTER_API_KEY="..." uv run --with requests python3 scripts/nano_banana.py edit \
  --prompt "Add soft shadow beneath the object" --input ./edits/step1-edited.png \
  --output-dir ./edits --prefix step2
```

## Edit Prompt Template

```
[What to change]: [Specific description of the desired modification]
[What to preserve]: [Explicit list of elements that must remain unchanged]
[Style/Mood]: [Any style adjustments, or "maintain current style"]
[Constraints]: [Additional restrictions — aspect ratio, color palette, etc.]
```

**Example:**
```
Change the background to a sunset beach scene with warm golden light.
Preserve the person's exact face, expression, hairstyle, and clothing.
Maintain the current photographic style and color grading.
Keep the person in the same position and scale within the frame.
```
upscaling.md 1.9 KB
# Upscaling & Post-Processing

## Native Upscaling

**Nano Banana** supports native resolution tiers:
- 1K (~1024px) — Default, fastest
- 2K (~2048px) — 2x quality
- 4K (~4096px) — Maximum, slowest

Always generate at 1K first for exploration, then regenerate at 2K/4K for finals (rather than upscaling a 1K image).

**FLUX** outputs at ~1024px max — no native upscaling.

## Post-Processing Pipeline

After generation, common post-processing steps:

### 1. Crop & Resize
Use any image tool to crop whitespace or resize to exact dimensions:
```bash
# Using ImageMagick (if installed)
convert input.png -trim -resize 1200x630 output.png

# Using Python/Pillow
uv run --with Pillow python3 -c "
from PIL import Image
img = Image.open('input.png')
img = img.resize((1200, 630), Image.LANCZOS)
img.save('output.png')
"
```

### 2. Background Removal
For transparent backgrounds, use the logo-creator's rembg script if available:
```bash
uv run --with "rembg>=2.0.57" --with Pillow --with onnxruntime --python 3.10 \
  python3 ~/.claude/skills/logo-creator/scripts/remove_bg.py input.png output-transparent.png
```

### 3. Format Conversion
```bash
# PNG to JPEG (with quality setting)
uv run --with Pillow python3 -c "
from PIL import Image
Image.open('input.png').convert('RGB').save('output.jpg', quality=90)
"

# PNG to WebP (smaller file size for web)
uv run --with Pillow python3 -c "
from PIL import Image
Image.open('input.png').save('output.webp', quality=85)
"
```

### 4. SVG Vectorization
For logos and icons, convert to SVG using logo-creator's vectorizer:
```bash
uv run --with vtracer python3 ~/.claude/skills/logo-creator/scripts/vectorize.py \
  input.png output.svg --detail medium --colormode color
```

## Batch Post-Processing

For multiple images, loop through a directory:
```bash
for img in ./output/*.png; do
  base=$(basename "$img" .png)
  # Your processing command here
done
```
scripts/
flux.py 4.9 KB
#!/usr/bin/env python3
"""Generate images via FLUX models (FLUX.2 Pro, FLUX.2 Flex) through OpenRouter.

Usage:
    # Generate single image
    python flux.py generate --prompt "PROMPT" --output-dir DIR [options]

    # Batch variations
    python flux.py batch --prompt "PROMPT" --output-dir DIR --count 5 [options]

Environment:
    OPENROUTER_API_KEY - Required.

Note: FLUX models are generation-only (no image editing support).
"""

import argparse
import sys
import time
from pathlib import Path

try:
    import requests
except ImportError:
    print("ERROR: 'requests' required. Run with: uv run --with requests python3 flux.py ...")
    sys.exit(1)

sys.path.insert(0, str(Path(__file__).parent))
from utils import get_api_key, save_base64_image, extract_image_b64, ensure_output_dir, generate_filename

API_URL = "https://openrouter.ai/api/v1/chat/completions"

MODELS = {
    "pro": "black-forest-labs/flux.2-pro",
    "flex": "black-forest-labs/flux.2-flex",
}

VALID_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]


def call_api(prompt: str, api_key: str, model_key: str = "pro", aspect_ratio: str = "1:1") -> dict:
    """Call OpenRouter FLUX endpoint."""
    model = MODELS.get(model_key, MODELS["pro"])
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    }
    payload = {
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
        "modalities": ["image"],
        "image_config": {
            "aspect_ratio": aspect_ratio,
        },
    }
    resp = requests.post(API_URL, headers=headers, json=payload, timeout=180)
    resp.raise_for_status()
    return resp.json()


def generate_single(prompt: str, api_key: str, output_path: Path,
                     model_key: str = "pro", aspect_ratio: str = "1:1") -> bool:
    """Generate single image. Returns True on success."""
    response = call_api(prompt, api_key, model_key, aspect_ratio)
    b64 = extract_image_b64(response)
    if not b64:
        print(f"  WARNING: No image in response for {output_path.name}")
        return False
    return save_base64_image(b64, output_path)


def cmd_generate(args):
    api_key = get_api_key("openrouter")
    out_dir = ensure_output_dir(args.output_dir)
    output_path = out_dir / f"{args.prefix}.png"

    model_name = MODELS.get(args.model, args.model)
    print(f"Generating with {model_name} | ratio={args.aspect_ratio}")
    print(f"Output: {output_path}")

    if generate_single(args.prompt, api_key, output_path, args.model, args.aspect_ratio):
        size_kb = output_path.stat().st_size / 1024
        print(f"OK ({size_kb:.0f} KB)")
    else:
        print("FAILED")
        sys.exit(1)


def cmd_batch(args):
    api_key = get_api_key("openrouter")
    out_dir = ensure_output_dir(args.output_dir)

    model_name = MODELS.get(args.model, args.model)
    print(f"Generating {args.count} variations with {model_name} | ratio={args.aspect_ratio}")
    print(f"Output: {out_dir}\n")

    success = 0
    for i in range(1, args.count + 1):
        filename = generate_filename(args.prefix, i)
        output_path = out_dir / filename
        print(f"[{i}/{args.count}] {filename}...", end=" ", flush=True)

        try:
            if generate_single(args.prompt, api_key, output_path, args.model, args.aspect_ratio):
                size_kb = output_path.stat().st_size / 1024
                print(f"OK ({size_kb:.0f} KB)")
                success += 1
            else:
                print("FAILED (no image)")
        except requests.exceptions.HTTPError as e:
            print(f"FAILED ({e.response.status_code}: {e.response.text[:200]})")
        except Exception as e:
            print(f"FAILED ({e})")

        if i < args.count:
            time.sleep(1)

    print(f"\nDone: {success}/{args.count} images in {out_dir}")


def main():
    parser = argparse.ArgumentParser(description="FLUX image generation via OpenRouter")
    sub = parser.add_subparsers(dest="command", required=True)

    def add_common(p):
        p.add_argument("--prompt", required=True, help="Generation prompt")
        p.add_argument("--output-dir", required=True, help="Output directory")
        p.add_argument("--aspect-ratio", default="1:1", choices=VALID_RATIOS)
        p.add_argument("--model", default="pro", choices=["pro", "flex"],
                       help="FLUX variant: pro (high quality) or flex (fast/cheap)")
        p.add_argument("--prefix", default="img", help="Filename prefix")

    gen = sub.add_parser("generate", help="Generate single image")
    add_common(gen)
    gen.set_defaults(func=cmd_generate)

    bat = sub.add_parser("batch", help="Generate multiple variations")
    add_common(bat)
    bat.add_argument("--count", type=int, default=5, help="Number of variations")
    bat.set_defaults(func=cmd_batch)

    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()
nano_banana.py 6.7 KB
#!/usr/bin/env python3
"""Generate or edit images via Nano Banana 2 (Gemini 3.1 Flash Image) through OpenRouter.

Usage:
    # Generate new image
    python nano_banana.py generate --prompt "PROMPT" --output-dir DIR [options]

    # Edit existing image
    python nano_banana.py edit --prompt "INSTRUCTIONS" --input IMAGE --output-dir DIR [options]

    # Generate batch variations
    python nano_banana.py batch --prompt "PROMPT" --output-dir DIR --count 5 [options]

Environment:
    OPENROUTER_API_KEY - Required.
"""

import argparse
import base64
import json
import sys
import time
from pathlib import Path

try:
    import requests
except ImportError:
    print("ERROR: 'requests' required. Run with: uv run --with requests python3 nano_banana.py ...")
    sys.exit(1)

# Import shared utils (same directory)
sys.path.insert(0, str(Path(__file__).parent))
from utils import get_api_key, save_base64_image, extract_image_b64, ensure_output_dir, generate_filename

API_URL = "https://openrouter.ai/api/v1/chat/completions"
MODEL = "google/gemini-3.1-flash-image-preview"
VALID_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]
VALID_SIZES = ["1K", "2K", "4K"]


def call_api(messages: list, api_key: str, aspect_ratio: str = "1:1", size: str = "1K") -> dict:
    """Call OpenRouter Nano Banana endpoint. Returns raw JSON response."""
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    }
    payload = {
        "model": MODEL,
        "messages": messages,
        "modalities": ["image", "text"],
        "image_config": {
            "aspect_ratio": aspect_ratio,
            "image_size": size,
        },
    }
    resp = requests.post(API_URL, headers=headers, json=payload, timeout=180)
    resp.raise_for_status()
    return resp.json()


def generate_single(prompt: str, api_key: str, output_path: Path,
                     aspect_ratio: str = "1:1", size: str = "1K") -> bool:
    """Generate a single image. Returns True on success."""
    messages = [{"role": "user", "content": prompt}]
    response = call_api(messages, api_key, aspect_ratio, size)
    b64 = extract_image_b64(response)
    if not b64:
        print(f"  WARNING: No image in response for {output_path.name}")
        return False
    return save_base64_image(b64, output_path)


def edit_image(prompt: str, input_path: Path, api_key: str, output_path: Path,
               aspect_ratio: str = "1:1", size: str = "1K") -> bool:
    """Edit an existing image with instructions. Returns True on success."""
    if not input_path.exists():
        print(f"ERROR: Input image not found: {input_path}")
        return False

    img_bytes = input_path.read_bytes()
    b64_img = base64.b64encode(img_bytes).decode()
    mime = "image/png" if input_path.suffix.lower() == ".png" else "image/jpeg"

    messages = [{
        "role": "user",
        "content": [
            {
                "type": "image_url",
                "image_url": {"url": f"data:{mime};base64,{b64_img}"}
            },
            {
                "type": "text",
                "text": prompt
            }
        ]
    }]

    response = call_api(messages, api_key, aspect_ratio, size)
    b64 = extract_image_b64(response)
    if not b64:
        print(f"  WARNING: No image in edit response")
        return False
    return save_base64_image(b64, output_path)


def cmd_generate(args):
    api_key = get_api_key("openrouter")
    out_dir = ensure_output_dir(args.output_dir)
    output_path = out_dir / f"{args.prefix}.png"

    print(f"Generating | ratio={args.aspect_ratio} size={args.size}")
    print(f"Output: {output_path}")

    if generate_single(args.prompt, api_key, output_path, args.aspect_ratio, args.size):
        size_kb = output_path.stat().st_size / 1024
        print(f"OK ({size_kb:.0f} KB)")
    else:
        print("FAILED")
        sys.exit(1)


def cmd_edit(args):
    api_key = get_api_key("openrouter")
    out_dir = ensure_output_dir(args.output_dir)
    input_path = Path(args.input)
    output_path = out_dir / f"{args.prefix}-edited.png"

    print(f"Editing {input_path.name} | ratio={args.aspect_ratio} size={args.size}")
    print(f"Output: {output_path}")

    if edit_image(args.prompt, input_path, api_key, output_path, args.aspect_ratio, args.size):
        size_kb = output_path.stat().st_size / 1024
        print(f"OK ({size_kb:.0f} KB)")
    else:
        print("FAILED")
        sys.exit(1)


def cmd_batch(args):
    api_key = get_api_key("openrouter")
    out_dir = ensure_output_dir(args.output_dir)

    print(f"Generating {args.count} variations | ratio={args.aspect_ratio} size={args.size}")
    print(f"Output: {out_dir}\n")

    success = 0
    for i in range(1, args.count + 1):
        filename = generate_filename(args.prefix, i)
        output_path = out_dir / filename
        print(f"[{i}/{args.count}] {filename}...", end=" ", flush=True)

        try:
            if generate_single(args.prompt, api_key, output_path, args.aspect_ratio, args.size):
                size_kb = output_path.stat().st_size / 1024
                print(f"OK ({size_kb:.0f} KB)")
                success += 1
            else:
                print("FAILED (no image)")
        except requests.exceptions.HTTPError as e:
            print(f"FAILED ({e.response.status_code}: {e.response.text[:200]})")
        except Exception as e:
            print(f"FAILED ({e})")

        if i < args.count:
            time.sleep(1)

    print(f"\nDone: {success}/{args.count} images in {out_dir}")


def main():
    parser = argparse.ArgumentParser(description="Nano Banana 2 (Gemini 3.1 Flash) image generation")
    sub = parser.add_subparsers(dest="command", required=True)

    # Shared args
    def add_common(p):
        p.add_argument("--prompt", required=True, help="Generation prompt or edit instructions")
        p.add_argument("--output-dir", required=True, help="Output directory")
        p.add_argument("--aspect-ratio", default="1:1", choices=VALID_RATIOS)
        p.add_argument("--size", default="1K", choices=VALID_SIZES, help="1K, 2K, or 4K")
        p.add_argument("--prefix", default="img", help="Filename prefix")

    # generate
    gen = sub.add_parser("generate", help="Generate single image")
    add_common(gen)
    gen.set_defaults(func=cmd_generate)

    # edit
    ed = sub.add_parser("edit", help="Edit existing image")
    add_common(ed)
    ed.add_argument("--input", required=True, help="Path to input image")
    ed.set_defaults(func=cmd_edit)

    # batch
    bat = sub.add_parser("batch", help="Generate multiple variations")
    add_common(bat)
    bat.add_argument("--count", type=int, default=5, help="Number of variations")
    bat.set_defaults(func=cmd_batch)

    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()
preview_gallery.py 1.1 KB
#!/usr/bin/env python3
"""Build an HTML preview gallery from generated images.

Usage:
    python preview_gallery.py --input-dir DIR [--output FILE] [--title TITLE]
"""

import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
from utils import build_preview_html


def main():
    parser = argparse.ArgumentParser(description="Build HTML image preview gallery")
    parser.add_argument("--input-dir", required=True, help="Directory containing generated images")
    parser.add_argument("--output", default=None, help="Output HTML file (default: input-dir/preview.html)")
    parser.add_argument("--title", default="Generated Images", help="Gallery title")
    args = parser.parse_args()

    input_dir = Path(args.input_dir)
    if not input_dir.exists():
        print(f"ERROR: Directory not found: {input_dir}")
        sys.exit(1)

    output_path = Path(args.output) if args.output else input_dir / "preview.html"
    html = build_preview_html(input_dir, args.title)
    output_path.write_text(html)
    print(f"Gallery saved to {output_path}")


if __name__ == "__main__":
    main()
utils.py 5.7 KB
#!/usr/bin/env python3
"""Shared utilities for generative-media scripts.

Handles: API key resolution, image saving, filename generation, preview gallery.
"""

import base64
import os
import sys
from datetime import datetime
from pathlib import Path

# --- API Key Resolution ---

def get_api_key(provider: str) -> str:
    """Resolve API key for the given provider. Exits on failure."""
    env_map = {
        "openrouter": "OPENROUTER_API_KEY",
        "gemini": "GEMINI_API_KEY",
    }
    env_var = env_map.get(provider)
    if not env_var:
        print(f"ERROR: Unknown provider '{provider}'")
        sys.exit(1)
    key = os.environ.get(env_var)
    if not key:
        print(f"ERROR: {env_var} environment variable is required.")
        sys.exit(1)
    return key


# --- File Handling ---

def generate_filename(prefix: str, index: int, ext: str = "png") -> str:
    """Generate a numbered filename: prefix-01.png"""
    return f"{prefix}-{index:02d}.{ext}"


def save_base64_image(b64_data: str, output_path: Path) -> bool:
    """Decode base64 image data and save to file. Returns True on success."""
    try:
        # Strip data URI prefix if present
        if "," in b64_data:
            b64_data = b64_data.split(",", 1)[1]
        img_bytes = base64.b64decode(b64_data)
        output_path.write_bytes(img_bytes)
        return True
    except Exception as e:
        print(f"  ERROR saving image: {e}")
        return False


def ensure_output_dir(path: str) -> Path:
    """Create output directory if needed, return Path object."""
    out = Path(path)
    out.mkdir(parents=True, exist_ok=True)
    return out


# --- Response Parsing ---

def extract_image_b64(response: dict) -> str | None:
    """Extract base64 image data from OpenRouter chat completion response."""
    try:
        message = response["choices"][0]["message"]

        # Method 1: images array (Nano Banana style)
        images = message.get("images", [])
        if images:
            return images[0]["image_url"]["url"]

        # Method 2: content parts with image_url
        content = message.get("content", "")
        if isinstance(content, list):
            for part in content:
                if isinstance(part, dict) and part.get("type") == "image_url":
                    return part["image_url"]["url"]

        # Method 3: inline_data in content parts (Gemini native)
        if isinstance(content, list):
            for part in content:
                if isinstance(part, dict) and "inline_data" in part:
                    return part["inline_data"].get("data")

        return None
    except (KeyError, IndexError):
        return None


def extract_text_response(response: dict) -> str | None:
    """Extract text content from response (useful for edit responses that include text)."""
    try:
        message = response["choices"][0]["message"]
        content = message.get("content", "")
        if isinstance(content, str):
            return content
        if isinstance(content, list):
            texts = [p.get("text", "") for p in content if isinstance(p, dict) and p.get("type") == "text"]
            return "\n".join(texts) if texts else None
        return None
    except (KeyError, IndexError):
        return None


# --- Preview Gallery ---

def build_preview_html(image_dir: Path, title: str = "Generated Images") -> str:
    """Build an HTML preview gallery for images in a directory."""
    images = sorted(image_dir.glob("*.png"))
    if not images:
        return "<html><body><p>No images found.</p></body></html>"

    cards = []
    for img in images:
        b64 = base64.b64encode(img.read_bytes()).decode()
        cards.append(f"""
        <div class="card" onclick="this.classList.toggle('selected')">
            <img src="data:image/png;base64,{b64}" alt="{img.name}">
            <div class="name">{img.name}</div>
            <div class="size">{img.stat().st_size / 1024:.0f} KB</div>
        </div>""")

    return f"""<!DOCTYPE html>
<html><head>
<title>{title}</title>
<style>
body {{ font-family: system-ui; background: #1a1a1a; color: #fff; padding: 20px; }}
h1 {{ text-align: center; font-weight: 300; }}
.controls {{ text-align: center; margin: 20px 0; }}
.controls button {{ padding: 8px 16px; margin: 0 4px; border: 1px solid #555; background: #333; color: #fff; border-radius: 6px; cursor: pointer; }}
.controls button.active {{ background: #4a9eff; border-color: #4a9eff; }}
.grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }}
.card {{ border: 2px solid transparent; border-radius: 12px; overflow: hidden; cursor: pointer; transition: all 0.2s; background: #222; }}
.card:hover {{ border-color: #555; }}
.card.selected {{ border-color: #4a9eff; box-shadow: 0 0 20px rgba(74,158,255,0.3); }}
.card img {{ width: 100%; display: block; }}
.name {{ padding: 8px 12px 2px; font-size: 14px; font-weight: 500; }}
.size {{ padding: 2px 12px 8px; font-size: 12px; color: #888; }}
.bg-checker {{ background-image: repeating-conic-gradient(#808080 0% 25%, transparent 0% 50%); background-size: 20px 20px; }}
.bg-white .card img {{ background: #fff; }}
.bg-dark .card img {{ background: #000; }}
</style>
</head><body>
<h1>{title}</h1>
<div class="controls">
    <button onclick="setbg('')" class="active">Default</button>
    <button onclick="setbg('bg-checker')">Checker</button>
    <button onclick="setbg('bg-white')">White</button>
    <button onclick="setbg('bg-dark')">Dark</button>
</div>
<div class="grid" id="grid">{"".join(cards)}</div>
<script>
function setbg(c) {{
    const g = document.getElementById('grid');
    g.className = 'grid ' + c;
    document.querySelectorAll('.controls button').forEach(b => b.classList.remove('active'));
    event.target.classList.add('active');
}}
</script>
</body></html>"""

License (Apache-2.0)

View full license text
Licensed under Apache-2.0