claude-code-templates 1.21.13 → 1.21.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +8 -2
- package/.claude-plugin/marketplace.json +251 -0
- package/.github/workflows/component-security-validation.yml +129 -0
- package/README.md +13 -6
- package/cli-tool/README.md +56 -0
- package/cli-tool/bin/create-claude-config.js +2 -0
- package/cli-tool/components/.claude-plugin/marketplace.json +92 -0
- package/cli-tool/components/agents/development-tools/flutter-go-reviewer.md +163 -0
- package/cli-tool/components/agents/development-tools/unused-code-cleaner.md +194 -0
- package/cli-tool/components/commands/git-workflow/gemini-review.md +293 -0
- package/cli-tool/components/commands/testing/add-mutation-testing.md +2 -2
- package/cli-tool/components/commands/testing/add-property-based-testing.md +2 -2
- package/cli-tool/components/commands/testing/e2e-setup.md +2 -2
- package/cli-tool/components/commands/testing/generate-test-cases.md +2 -2
- package/cli-tool/components/commands/testing/generate-tests.md +8 -3
- package/cli-tool/components/commands/testing/setup-comprehensive-testing.md +2 -2
- package/cli-tool/components/commands/testing/setup-load-testing.md +2 -2
- package/cli-tool/components/commands/testing/setup-visual-testing.md +2 -2
- package/cli-tool/components/commands/testing/test-automation-orchestrator.md +2 -2
- package/cli-tool/components/commands/testing/test-changelog-automation.md +2 -2
- package/cli-tool/components/commands/testing/test-coverage.md +2 -2
- package/cli-tool/components/commands/testing/test-quality-analyzer.md +2 -2
- package/cli-tool/components/commands/testing/testing_plan_integration.md +2 -2
- package/cli-tool/components/commands/testing/write-tests.md +2 -2
- package/cli-tool/components/commands/utilities/ultra-think.md +10 -5
- package/cli-tool/components/hooks/git/validate-branch-name.json +1 -1
- package/cli-tool/components/mcps/devtools/chrome-devtools.json +9 -0
- package/cli-tool/components/mcps/devtools/grafana.json +15 -0
- package/cli-tool/components/mcps/devtools/pulumi.json +9 -0
- package/cli-tool/components/mcps/devtools/terraform.json +1 -1
- package/cli-tool/components/settings/statusline/context-monitor.py +1 -1
- package/cli-tool/components/skills/ANTHROPIC_ATTRIBUTION.md +81 -0
- package/cli-tool/components/skills/creative-design/algorithmic-art/LICENSE.txt +202 -0
- package/cli-tool/components/skills/creative-design/algorithmic-art/SKILL.md +405 -0
- package/cli-tool/components/skills/creative-design/algorithmic-art/templates/generator_template.js +223 -0
- package/cli-tool/components/skills/creative-design/algorithmic-art/templates/viewer.html +599 -0
- package/cli-tool/components/skills/creative-design/canvas-design/LICENSE.txt +202 -0
- package/cli-tool/components/skills/creative-design/canvas-design/SKILL.md +130 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/cli-tool/components/skills/creative-design/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/LICENSE.txt +202 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/SKILL.md +646 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/core/color_palettes.py +302 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/core/easing.py +230 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/core/frame_composer.py +469 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/core/gif_builder.py +246 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/core/typography.py +357 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/core/validators.py +264 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/core/visual_effects.py +494 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/requirements.txt +4 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/bounce.py +106 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/explode.py +331 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/fade.py +329 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/flip.py +291 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/kaleidoscope.py +211 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/morph.py +329 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/move.py +293 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/pulse.py +268 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/shake.py +127 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/slide.py +291 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/spin.py +269 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/wiggle.py +300 -0
- package/cli-tool/components/skills/creative-design/slack-gif-creator/templates/zoom.py +312 -0
- package/cli-tool/components/skills/creative-design/theme-factory/LICENSE.txt +202 -0
- package/cli-tool/components/skills/creative-design/theme-factory/SKILL.md +59 -0
- package/cli-tool/components/skills/creative-design/theme-factory/theme-showcase.pdf +0 -0
- package/cli-tool/components/skills/creative-design/theme-factory/themes/arctic-frost.md +19 -0
- package/cli-tool/components/skills/creative-design/theme-factory/themes/botanical-garden.md +19 -0
- package/cli-tool/components/skills/creative-design/theme-factory/themes/desert-rose.md +19 -0
- package/cli-tool/components/skills/creative-design/theme-factory/themes/forest-canopy.md +19 -0
- package/cli-tool/components/skills/creative-design/theme-factory/themes/golden-hour.md +19 -0
- package/cli-tool/components/skills/creative-design/theme-factory/themes/midnight-galaxy.md +19 -0
- package/cli-tool/components/skills/creative-design/theme-factory/themes/modern-minimalist.md +19 -0
- package/cli-tool/components/skills/creative-design/theme-factory/themes/ocean-depths.md +19 -0
- package/cli-tool/components/skills/creative-design/theme-factory/themes/sunset-boulevard.md +19 -0
- package/cli-tool/components/skills/creative-design/theme-factory/themes/tech-innovation.md +19 -0
- package/cli-tool/components/skills/development/artifacts-builder/LICENSE.txt +202 -0
- package/cli-tool/components/skills/development/artifacts-builder/SKILL.md +74 -0
- package/cli-tool/components/skills/development/artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/cli-tool/components/skills/development/artifacts-builder/scripts/init-artifact.sh +322 -0
- package/cli-tool/components/skills/development/artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/cli-tool/components/skills/development/git-commit-helper/SKILL.md +203 -0
- package/cli-tool/components/skills/development/mcp-builder/LICENSE.txt +202 -0
- package/cli-tool/components/skills/development/mcp-builder/SKILL.md +328 -0
- package/cli-tool/components/skills/development/mcp-builder/reference/evaluation.md +602 -0
- package/cli-tool/components/skills/development/mcp-builder/reference/mcp_best_practices.md +915 -0
- package/cli-tool/components/skills/development/mcp-builder/reference/node_mcp_server.md +916 -0
- package/cli-tool/components/skills/development/mcp-builder/reference/python_mcp_server.md +752 -0
- package/cli-tool/components/skills/development/mcp-builder/scripts/connections.py +151 -0
- package/cli-tool/components/skills/development/mcp-builder/scripts/evaluation.py +373 -0
- package/cli-tool/components/skills/development/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/cli-tool/components/skills/development/mcp-builder/scripts/requirements.txt +2 -0
- package/cli-tool/components/skills/development/skill-creator/LICENSE.txt +202 -0
- package/cli-tool/components/skills/development/skill-creator/SKILL.md +209 -0
- package/cli-tool/components/skills/development/skill-creator/scripts/init_skill.py +303 -0
- package/cli-tool/components/skills/development/skill-creator/scripts/package_skill.py +110 -0
- package/cli-tool/components/skills/development/skill-creator/scripts/quick_validate.py +65 -0
- package/cli-tool/components/skills/development/webapp-testing/LICENSE.txt +202 -0
- package/cli-tool/components/skills/development/webapp-testing/SKILL.md +96 -0
- package/cli-tool/components/skills/development/webapp-testing/examples/console_logging.py +35 -0
- package/cli-tool/components/skills/development/webapp-testing/examples/element_discovery.py +40 -0
- package/cli-tool/components/skills/development/webapp-testing/examples/static_html_automation.py +33 -0
- package/cli-tool/components/skills/development/webapp-testing/scripts/with_server.py +106 -0
- package/cli-tool/components/skills/document-processing/docx/LICENSE.txt +30 -0
- package/cli-tool/components/skills/document-processing/docx/SKILL.md +197 -0
- package/cli-tool/components/skills/document-processing/docx/docx-js.md +350 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/mce/mc.xsd +75 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/scripts/pack.py +159 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/scripts/unpack.py +29 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/scripts/validate.py +69 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/scripts/validation/__init__.py +15 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/scripts/validation/base.py +951 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/scripts/validation/docx.py +274 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/scripts/validation/pptx.py +315 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml/scripts/validation/redlining.py +279 -0
- package/cli-tool/components/skills/document-processing/docx/ooxml.md +610 -0
- package/cli-tool/components/skills/document-processing/docx/scripts/__init__.py +1 -0
- package/cli-tool/components/skills/document-processing/docx/scripts/document.py +1276 -0
- package/cli-tool/components/skills/document-processing/docx/scripts/templates/comments.xml +3 -0
- package/cli-tool/components/skills/document-processing/docx/scripts/templates/commentsExtended.xml +3 -0
- package/cli-tool/components/skills/document-processing/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/cli-tool/components/skills/document-processing/docx/scripts/templates/commentsIds.xml +3 -0
- package/cli-tool/components/skills/document-processing/docx/scripts/templates/people.xml +3 -0
- package/cli-tool/components/skills/document-processing/docx/scripts/utilities.py +374 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/LICENSE.txt +30 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/SKILL.md +294 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/forms.md +205 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/reference.md +612 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/scripts/check_bounding_boxes.py +70 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/scripts/check_bounding_boxes_test.py +226 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/scripts/check_fillable_fields.py +12 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/scripts/convert_pdf_to_images.py +35 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/scripts/create_validation_image.py +41 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/scripts/extract_form_field_info.py +152 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/scripts/fill_fillable_fields.py +114 -0
- package/cli-tool/components/skills/document-processing/pdf-anthropic/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/cli-tool/components/skills/document-processing/pdf-processing/FORMS.md +143 -0
- package/cli-tool/components/skills/document-processing/pdf-processing/SKILL.md +149 -0
- package/cli-tool/components/skills/document-processing/pdf-processing-pro/FORMS.md +610 -0
- package/cli-tool/components/skills/document-processing/pdf-processing-pro/OCR.md +137 -0
- package/cli-tool/components/skills/document-processing/pdf-processing-pro/SKILL.md +296 -0
- package/cli-tool/components/skills/document-processing/pdf-processing-pro/TABLES.md +626 -0
- package/cli-tool/components/skills/document-processing/pdf-processing-pro/scripts/analyze_form.py +307 -0
- package/cli-tool/components/skills/document-processing/pptx/LICENSE.txt +30 -0
- package/cli-tool/components/skills/document-processing/pptx/SKILL.md +484 -0
- package/cli-tool/components/skills/document-processing/pptx/html2pptx.md +625 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/mce/mc.xsd +75 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/scripts/pack.py +159 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/scripts/unpack.py +29 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/scripts/validate.py +69 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/scripts/validation/base.py +951 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/cli-tool/components/skills/document-processing/pptx/ooxml.md +427 -0
- package/cli-tool/components/skills/document-processing/pptx/scripts/html2pptx.js +979 -0
- package/cli-tool/components/skills/document-processing/pptx/scripts/inventory.py +1020 -0
- package/cli-tool/components/skills/document-processing/pptx/scripts/rearrange.py +231 -0
- package/cli-tool/components/skills/document-processing/pptx/scripts/replace.py +385 -0
- package/cli-tool/components/skills/document-processing/pptx/scripts/thumbnail.py +450 -0
- package/cli-tool/components/skills/document-processing/xlsx/LICENSE.txt +30 -0
- package/cli-tool/components/skills/document-processing/xlsx/SKILL.md +289 -0
- package/cli-tool/components/skills/document-processing/xlsx/recalc.py +178 -0
- package/cli-tool/components/skills/enterprise-communication/brand-guidelines/LICENSE.txt +202 -0
- package/cli-tool/components/skills/enterprise-communication/brand-guidelines/SKILL.md +73 -0
- package/cli-tool/components/skills/enterprise-communication/email-composer/SKILL.md +317 -0
- package/cli-tool/components/skills/enterprise-communication/excel-analysis/SKILL.md +247 -0
- package/cli-tool/components/skills/enterprise-communication/internal-comms/LICENSE.txt +202 -0
- package/cli-tool/components/skills/enterprise-communication/internal-comms/SKILL.md +32 -0
- package/cli-tool/components/skills/enterprise-communication/internal-comms/examples/3p-updates.md +47 -0
- package/cli-tool/components/skills/enterprise-communication/internal-comms/examples/company-newsletter.md +65 -0
- package/cli-tool/components/skills/enterprise-communication/internal-comms/examples/faq-answers.md +30 -0
- package/cli-tool/components/skills/enterprise-communication/internal-comms/examples/general-comms.md +16 -0
- package/cli-tool/package-lock.json +39 -16
- package/cli-tool/package.json +7 -2
- package/cli-tool/security-report.json +62361 -0
- package/cli-tool/src/analytics-web/chats_mobile.html +17 -16
- package/cli-tool/src/console-bridge.js +3 -3
- package/cli-tool/src/index.js +157 -10
- package/cli-tool/src/plugin-dashboard-web/app.js +806 -0
- package/cli-tool/src/plugin-dashboard-web/index.html +292 -0
- package/cli-tool/src/plugin-dashboard-web/styles.css +1781 -0
- package/cli-tool/src/plugin-dashboard.js +689 -0
- package/cli-tool/src/security-audit.js +164 -0
- package/cli-tool/src/validation/ARCHITECTURE.md +309 -0
- package/cli-tool/src/validation/BaseValidator.js +152 -0
- package/cli-tool/src/validation/README.md +543 -0
- package/cli-tool/src/validation/ValidationOrchestrator.js +305 -0
- package/cli-tool/src/validation/validators/IntegrityValidator.js +338 -0
- package/cli-tool/src/validation/validators/ProvenanceValidator.js +399 -0
- package/cli-tool/src/validation/validators/ReferenceValidator.js +373 -0
- package/cli-tool/src/validation/validators/SemanticValidator.js +449 -0
- package/cli-tool/src/validation/validators/StructuralValidator.js +376 -0
- package/docs/CLAUDE.md +363 -0
- package/docs/api/README.md +297 -0
- package/docs/api/package.json +7 -0
- package/docs/api/track-download-supabase.js +150 -0
- package/docs/blog/README.md +199 -0
- package/docs/blog/blog-articles.json +133 -0
- package/docs/blog/css/blog-controls.css +254 -0
- package/docs/blog/e2b-claude-code-sandbox/index.html +8 -0
- package/docs/blog/index.html +81 -124
- package/docs/blog/js/blog-loader.js +602 -0
- package/docs/blog/nextjs-vercel-claude-code-integration/index.html +8 -0
- package/docs/blog/supabase-claude-code-integration/index.html +8 -0
- package/docs/component.html +226 -48
- package/docs/components.json +61610 -604
- package/docs/css/blog.css +292 -0
- package/docs/css/component-page.css +840 -3
- package/docs/css/plugin-page.css +648 -0
- package/docs/css/styles.css +504 -1
- package/docs/css/trending.css +110 -6
- package/docs/download-stats.html +8 -0
- package/docs/index.html +48 -22
- package/docs/jobs.html +8 -0
- package/docs/js/cart-manager.js +21 -8
- package/docs/js/component-page.js +1013 -12
- package/docs/js/data-loader.js +11 -8
- package/docs/js/index-events.js +305 -53
- package/docs/js/plugin-page.js +390 -0
- package/docs/js/script.js +50 -3
- package/docs/js/search-functionality.js +19 -16
- package/docs/js/trending.js +55 -20
- package/docs/plugin.html +262 -0
- package/docs/sandbox-interface.html +8 -0
- package/docs/static/favicon/about.txt +6 -0
- package/docs/static/favicon/android-chrome-192x192.png +0 -0
- package/docs/static/favicon/android-chrome-512x512.png +0 -0
- package/docs/static/favicon/apple-touch-icon.png +0 -0
- package/docs/static/favicon/favicon-16x16.png +0 -0
- package/docs/static/favicon/favicon-32x32.png +0 -0
- package/docs/static/favicon/favicon.ico +0 -0
- package/docs/static/favicon/site.webmanifest +1 -0
- package/docs/trending-data.json +616 -579
- package/docs/trending.html +24 -3
- package/docs/vercel.json +12 -0
- package/docs/workflows.html +8 -0
- package/generate_components_json.py +386 -11
- package/package.json +1 -1
- package/sync-api.sh +50 -0
- package/vercel.json +10 -75
- package/ROADMAP.md +0 -278
- package/test_serpapi.py +0 -36
- /package/cli-tool/components/commands/svelte/{svelte:a11y.md → svelte-a11y.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:component.md → svelte-component.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:debug.md → svelte-debug.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:migrate.md → svelte-migrate.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:optimize.md → svelte-optimize.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:scaffold.md → svelte-scaffold.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:storybook-migrate.md → svelte-storybook-migrate.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:storybook-mock.md → svelte-storybook-mock.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:storybook-setup.md → svelte-storybook-setup.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:storybook-story.md → svelte-storybook-story.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:storybook-troubleshoot.md → svelte-storybook-troubleshoot.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:storybook.md → svelte-storybook.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:test-coverage.md → svelte-test-coverage.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:test-fix.md → svelte-test-fix.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:test-setup.md → svelte-test-setup.md} +0 -0
- /package/cli-tool/components/commands/svelte/{svelte:test.md → svelte-test.md} +0 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Frame Composer - Utilities for composing visual elements into frames.
|
|
4
|
+
|
|
5
|
+
Provides functions for drawing shapes, text, emojis, and compositing elements
|
|
6
|
+
together to create animation frames.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
10
|
+
import numpy as np
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_blank_frame(width: int, height: int, color: tuple[int, int, int] = (255, 255, 255)) -> Image.Image:
|
|
15
|
+
"""
|
|
16
|
+
Create a blank frame with solid color background.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
width: Frame width
|
|
20
|
+
height: Frame height
|
|
21
|
+
color: RGB color tuple (default: white)
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
PIL Image
|
|
25
|
+
"""
|
|
26
|
+
return Image.new('RGB', (width, height), color)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def draw_circle(frame: Image.Image, center: tuple[int, int], radius: int,
|
|
30
|
+
fill_color: Optional[tuple[int, int, int]] = None,
|
|
31
|
+
outline_color: Optional[tuple[int, int, int]] = None,
|
|
32
|
+
outline_width: int = 1) -> Image.Image:
|
|
33
|
+
"""
|
|
34
|
+
Draw a circle on a frame.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
frame: PIL Image to draw on
|
|
38
|
+
center: (x, y) center position
|
|
39
|
+
radius: Circle radius
|
|
40
|
+
fill_color: RGB fill color (None for no fill)
|
|
41
|
+
outline_color: RGB outline color (None for no outline)
|
|
42
|
+
outline_width: Outline width in pixels
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Modified frame
|
|
46
|
+
"""
|
|
47
|
+
draw = ImageDraw.Draw(frame)
|
|
48
|
+
x, y = center
|
|
49
|
+
bbox = [x - radius, y - radius, x + radius, y + radius]
|
|
50
|
+
draw.ellipse(bbox, fill=fill_color, outline=outline_color, width=outline_width)
|
|
51
|
+
return frame
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def draw_rectangle(frame: Image.Image, top_left: tuple[int, int], bottom_right: tuple[int, int],
|
|
55
|
+
fill_color: Optional[tuple[int, int, int]] = None,
|
|
56
|
+
outline_color: Optional[tuple[int, int, int]] = None,
|
|
57
|
+
outline_width: int = 1) -> Image.Image:
|
|
58
|
+
"""
|
|
59
|
+
Draw a rectangle on a frame.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
frame: PIL Image to draw on
|
|
63
|
+
top_left: (x, y) top-left corner
|
|
64
|
+
bottom_right: (x, y) bottom-right corner
|
|
65
|
+
fill_color: RGB fill color (None for no fill)
|
|
66
|
+
outline_color: RGB outline color (None for no outline)
|
|
67
|
+
outline_width: Outline width in pixels
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Modified frame
|
|
71
|
+
"""
|
|
72
|
+
draw = ImageDraw.Draw(frame)
|
|
73
|
+
draw.rectangle([top_left, bottom_right], fill=fill_color, outline=outline_color, width=outline_width)
|
|
74
|
+
return frame
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def draw_line(frame: Image.Image, start: tuple[int, int], end: tuple[int, int],
|
|
78
|
+
color: tuple[int, int, int] = (0, 0, 0), width: int = 2) -> Image.Image:
|
|
79
|
+
"""
|
|
80
|
+
Draw a line on a frame.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
frame: PIL Image to draw on
|
|
84
|
+
start: (x, y) start position
|
|
85
|
+
end: (x, y) end position
|
|
86
|
+
color: RGB line color
|
|
87
|
+
width: Line width in pixels
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Modified frame
|
|
91
|
+
"""
|
|
92
|
+
draw = ImageDraw.Draw(frame)
|
|
93
|
+
draw.line([start, end], fill=color, width=width)
|
|
94
|
+
return frame
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def draw_text(frame: Image.Image, text: str, position: tuple[int, int],
|
|
98
|
+
font_size: int = 40, color: tuple[int, int, int] = (0, 0, 0),
|
|
99
|
+
centered: bool = False) -> Image.Image:
|
|
100
|
+
"""
|
|
101
|
+
Draw text on a frame.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
frame: PIL Image to draw on
|
|
105
|
+
text: Text to draw
|
|
106
|
+
position: (x, y) position (top-left unless centered=True)
|
|
107
|
+
font_size: Font size in pixels
|
|
108
|
+
color: RGB text color
|
|
109
|
+
centered: If True, center text at position
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Modified frame
|
|
113
|
+
"""
|
|
114
|
+
draw = ImageDraw.Draw(frame)
|
|
115
|
+
|
|
116
|
+
# Try to use default font, fall back to basic if not available
|
|
117
|
+
try:
|
|
118
|
+
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", font_size)
|
|
119
|
+
except:
|
|
120
|
+
font = ImageFont.load_default()
|
|
121
|
+
|
|
122
|
+
if centered:
|
|
123
|
+
bbox = draw.textbbox((0, 0), text, font=font)
|
|
124
|
+
text_width = bbox[2] - bbox[0]
|
|
125
|
+
text_height = bbox[3] - bbox[1]
|
|
126
|
+
x = position[0] - text_width // 2
|
|
127
|
+
y = position[1] - text_height // 2
|
|
128
|
+
position = (x, y)
|
|
129
|
+
|
|
130
|
+
draw.text(position, text, fill=color, font=font)
|
|
131
|
+
return frame
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def draw_emoji(frame: Image.Image, emoji: str, position: tuple[int, int], size: int = 60) -> Image.Image:
|
|
135
|
+
"""
|
|
136
|
+
Draw emoji text on a frame (requires system emoji support).
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
frame: PIL Image to draw on
|
|
140
|
+
emoji: Emoji character(s)
|
|
141
|
+
position: (x, y) position
|
|
142
|
+
size: Emoji size in pixels
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Modified frame
|
|
146
|
+
"""
|
|
147
|
+
draw = ImageDraw.Draw(frame)
|
|
148
|
+
|
|
149
|
+
# Use Apple Color Emoji font on macOS
|
|
150
|
+
try:
|
|
151
|
+
font = ImageFont.truetype("/System/Library/Fonts/Apple Color Emoji.ttc", size)
|
|
152
|
+
except:
|
|
153
|
+
# Fallback to text-based emoji
|
|
154
|
+
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", size)
|
|
155
|
+
|
|
156
|
+
draw.text(position, emoji, font=font, embedded_color=True)
|
|
157
|
+
return frame
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def composite_layers(base: Image.Image, overlay: Image.Image,
|
|
161
|
+
position: tuple[int, int] = (0, 0), alpha: float = 1.0) -> Image.Image:
|
|
162
|
+
"""
|
|
163
|
+
Composite one image on top of another.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
base: Base image
|
|
167
|
+
overlay: Image to overlay on top
|
|
168
|
+
position: (x, y) position to place overlay
|
|
169
|
+
alpha: Opacity of overlay (0.0 = transparent, 1.0 = opaque)
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Composite image
|
|
173
|
+
"""
|
|
174
|
+
# Convert to RGBA for transparency support
|
|
175
|
+
base_rgba = base.convert('RGBA')
|
|
176
|
+
overlay_rgba = overlay.convert('RGBA')
|
|
177
|
+
|
|
178
|
+
# Apply alpha
|
|
179
|
+
if alpha < 1.0:
|
|
180
|
+
overlay_rgba = overlay_rgba.copy()
|
|
181
|
+
overlay_rgba.putalpha(int(255 * alpha))
|
|
182
|
+
|
|
183
|
+
# Paste overlay onto base
|
|
184
|
+
base_rgba.paste(overlay_rgba, position, overlay_rgba)
|
|
185
|
+
|
|
186
|
+
# Convert back to RGB
|
|
187
|
+
return base_rgba.convert('RGB')
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def draw_stick_figure(frame: Image.Image, position: tuple[int, int], scale: float = 1.0,
|
|
191
|
+
color: tuple[int, int, int] = (0, 0, 0), line_width: int = 3) -> Image.Image:
|
|
192
|
+
"""
|
|
193
|
+
Draw a simple stick figure.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
frame: PIL Image to draw on
|
|
197
|
+
position: (x, y) center position of head
|
|
198
|
+
scale: Size multiplier
|
|
199
|
+
color: RGB line color
|
|
200
|
+
line_width: Line width in pixels
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Modified frame
|
|
204
|
+
"""
|
|
205
|
+
draw = ImageDraw.Draw(frame)
|
|
206
|
+
x, y = position
|
|
207
|
+
|
|
208
|
+
# Scale dimensions
|
|
209
|
+
head_radius = int(15 * scale)
|
|
210
|
+
body_length = int(40 * scale)
|
|
211
|
+
arm_length = int(25 * scale)
|
|
212
|
+
leg_length = int(35 * scale)
|
|
213
|
+
leg_spread = int(15 * scale)
|
|
214
|
+
|
|
215
|
+
# Head
|
|
216
|
+
draw.ellipse([x - head_radius, y - head_radius, x + head_radius, y + head_radius],
|
|
217
|
+
outline=color, width=line_width)
|
|
218
|
+
|
|
219
|
+
# Body
|
|
220
|
+
body_start = y + head_radius
|
|
221
|
+
body_end = body_start + body_length
|
|
222
|
+
draw.line([(x, body_start), (x, body_end)], fill=color, width=line_width)
|
|
223
|
+
|
|
224
|
+
# Arms
|
|
225
|
+
arm_y = body_start + int(body_length * 0.3)
|
|
226
|
+
draw.line([(x - arm_length, arm_y), (x + arm_length, arm_y)], fill=color, width=line_width)
|
|
227
|
+
|
|
228
|
+
# Legs
|
|
229
|
+
draw.line([(x, body_end), (x - leg_spread, body_end + leg_length)], fill=color, width=line_width)
|
|
230
|
+
draw.line([(x, body_end), (x + leg_spread, body_end + leg_length)], fill=color, width=line_width)
|
|
231
|
+
|
|
232
|
+
return frame
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def create_gradient_background(width: int, height: int,
|
|
236
|
+
top_color: tuple[int, int, int],
|
|
237
|
+
bottom_color: tuple[int, int, int]) -> Image.Image:
|
|
238
|
+
"""
|
|
239
|
+
Create a vertical gradient background.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
width: Frame width
|
|
243
|
+
height: Frame height
|
|
244
|
+
top_color: RGB color at top
|
|
245
|
+
bottom_color: RGB color at bottom
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
PIL Image with gradient
|
|
249
|
+
"""
|
|
250
|
+
frame = Image.new('RGB', (width, height))
|
|
251
|
+
draw = ImageDraw.Draw(frame)
|
|
252
|
+
|
|
253
|
+
# Calculate color step for each row
|
|
254
|
+
r1, g1, b1 = top_color
|
|
255
|
+
r2, g2, b2 = bottom_color
|
|
256
|
+
|
|
257
|
+
for y in range(height):
|
|
258
|
+
# Interpolate color
|
|
259
|
+
ratio = y / height
|
|
260
|
+
r = int(r1 * (1 - ratio) + r2 * ratio)
|
|
261
|
+
g = int(g1 * (1 - ratio) + g2 * ratio)
|
|
262
|
+
b = int(b1 * (1 - ratio) + b2 * ratio)
|
|
263
|
+
|
|
264
|
+
# Draw horizontal line
|
|
265
|
+
draw.line([(0, y), (width, y)], fill=(r, g, b))
|
|
266
|
+
|
|
267
|
+
return frame
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def draw_emoji_enhanced(frame: Image.Image, emoji: str, position: tuple[int, int],
|
|
271
|
+
size: int = 60, shadow: bool = True,
|
|
272
|
+
shadow_offset: tuple[int, int] = (2, 2)) -> Image.Image:
|
|
273
|
+
"""
|
|
274
|
+
Draw emoji with optional shadow for better visual quality.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
frame: PIL Image to draw on
|
|
278
|
+
emoji: Emoji character(s)
|
|
279
|
+
position: (x, y) position
|
|
280
|
+
size: Emoji size in pixels (minimum 12)
|
|
281
|
+
shadow: Whether to add drop shadow
|
|
282
|
+
shadow_offset: Shadow offset
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
Modified frame
|
|
286
|
+
"""
|
|
287
|
+
draw = ImageDraw.Draw(frame)
|
|
288
|
+
|
|
289
|
+
# Ensure minimum size to avoid font rendering errors
|
|
290
|
+
size = max(12, size)
|
|
291
|
+
|
|
292
|
+
# Use Apple Color Emoji font on macOS
|
|
293
|
+
try:
|
|
294
|
+
font = ImageFont.truetype("/System/Library/Fonts/Apple Color Emoji.ttc", size)
|
|
295
|
+
except:
|
|
296
|
+
# Fallback to text-based emoji
|
|
297
|
+
try:
|
|
298
|
+
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", size)
|
|
299
|
+
except:
|
|
300
|
+
font = ImageFont.load_default()
|
|
301
|
+
|
|
302
|
+
# Draw shadow first if enabled
|
|
303
|
+
if shadow and size >= 20: # Only draw shadow for larger emojis
|
|
304
|
+
shadow_pos = (position[0] + shadow_offset[0], position[1] + shadow_offset[1])
|
|
305
|
+
# Draw semi-transparent shadow (simulated by drawing multiple times)
|
|
306
|
+
for offset in range(1, 3):
|
|
307
|
+
try:
|
|
308
|
+
draw.text((shadow_pos[0] + offset, shadow_pos[1] + offset),
|
|
309
|
+
emoji, font=font, embedded_color=True, fill=(0, 0, 0, 100))
|
|
310
|
+
except:
|
|
311
|
+
pass # Skip shadow if it fails
|
|
312
|
+
|
|
313
|
+
# Draw main emoji
|
|
314
|
+
try:
|
|
315
|
+
draw.text(position, emoji, font=font, embedded_color=True)
|
|
316
|
+
except:
|
|
317
|
+
# Fallback to basic drawing if embedded color fails
|
|
318
|
+
draw.text(position, emoji, font=font, fill=(0, 0, 0))
|
|
319
|
+
|
|
320
|
+
return frame
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def draw_circle_with_shadow(frame: Image.Image, center: tuple[int, int], radius: int,
|
|
324
|
+
fill_color: tuple[int, int, int],
|
|
325
|
+
shadow_offset: tuple[int, int] = (3, 3),
|
|
326
|
+
shadow_color: tuple[int, int, int] = (0, 0, 0)) -> Image.Image:
|
|
327
|
+
"""
|
|
328
|
+
Draw a circle with drop shadow.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
frame: PIL Image to draw on
|
|
332
|
+
center: (x, y) center position
|
|
333
|
+
radius: Circle radius
|
|
334
|
+
fill_color: RGB fill color
|
|
335
|
+
shadow_offset: (x, y) shadow offset
|
|
336
|
+
shadow_color: RGB shadow color
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Modified frame
|
|
340
|
+
"""
|
|
341
|
+
draw = ImageDraw.Draw(frame)
|
|
342
|
+
x, y = center
|
|
343
|
+
|
|
344
|
+
# Draw shadow
|
|
345
|
+
shadow_center = (x + shadow_offset[0], y + shadow_offset[1])
|
|
346
|
+
shadow_bbox = [
|
|
347
|
+
shadow_center[0] - radius,
|
|
348
|
+
shadow_center[1] - radius,
|
|
349
|
+
shadow_center[0] + radius,
|
|
350
|
+
shadow_center[1] + radius
|
|
351
|
+
]
|
|
352
|
+
draw.ellipse(shadow_bbox, fill=shadow_color)
|
|
353
|
+
|
|
354
|
+
# Draw main circle
|
|
355
|
+
bbox = [x - radius, y - radius, x + radius, y + radius]
|
|
356
|
+
draw.ellipse(bbox, fill=fill_color)
|
|
357
|
+
|
|
358
|
+
return frame
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def draw_rounded_rectangle(frame: Image.Image, top_left: tuple[int, int],
|
|
362
|
+
bottom_right: tuple[int, int], radius: int,
|
|
363
|
+
fill_color: Optional[tuple[int, int, int]] = None,
|
|
364
|
+
outline_color: Optional[tuple[int, int, int]] = None,
|
|
365
|
+
outline_width: int = 1) -> Image.Image:
|
|
366
|
+
"""
|
|
367
|
+
Draw a rectangle with rounded corners.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
frame: PIL Image to draw on
|
|
371
|
+
top_left: (x, y) top-left corner
|
|
372
|
+
bottom_right: (x, y) bottom-right corner
|
|
373
|
+
radius: Corner radius
|
|
374
|
+
fill_color: RGB fill color (None for no fill)
|
|
375
|
+
outline_color: RGB outline color (None for no outline)
|
|
376
|
+
outline_width: Outline width
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
Modified frame
|
|
380
|
+
"""
|
|
381
|
+
draw = ImageDraw.Draw(frame)
|
|
382
|
+
x1, y1 = top_left
|
|
383
|
+
x2, y2 = bottom_right
|
|
384
|
+
|
|
385
|
+
# Draw rounded rectangle using PIL's built-in method
|
|
386
|
+
draw.rounded_rectangle([x1, y1, x2, y2], radius=radius,
|
|
387
|
+
fill=fill_color, outline=outline_color, width=outline_width)
|
|
388
|
+
|
|
389
|
+
return frame
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def add_vignette(frame: Image.Image, strength: float = 0.5) -> Image.Image:
|
|
393
|
+
"""
|
|
394
|
+
Add a vignette effect (darkened edges) to frame.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
frame: PIL Image
|
|
398
|
+
strength: Vignette strength (0.0-1.0)
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Frame with vignette
|
|
402
|
+
"""
|
|
403
|
+
width, height = frame.size
|
|
404
|
+
|
|
405
|
+
# Create radial gradient mask
|
|
406
|
+
center_x, center_y = width // 2, height // 2
|
|
407
|
+
max_dist = ((width / 2) ** 2 + (height / 2) ** 2) ** 0.5
|
|
408
|
+
|
|
409
|
+
# Create overlay
|
|
410
|
+
overlay = Image.new('RGB', (width, height), (0, 0, 0))
|
|
411
|
+
pixels = overlay.load()
|
|
412
|
+
|
|
413
|
+
for y in range(height):
|
|
414
|
+
for x in range(width):
|
|
415
|
+
# Calculate distance from center
|
|
416
|
+
dx = x - center_x
|
|
417
|
+
dy = y - center_y
|
|
418
|
+
dist = (dx ** 2 + dy ** 2) ** 0.5
|
|
419
|
+
|
|
420
|
+
# Calculate vignette value
|
|
421
|
+
vignette = min(1, (dist / max_dist) * strength)
|
|
422
|
+
value = int(255 * (1 - vignette))
|
|
423
|
+
pixels[x, y] = (value, value, value)
|
|
424
|
+
|
|
425
|
+
# Blend with original using multiply
|
|
426
|
+
frame_array = np.array(frame, dtype=np.float32) / 255
|
|
427
|
+
overlay_array = np.array(overlay, dtype=np.float32) / 255
|
|
428
|
+
|
|
429
|
+
result = frame_array * overlay_array
|
|
430
|
+
result = (result * 255).astype(np.uint8)
|
|
431
|
+
|
|
432
|
+
return Image.fromarray(result)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def draw_star(frame: Image.Image, center: tuple[int, int], size: int,
|
|
436
|
+
fill_color: tuple[int, int, int],
|
|
437
|
+
outline_color: Optional[tuple[int, int, int]] = None,
|
|
438
|
+
outline_width: int = 1) -> Image.Image:
|
|
439
|
+
"""
|
|
440
|
+
Draw a 5-pointed star.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
frame: PIL Image to draw on
|
|
444
|
+
center: (x, y) center position
|
|
445
|
+
size: Star size (outer radius)
|
|
446
|
+
fill_color: RGB fill color
|
|
447
|
+
outline_color: RGB outline color (None for no outline)
|
|
448
|
+
outline_width: Outline width
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
Modified frame
|
|
452
|
+
"""
|
|
453
|
+
import math
|
|
454
|
+
draw = ImageDraw.Draw(frame)
|
|
455
|
+
x, y = center
|
|
456
|
+
|
|
457
|
+
# Calculate star points
|
|
458
|
+
points = []
|
|
459
|
+
for i in range(10):
|
|
460
|
+
angle = (i * 36 - 90) * math.pi / 180 # 36 degrees per point, start at top
|
|
461
|
+
radius = size if i % 2 == 0 else size * 0.4 # Alternate between outer and inner
|
|
462
|
+
px = x + radius * math.cos(angle)
|
|
463
|
+
py = y + radius * math.sin(angle)
|
|
464
|
+
points.append((px, py))
|
|
465
|
+
|
|
466
|
+
# Draw star
|
|
467
|
+
draw.polygon(points, fill=fill_color, outline=outline_color, width=outline_width)
|
|
468
|
+
|
|
469
|
+
return frame
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
GIF Builder - Core module for assembling frames into GIFs optimized for Slack.
|
|
4
|
+
|
|
5
|
+
This module provides the main interface for creating GIFs from programmatically
|
|
6
|
+
generated frames, with automatic optimization for Slack's requirements.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
import imageio.v3 as imageio
|
|
12
|
+
from PIL import Image
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GIFBuilder:
|
|
17
|
+
"""Builder for creating optimized GIFs from frames."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, width: int = 480, height: int = 480, fps: int = 15):
|
|
20
|
+
"""
|
|
21
|
+
Initialize GIF builder.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
width: Frame width in pixels
|
|
25
|
+
height: Frame height in pixels
|
|
26
|
+
fps: Frames per second
|
|
27
|
+
"""
|
|
28
|
+
self.width = width
|
|
29
|
+
self.height = height
|
|
30
|
+
self.fps = fps
|
|
31
|
+
self.frames: list[np.ndarray] = []
|
|
32
|
+
|
|
33
|
+
def add_frame(self, frame: np.ndarray | Image.Image):
|
|
34
|
+
"""
|
|
35
|
+
Add a frame to the GIF.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
frame: Frame as numpy array or PIL Image (will be converted to RGB)
|
|
39
|
+
"""
|
|
40
|
+
if isinstance(frame, Image.Image):
|
|
41
|
+
frame = np.array(frame.convert('RGB'))
|
|
42
|
+
|
|
43
|
+
# Ensure frame is correct size
|
|
44
|
+
if frame.shape[:2] != (self.height, self.width):
|
|
45
|
+
pil_frame = Image.fromarray(frame)
|
|
46
|
+
pil_frame = pil_frame.resize((self.width, self.height), Image.Resampling.LANCZOS)
|
|
47
|
+
frame = np.array(pil_frame)
|
|
48
|
+
|
|
49
|
+
self.frames.append(frame)
|
|
50
|
+
|
|
51
|
+
def add_frames(self, frames: list[np.ndarray | Image.Image]):
|
|
52
|
+
"""Add multiple frames at once."""
|
|
53
|
+
for frame in frames:
|
|
54
|
+
self.add_frame(frame)
|
|
55
|
+
|
|
56
|
+
def optimize_colors(self, num_colors: int = 128, use_global_palette: bool = True) -> list[np.ndarray]:
|
|
57
|
+
"""
|
|
58
|
+
Reduce colors in all frames using quantization.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
num_colors: Target number of colors (8-256)
|
|
62
|
+
use_global_palette: Use a single palette for all frames (better compression)
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
List of color-optimized frames
|
|
66
|
+
"""
|
|
67
|
+
optimized = []
|
|
68
|
+
|
|
69
|
+
if use_global_palette and len(self.frames) > 1:
|
|
70
|
+
# Create a global palette from all frames
|
|
71
|
+
# Sample frames to build palette
|
|
72
|
+
sample_size = min(5, len(self.frames))
|
|
73
|
+
sample_indices = [int(i * len(self.frames) / sample_size) for i in range(sample_size)]
|
|
74
|
+
sample_frames = [self.frames[i] for i in sample_indices]
|
|
75
|
+
|
|
76
|
+
# Combine sample frames into a single image for palette generation
|
|
77
|
+
# Flatten each frame to get all pixels, then stack them
|
|
78
|
+
all_pixels = np.vstack([f.reshape(-1, 3) for f in sample_frames]) # (total_pixels, 3)
|
|
79
|
+
|
|
80
|
+
# Create a properly-shaped RGB image from the pixel data
|
|
81
|
+
# We'll make a roughly square image from all the pixels
|
|
82
|
+
total_pixels = len(all_pixels)
|
|
83
|
+
width = min(512, int(np.sqrt(total_pixels))) # Reasonable width, max 512
|
|
84
|
+
height = (total_pixels + width - 1) // width # Ceiling division
|
|
85
|
+
|
|
86
|
+
# Pad if necessary to fill the rectangle
|
|
87
|
+
pixels_needed = width * height
|
|
88
|
+
if pixels_needed > total_pixels:
|
|
89
|
+
padding = np.zeros((pixels_needed - total_pixels, 3), dtype=np.uint8)
|
|
90
|
+
all_pixels = np.vstack([all_pixels, padding])
|
|
91
|
+
|
|
92
|
+
# Reshape to proper RGB image format (H, W, 3)
|
|
93
|
+
img_array = all_pixels[:pixels_needed].reshape(height, width, 3).astype(np.uint8)
|
|
94
|
+
combined_img = Image.fromarray(img_array, mode='RGB')
|
|
95
|
+
|
|
96
|
+
# Generate global palette
|
|
97
|
+
global_palette = combined_img.quantize(colors=num_colors, method=2)
|
|
98
|
+
|
|
99
|
+
# Apply global palette to all frames
|
|
100
|
+
for frame in self.frames:
|
|
101
|
+
pil_frame = Image.fromarray(frame)
|
|
102
|
+
quantized = pil_frame.quantize(palette=global_palette, dither=1)
|
|
103
|
+
optimized.append(np.array(quantized.convert('RGB')))
|
|
104
|
+
else:
|
|
105
|
+
# Use per-frame quantization
|
|
106
|
+
for frame in self.frames:
|
|
107
|
+
pil_frame = Image.fromarray(frame)
|
|
108
|
+
quantized = pil_frame.quantize(colors=num_colors, method=2, dither=1)
|
|
109
|
+
optimized.append(np.array(quantized.convert('RGB')))
|
|
110
|
+
|
|
111
|
+
return optimized
|
|
112
|
+
|
|
113
|
+
def deduplicate_frames(self, threshold: float = 0.995) -> int:
|
|
114
|
+
"""
|
|
115
|
+
Remove duplicate or near-duplicate consecutive frames.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
threshold: Similarity threshold (0.0-1.0). Higher = more strict (0.995 = very similar).
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Number of frames removed
|
|
122
|
+
"""
|
|
123
|
+
if len(self.frames) < 2:
|
|
124
|
+
return 0
|
|
125
|
+
|
|
126
|
+
deduplicated = [self.frames[0]]
|
|
127
|
+
removed_count = 0
|
|
128
|
+
|
|
129
|
+
for i in range(1, len(self.frames)):
|
|
130
|
+
# Compare with previous frame
|
|
131
|
+
prev_frame = np.array(deduplicated[-1], dtype=np.float32)
|
|
132
|
+
curr_frame = np.array(self.frames[i], dtype=np.float32)
|
|
133
|
+
|
|
134
|
+
# Calculate similarity (normalized)
|
|
135
|
+
diff = np.abs(prev_frame - curr_frame)
|
|
136
|
+
similarity = 1.0 - (np.mean(diff) / 255.0)
|
|
137
|
+
|
|
138
|
+
# Keep frame if sufficiently different
|
|
139
|
+
# High threshold (0.995) means only remove truly identical frames
|
|
140
|
+
if similarity < threshold:
|
|
141
|
+
deduplicated.append(self.frames[i])
|
|
142
|
+
else:
|
|
143
|
+
removed_count += 1
|
|
144
|
+
|
|
145
|
+
self.frames = deduplicated
|
|
146
|
+
return removed_count
|
|
147
|
+
|
|
148
|
+
def save(self, output_path: str | Path, num_colors: int = 128,
|
|
149
|
+
optimize_for_emoji: bool = False, remove_duplicates: bool = True) -> dict:
|
|
150
|
+
"""
|
|
151
|
+
Save frames as optimized GIF for Slack.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
output_path: Where to save the GIF
|
|
155
|
+
num_colors: Number of colors to use (fewer = smaller file)
|
|
156
|
+
optimize_for_emoji: If True, optimize for <64KB emoji size
|
|
157
|
+
remove_duplicates: Remove duplicate consecutive frames
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Dictionary with file info (path, size, dimensions, frame_count)
|
|
161
|
+
"""
|
|
162
|
+
if not self.frames:
|
|
163
|
+
raise ValueError("No frames to save. Add frames with add_frame() first.")
|
|
164
|
+
|
|
165
|
+
output_path = Path(output_path)
|
|
166
|
+
original_frame_count = len(self.frames)
|
|
167
|
+
|
|
168
|
+
# Remove duplicate frames to reduce file size
|
|
169
|
+
if remove_duplicates:
|
|
170
|
+
removed = self.deduplicate_frames(threshold=0.98)
|
|
171
|
+
if removed > 0:
|
|
172
|
+
print(f" Removed {removed} duplicate frames")
|
|
173
|
+
|
|
174
|
+
# Optimize for emoji if requested
|
|
175
|
+
if optimize_for_emoji:
|
|
176
|
+
if self.width > 128 or self.height > 128:
|
|
177
|
+
print(f" Resizing from {self.width}x{self.height} to 128x128 for emoji")
|
|
178
|
+
self.width = 128
|
|
179
|
+
self.height = 128
|
|
180
|
+
# Resize all frames
|
|
181
|
+
resized_frames = []
|
|
182
|
+
for frame in self.frames:
|
|
183
|
+
pil_frame = Image.fromarray(frame)
|
|
184
|
+
pil_frame = pil_frame.resize((128, 128), Image.Resampling.LANCZOS)
|
|
185
|
+
resized_frames.append(np.array(pil_frame))
|
|
186
|
+
self.frames = resized_frames
|
|
187
|
+
num_colors = min(num_colors, 48) # More aggressive color limit for emoji
|
|
188
|
+
|
|
189
|
+
# More aggressive FPS reduction for emoji
|
|
190
|
+
if len(self.frames) > 12:
|
|
191
|
+
print(f" Reducing frames from {len(self.frames)} to ~12 for emoji size")
|
|
192
|
+
# Keep every nth frame to get close to 12 frames
|
|
193
|
+
keep_every = max(1, len(self.frames) // 12)
|
|
194
|
+
self.frames = [self.frames[i] for i in range(0, len(self.frames), keep_every)]
|
|
195
|
+
|
|
196
|
+
# Optimize colors with global palette
|
|
197
|
+
optimized_frames = self.optimize_colors(num_colors, use_global_palette=True)
|
|
198
|
+
|
|
199
|
+
# Calculate frame duration in milliseconds
|
|
200
|
+
frame_duration = 1000 / self.fps
|
|
201
|
+
|
|
202
|
+
# Save GIF
|
|
203
|
+
imageio.imwrite(
|
|
204
|
+
output_path,
|
|
205
|
+
optimized_frames,
|
|
206
|
+
duration=frame_duration,
|
|
207
|
+
loop=0 # Infinite loop
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Get file info
|
|
211
|
+
file_size_kb = output_path.stat().st_size / 1024
|
|
212
|
+
file_size_mb = file_size_kb / 1024
|
|
213
|
+
|
|
214
|
+
info = {
|
|
215
|
+
'path': str(output_path),
|
|
216
|
+
'size_kb': file_size_kb,
|
|
217
|
+
'size_mb': file_size_mb,
|
|
218
|
+
'dimensions': f'{self.width}x{self.height}',
|
|
219
|
+
'frame_count': len(optimized_frames),
|
|
220
|
+
'fps': self.fps,
|
|
221
|
+
'duration_seconds': len(optimized_frames) / self.fps,
|
|
222
|
+
'colors': num_colors
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# Print info
|
|
226
|
+
print(f"\n✓ GIF created successfully!")
|
|
227
|
+
print(f" Path: {output_path}")
|
|
228
|
+
print(f" Size: {file_size_kb:.1f} KB ({file_size_mb:.2f} MB)")
|
|
229
|
+
print(f" Dimensions: {self.width}x{self.height}")
|
|
230
|
+
print(f" Frames: {len(optimized_frames)} @ {self.fps} fps")
|
|
231
|
+
print(f" Duration: {info['duration_seconds']:.1f}s")
|
|
232
|
+
print(f" Colors: {num_colors}")
|
|
233
|
+
|
|
234
|
+
# Warnings
|
|
235
|
+
if optimize_for_emoji and file_size_kb > 64:
|
|
236
|
+
print(f"\n⚠️ WARNING: Emoji file size ({file_size_kb:.1f} KB) exceeds 64 KB limit")
|
|
237
|
+
print(" Try: fewer frames, fewer colors, or simpler design")
|
|
238
|
+
elif not optimize_for_emoji and file_size_kb > 2048:
|
|
239
|
+
print(f"\n⚠️ WARNING: File size ({file_size_kb:.1f} KB) is large for Slack")
|
|
240
|
+
print(" Try: fewer frames, smaller dimensions, or fewer colors")
|
|
241
|
+
|
|
242
|
+
return info
|
|
243
|
+
|
|
244
|
+
def clear(self):
|
|
245
|
+
"""Clear all frames (useful for creating multiple GIFs)."""
|
|
246
|
+
self.frames = []
|