mirror of
https://github.com/iamzifei/xiaohongshu-images-skill.git
synced 2026-09-19 05:32:16 +08:00
Initial commit: Xiaohongshu Images Skill
- Add SKILL.md with complete skill definition - Add default HTML/CSS prompt template for styled article pages - Add Gemini API image generation script for cover images - Add Playwright screenshot script with 3:4 ratio capture - Add .env.example and .gitignore configuration Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# Xiaohongshu Images Skill - Environment Configuration
|
||||
#
|
||||
# Instructions:
|
||||
# 1. Copy this file to .env
|
||||
# 2. Fill in your API key
|
||||
# 3. The .env file is gitignored and will not be committed to version control
|
||||
#
|
||||
# ============================================================================
|
||||
|
||||
# ============================================================================
|
||||
# Image Generation Configuration
|
||||
# ============================================================================
|
||||
|
||||
# Google Gemini API Key
|
||||
# Obtain from: https://aistudio.google.com/app/apikey
|
||||
# Used for generating cover images with AI
|
||||
GEMINI_API_KEY=your_gemini_api_key_here
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
# Environment variables (contains API keys, should not be committed)
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Output directory (generated content)
|
||||
output/
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
.venv/
|
||||
|
||||
# IDE and editors
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
._*
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Playwright
|
||||
.playwright-mcp/
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
*.log
|
||||
|
||||
# Package artifacts
|
||||
*.skill
|
||||
@@ -0,0 +1,208 @@
|
||||
# Xiaohongshu Images Skill
|
||||
|
||||
A Claude Code skill that transforms markdown, HTML, or text content into beautifully styled HTML pages with AI-generated cover images, then captures them as sequential screenshots at 3:4 ratio for Xiaohongshu posting.
|
||||
|
||||
## Features
|
||||
|
||||
- **Content Processing**: Accepts markdown, HTML, or plain text content
|
||||
- **AI Cover Images**: Generates editorial-style cover illustrations using Google Gemini
|
||||
- **Styled HTML Output**: Creates beautifully formatted HTML pages with modern typography
|
||||
- **Screenshot Capture**: Takes sequential 3:4 ratio screenshots optimized for Xiaohongshu
|
||||
- **Smart Text Boundaries**: Ensures no text is cut off in screenshots
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.8 or higher
|
||||
- Claude Code CLI
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Clone or copy this skill to your Claude skills directory:**
|
||||
|
||||
```bash
|
||||
# Copy to global skills
|
||||
cp -r xiaohongshu-images-skill ~/.claude/skills/
|
||||
|
||||
# Or symlink for development
|
||||
ln -s /path/to/xiaohongshu-images-skill ~/.claude/skills/xiaohongshu-images-skill
|
||||
```
|
||||
|
||||
2. **Install Python dependencies:**
|
||||
|
||||
```bash
|
||||
pip install python-dotenv playwright
|
||||
playwright install chromium
|
||||
```
|
||||
|
||||
3. **Configure environment variables:**
|
||||
|
||||
```bash
|
||||
cd ~/.claude/skills/xiaohongshu-images-skill
|
||||
cp .env.example .env
|
||||
# Edit .env and add your GEMINI_API_KEY
|
||||
```
|
||||
|
||||
4. **Get your Gemini API Key:**
|
||||
|
||||
Visit [Google AI Studio](https://aistudio.google.com/app/apikey) to obtain your API key.
|
||||
|
||||
## Usage
|
||||
|
||||
### Via Claude Code
|
||||
|
||||
Invoke the skill in Claude Code:
|
||||
|
||||
```
|
||||
/xiaohongshu-images
|
||||
```
|
||||
|
||||
Then provide your content:
|
||||
- Paste markdown/HTML content directly
|
||||
- Provide a file path: `/path/to/article.md`
|
||||
- Provide a URL to fetch content from
|
||||
|
||||
### Example
|
||||
|
||||
```markdown
|
||||
/xiaohongshu-images
|
||||
|
||||
# My Article Title
|
||||
|
||||
This is the introduction paragraph explaining the topic...
|
||||
|
||||
## Section 1
|
||||
|
||||
Content for section 1 with detailed explanation...
|
||||
|
||||
## Section 2
|
||||
|
||||
More content here with examples...
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
The skill generates:
|
||||
- `output/<date-title>/index.html` - Styled HTML page
|
||||
- `output/<date-title>/images/cover.png` - AI-generated cover image
|
||||
- `output/<date-title>/screenshots/01.png, 02.png, ...` - Sequential screenshots
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
xiaohongshu-images-skill/
|
||||
├── SKILL.md # Main skill definition
|
||||
├── README.md # This file
|
||||
├── prompts/
|
||||
│ └── default.md # Default HTML/CSS styling prompt
|
||||
├── scripts/
|
||||
│ ├── generate_images.py # Gemini image generation
|
||||
│ └── screenshot.py # Screenshot capture
|
||||
├── output/ # Generated outputs (gitignored)
|
||||
├── .env # Environment variables (gitignored)
|
||||
├── .env.example # Environment template
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### Custom Prompt Templates
|
||||
|
||||
Create custom styling templates in the `prompts/` directory:
|
||||
|
||||
1. Create a new `.md` file (e.g., `prompts/minimal.md`)
|
||||
2. Define your HTML/CSS specifications
|
||||
3. Invoke with: "Use the minimal template for this article"
|
||||
|
||||
### Modifying Styles
|
||||
|
||||
Edit `prompts/default.md` to customize:
|
||||
- Card dimensions and colors
|
||||
- Font families and sizes
|
||||
- Typography hierarchy
|
||||
- Code block styling
|
||||
- Responsive breakpoints
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Required |
|
||||
|----------|-------------|----------|
|
||||
| `GEMINI_API_KEY` | Google Gemini API key for image generation | Yes |
|
||||
|
||||
### Screenshot Settings
|
||||
|
||||
Default screenshot dimensions (3:4 ratio for Xiaohongshu):
|
||||
- Width: 1080px
|
||||
- Height: 1440px
|
||||
- Scale factor: 2x (Retina quality)
|
||||
|
||||
To modify, edit `scripts/screenshot.py`:
|
||||
```python
|
||||
SCREENSHOT_WIDTH = 1080
|
||||
SCREENSHOT_HEIGHT = 1440
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
### generate_images.py
|
||||
|
||||
Generates cover images using Google Gemini API.
|
||||
|
||||
```bash
|
||||
python scripts/generate_images.py output/<folder>/prompts.json
|
||||
```
|
||||
|
||||
JSON format:
|
||||
```json
|
||||
{
|
||||
"theme": "Article theme for cover image generation"
|
||||
}
|
||||
```
|
||||
|
||||
### screenshot.py
|
||||
|
||||
Captures sequential screenshots of HTML pages.
|
||||
|
||||
```bash
|
||||
python scripts/screenshot.py output/<folder>/index.html
|
||||
```
|
||||
|
||||
Features:
|
||||
- Automatic page scrolling
|
||||
- Smart text boundary detection
|
||||
- No text cut-off at boundaries
|
||||
- 3:4 aspect ratio output
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gemini API Issues
|
||||
|
||||
- Verify your API key is correctly set in `.env`
|
||||
- Check API quotas at [Google AI Studio](https://aistudio.google.com/)
|
||||
- Ensure the API key has access to image generation models
|
||||
|
||||
### Screenshot Issues
|
||||
|
||||
- Install Playwright browsers: `playwright install chromium`
|
||||
- Check file paths are correct
|
||||
- Ensure HTML file is valid and accessible
|
||||
|
||||
### Font Loading
|
||||
|
||||
If fonts don't load in screenshots:
|
||||
- Increase wait time in `screenshot.py`
|
||||
- Check Google Fonts availability
|
||||
- Consider using local fonts
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See LICENSE file for details.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `chinese-viral-writer` - Chinese viral content creation
|
||||
- `wechat-article-formatter` - WeChat article formatting
|
||||
- `wechat-article-publisher` - WeChat publishing automation
|
||||
@@ -0,0 +1,267 @@
|
||||
---
|
||||
name: xiaohongshu-images
|
||||
description: Generate beautifully styled HTML pages from markdown/HTML/txt content with cover images using Gemini AI, then capture screenshots at 3:4 ratio for Xiaohongshu. Use when user wants to create styled article pages, generate article images, or prepare content for Xiaohongshu platform.
|
||||
---
|
||||
|
||||
# Xiaohongshu Images Skill
|
||||
|
||||
This skill transforms markdown, HTML, or text content into beautifully styled HTML pages with AI-generated cover images, then captures them as sequential screenshots at 3:4 ratio for Xiaohongshu posting.
|
||||
|
||||
## Overview
|
||||
|
||||
The skill performs the following workflow:
|
||||
|
||||
1. **Accept Content**: Receives markdown, HTML, or txt format content from the user
|
||||
2. **Load Prompt Template**: Reads the prompt template from `prompts/default.md` in this skill's directory
|
||||
3. **Generate Cover Image**: Uses Gemini API to generate a cover image based on the article content
|
||||
4. **Generate HTML**: Creates a beautifully styled HTML page following the prompt template specifications
|
||||
5. **Save Output**: Saves the HTML to `/output/<date-article-title>/index.html`
|
||||
6. **Capture Screenshots**: Takes sequential 3:4 ratio screenshots of the entire page without cutting text
|
||||
|
||||
## Usage
|
||||
|
||||
When the user invokes this skill, follow these steps:
|
||||
|
||||
### Step 1: Identify the Input
|
||||
|
||||
The user will provide one of the following:
|
||||
- A file path to a markdown, HTML, or txt file (e.g., `/path/to/article.md`)
|
||||
- Raw content directly in the conversation
|
||||
- A URL to fetch content from
|
||||
|
||||
If the input is unclear, ask the user to provide either a file path, URL, or paste the content directly.
|
||||
|
||||
### Step 2: Read the Prompt Template
|
||||
|
||||
Read the prompt template from this skill's directory:
|
||||
|
||||
```
|
||||
{{SKILL_DIR}}/prompts/default.md
|
||||
```
|
||||
|
||||
Use the Read tool to get the prompt template content. This template defines the HTML/CSS styling specifications.
|
||||
|
||||
### Step 3: Extract Article Title and Date
|
||||
|
||||
From the content, extract:
|
||||
- **Title**: The main heading (h1) or first significant title in the content
|
||||
- **Date**: Current date in YYYY-MM-DD format
|
||||
|
||||
Create the output folder name as: `<date>-<sanitized-title>`
|
||||
- Replace spaces with hyphens
|
||||
- Remove special characters
|
||||
- Keep it reasonably short (max 50 characters)
|
||||
|
||||
### Step 4: Generate Cover Image with Gemini
|
||||
|
||||
If the prompt template specifies image generation requirements (which it does by default):
|
||||
|
||||
1. **Read the environment variables** from `{{SKILL_DIR}}/.env` to get `GEMINI_API_KEY`
|
||||
2. **Analyze the article content** to extract the main theme
|
||||
3. **Generate image prompt** based on the template:
|
||||
- Style: Hand-drawn illustration similar to *The New Yorker* editorial cartoons
|
||||
- Content: Visual representation of the article's main theme
|
||||
- Dimensions: 600px × 350px (will be scaled to fit)
|
||||
|
||||
4. **Call Gemini API** using the generate_images.py script:
|
||||
|
||||
```bash
|
||||
cd {{SKILL_DIR}} && python scripts/generate_images.py output/<folder-name>/prompts.json
|
||||
```
|
||||
|
||||
Or use direct API call via curl:
|
||||
|
||||
```bash
|
||||
curl -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key=${GEMINI_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"contents": [{
|
||||
"parts": [{"text": "<image_prompt>"}]
|
||||
}],
|
||||
"generationConfig": {
|
||||
"responseModalities": ["TEXT", "IMAGE"]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
5. **Save the generated image** to `output/<folder-name>/images/cover.png`
|
||||
|
||||
### Step 5: Generate HTML
|
||||
|
||||
Using the prompt template and the user's content:
|
||||
|
||||
1. **Parse the content** to identify:
|
||||
- Title (h1)
|
||||
- Subtitles (h2-h6)
|
||||
- Paragraphs
|
||||
- Lists
|
||||
- Code blocks
|
||||
- Links
|
||||
- Emphasis/bold text
|
||||
- Blockquotes
|
||||
|
||||
2. **Generate complete HTML** following the template specifications:
|
||||
- Dark gradient background
|
||||
- 600px × 800px cream-colored card
|
||||
- Proper typography with Google Fonts (Noto Serif SC, Inter, JetBrains Mono)
|
||||
- Cover image at the top
|
||||
- All specified styling for text, links, lists, code blocks, etc.
|
||||
- Responsive design for mobile
|
||||
|
||||
3. **Important HTML Structure**:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Article Title</title>
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@700&family=Inter:wght@300;400;700;800&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* All CSS styles inline */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<img src="images/cover.png" class="cover-image" alt="Cover">
|
||||
<div class="content">
|
||||
<!-- Article content -->
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
4. **Save the HTML** to `output/<folder-name>/index.html`
|
||||
|
||||
### Step 6: Take Screenshots
|
||||
|
||||
After generating the HTML, capture sequential screenshots at 3:4 ratio (e.g., 1080×1440 pixels):
|
||||
|
||||
1. **Open the HTML page** using Playwright browser
|
||||
2. **Calculate screenshot sections**:
|
||||
- Screenshot height: 1440px (at 1080px width for 3:4 ratio)
|
||||
- Total page height / screenshot height = number of screenshots needed
|
||||
3. **For each screenshot**:
|
||||
- Ensure no text is cut off at boundaries
|
||||
- If text would be cut, move the boundary to before that line and leave whitespace
|
||||
- Use smart text detection to find safe cutting points
|
||||
4. **Save screenshots** to `output/<folder-name>/screenshots/`:
|
||||
- `01.png`, `02.png`, `03.png`, etc.
|
||||
|
||||
Use the screenshot script:
|
||||
|
||||
```bash
|
||||
cd {{SKILL_DIR}} && python scripts/screenshot.py output/<folder-name>/index.html
|
||||
```
|
||||
|
||||
### Step 7: Report Results
|
||||
|
||||
After completion, report to the user:
|
||||
- HTML file location
|
||||
- Number of screenshots generated
|
||||
- Screenshots folder location
|
||||
- Preview of the first screenshot (if possible)
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
{{SKILL_DIR}}/
|
||||
├── SKILL.md # This file
|
||||
├── prompts/
|
||||
│ └── default.md # Default HTML/CSS styling prompt
|
||||
├── scripts/
|
||||
│ ├── generate_images.py # Gemini image generation script
|
||||
│ └── screenshot.py # Screenshot capture script
|
||||
├── output/ # Generated outputs (gitignored)
|
||||
│ └── <date-title>/
|
||||
│ ├── index.html
|
||||
│ ├── images/
|
||||
│ │ └── cover.png
|
||||
│ └── screenshots/
|
||||
│ ├── 01.png
|
||||
│ ├── 02.png
|
||||
│ └── ...
|
||||
├── .env # Environment variables (gitignored)
|
||||
├── .env.example # Environment variable template
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Required environment variables in `.env`:
|
||||
|
||||
```
|
||||
GEMINI_API_KEY=your_gemini_api_key_here
|
||||
```
|
||||
|
||||
Get your API key from: https://aistudio.google.com/app/apikey
|
||||
|
||||
## Example Workflow
|
||||
|
||||
**User:** Create a styled article page from this markdown:
|
||||
|
||||
```markdown
|
||||
# My Article Title
|
||||
|
||||
This is the introduction paragraph...
|
||||
|
||||
## Section 1
|
||||
|
||||
Content for section 1...
|
||||
```
|
||||
|
||||
**Assistant Actions:**
|
||||
1. Read prompt template from `prompts/default.md`
|
||||
2. Extract title: "My Article Title"
|
||||
3. Create output folder: `output/2024-01-14-my-article-title/`
|
||||
4. Generate cover image using Gemini API based on article theme
|
||||
5. Generate styled HTML following template specifications
|
||||
6. Save to `output/2024-01-14-my-article-title/index.html`
|
||||
7. Open in browser and take 3:4 ratio screenshots
|
||||
8. Save screenshots to `output/2024-01-14-my-article-title/screenshots/`
|
||||
9. Report completion with file locations
|
||||
|
||||
## Custom Prompt Templates
|
||||
|
||||
Users can provide custom prompt templates by:
|
||||
1. Placing a `.md` file in the `prompts/` directory
|
||||
2. Specifying the template name when invoking the skill
|
||||
|
||||
Example: "Use the `xiaohongshu-style` template for this article"
|
||||
|
||||
## Error Handling
|
||||
|
||||
If the Gemini API call fails:
|
||||
1. Display the error message to the user
|
||||
2. Offer to retry or proceed without cover image
|
||||
3. If proceeding without image, use a placeholder or omit the cover
|
||||
|
||||
If screenshot capture fails:
|
||||
1. Verify the HTML file exists and is valid
|
||||
2. Check browser dependencies
|
||||
3. Report the specific error to the user
|
||||
|
||||
## Dependencies
|
||||
|
||||
This skill requires:
|
||||
- Python 3.8+
|
||||
- `python-dotenv` package
|
||||
- Playwright for screenshot capture (installed via pip: `pip install playwright && playwright install chromium`)
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
pip install python-dotenv playwright
|
||||
playwright install chromium
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The skill preserves all original content exactly as provided
|
||||
- No modifications, simplifications, or deletions to the content
|
||||
- The cover image is generated based on the article's main theme
|
||||
- Screenshots are optimized for Xiaohongshu's 3:4 aspect ratio
|
||||
- Text is never cut off in screenshots - boundaries are adjusted intelligently
|
||||
@@ -0,0 +1,241 @@
|
||||
# HTML/CSS Article Image Expert Prompt
|
||||
|
||||
You are a frontend development and web layout expert proficient in HTML/CSS.
|
||||
|
||||
## Task Objective
|
||||
|
||||
Please carefully read the article link or article content provided by the user, and generate a complete HTML page according to the following style specifications. The page should be presented as a cream-colored card on a dark background, with a modern feel and good reading experience.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overall Layout
|
||||
|
||||
### 1.1 Page Background
|
||||
|
||||
- **Dark gradient background**
|
||||
|
||||
```css
|
||||
background: linear-gradient(135deg, #1e1e2e 0%, #2d2b55 50%, #3e3a5f 100%);
|
||||
background-attachment: fixed;
|
||||
```
|
||||
|
||||
- **Layout method**: Use Flexbox to achieve vertical and horizontal centering
|
||||
|
||||
### 1.2 Main Container (Cream-colored Card)
|
||||
|
||||
- **Dimensions**: 600px × 800px
|
||||
- **Background color**: `#F9F9F6`
|
||||
- **border-radius**: 0px (card has no rounded corners, rectangular with right angles)
|
||||
- **3D shadow** (three layers):
|
||||
|
||||
```css
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.4),
|
||||
0 10px 30px rgba(0, 10, 20, 0.3),
|
||||
0 5px 15px rgba(0, 5, 15, 0.25);
|
||||
```
|
||||
|
||||
### 1.3 Content Area
|
||||
|
||||
- **Content area scope**: Cover image, title, and body text. Note: Cover is part of the content area and scrolls with the content; NEVER use `position: fixed` or `position: sticky` to make the cover image hover.
|
||||
- **Padding**: `20px 50px 50px 50px` (top, right, bottom, left - top padding reduced to 20px)
|
||||
- **Scrolling**: Vertical scrolling enabled
|
||||
- **Custom scrollbar**: Fully transparent scrollbar or no scrollbar display
|
||||
|
||||
- **CSS Implementation Key Points**:
|
||||
- `.container` set `overflow-y: auto`
|
||||
- `.content` only sets `padding`, no scrolling
|
||||
- Cover image as direct child element of `.container`, positioned before `.content`
|
||||
|
||||
---
|
||||
|
||||
## 2. Font System
|
||||
|
||||
### 2.1 Import Fonts
|
||||
|
||||
Import the following fonts from Google Fonts:
|
||||
|
||||
- **Noto Serif SC** (Source Han Serif): `weight: 700`
|
||||
- **Inter**: `weight: 300, 400, 700, 800`
|
||||
- **JetBrains Mono**: `weight: 400, 700`
|
||||
|
||||
### 2.2 Font Application Rules
|
||||
|
||||
| Content Type | Font |
|
||||
|--------------|------|
|
||||
| Body default | System font stack (`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto`, etc.) |
|
||||
| H1 Main title | Noto Serif SC (Source Han Serif) |
|
||||
| H2 Subtitle | Times New Roman |
|
||||
| English titles | Inter |
|
||||
| Code | JetBrains Mono |
|
||||
|
||||
---
|
||||
|
||||
## 3. Text Style Specifications
|
||||
|
||||
### 3.1 Cover Image
|
||||
|
||||
- **Dimensions**: 600px × 350px
|
||||
- **Image uses: object-fit: cover to ensure no compression**
|
||||
- Generate a hand-drawn illustration/comic based on the content's main theme, in a style similar to *The New Yorker* editorial cartoons. (The comic reflects the article's meaning)
|
||||
|
||||
### 3.2 Title Hierarchy
|
||||
|
||||
| Element | Font | Size | Color | Weight | Line Height | Margin |
|
||||
|---------|------|------|-------|--------|-------------|--------|
|
||||
| `h1` | Noto Serif SC | 42px | `#000000` | 700 | 1.3 | `margin-bottom: 30px` |
|
||||
| `h2` | Times New Roman | 26px | `#000000` | 700 | - | `margin: 40px 0 20px` |
|
||||
| `h3` | Default | 22px | `#2c3e50` | 600 | - | `margin: 30px 0 15px` |
|
||||
| `h4` | Default | 20px | `#5a6c7d` | 600 | - | `margin: 25px 0 12px` |
|
||||
|
||||
### 3.3 Body Text
|
||||
|
||||
- **Font size**: `20px`
|
||||
- **Color**: `#333333`
|
||||
- **Line height**: `2`
|
||||
- **Paragraph spacing**: `margin-bottom: 20px`
|
||||
|
||||
### 3.4 Special Text Classes
|
||||
|
||||
**English title** (`.en-title`)
|
||||
|
||||
- Font: Inter
|
||||
- Size: 18px
|
||||
- Color: `#888888`
|
||||
- Weight: 300
|
||||
|
||||
**Metadata** (`.metadata`)
|
||||
|
||||
- Size: 14px
|
||||
- Color: `#888888`
|
||||
|
||||
---
|
||||
|
||||
## 4. Emphasis and Markers
|
||||
|
||||
### 4.1 Links (`<a>`)
|
||||
|
||||
- Color: `#4a9eff` (blue)
|
||||
- Default no underline
|
||||
- Show underline on hover
|
||||
- Transition effect: `transition: 0.2s ease`
|
||||
|
||||
### 4.2 Emphasis (`<em>`)
|
||||
|
||||
- Color: `#000000` (black)
|
||||
- Font style: `normal` (not italic)
|
||||
- **Usage**: Text that needs emphasis but not highlighting
|
||||
|
||||
### 4.3 Bold (`<strong>`)
|
||||
|
||||
- **Usage**: Important keywords
|
||||
|
||||
### 4.4 Highlight marker (`<mark>`)
|
||||
|
||||
- Background color: `#fff59d` (light yellow)
|
||||
- Text color: `#000000`
|
||||
- Weight: `bold`
|
||||
- Bottom border: `2px solid #ff9800` (orange)
|
||||
- Border radius: `4px`
|
||||
- Padding: `2px 6px`
|
||||
|
||||
---
|
||||
|
||||
## 5. Lists and Quotes
|
||||
|
||||
### 5.1 Lists (`<ul>`, `<ol>`)
|
||||
|
||||
- Font size: `20px`
|
||||
- Left padding: `20px`
|
||||
- Bottom margin: `margin-bottom: 20px`
|
||||
|
||||
### 5.2 List items (`<li>`)
|
||||
|
||||
- Item spacing: `margin-bottom: 8px`
|
||||
|
||||
### 5.3 Blockquote (`<blockquote>`)
|
||||
|
||||
- Left border: `4px solid #4a9eff` (blue vertical line)
|
||||
- Left padding: `20px`
|
||||
- Font style: italic
|
||||
- Top/bottom margin: `margin: 20px 0`
|
||||
|
||||
---
|
||||
|
||||
## 6. Code Styles
|
||||
|
||||
### 6.1 Code block (`<pre><code>`)
|
||||
|
||||
- Font: JetBrains Mono
|
||||
- Size: `17px`
|
||||
- Background color: `#f5f5f5`
|
||||
- Border: `1px solid #e0e0e0`
|
||||
- Border radius: `6px`
|
||||
- Padding: `20px`
|
||||
- Line height: `1.6`
|
||||
- Horizontal scrolling enabled
|
||||
|
||||
### 6.2 Inline code (`<code>`)
|
||||
|
||||
- Font: JetBrains Mono
|
||||
- Size: inherit, slightly smaller
|
||||
- Background color: `#f5f5f5`
|
||||
- Padding: `2px 6px`
|
||||
- Border radius: `4px`
|
||||
|
||||
---
|
||||
|
||||
## 7. Responsive Design
|
||||
|
||||
**Breakpoint**: `650px` and below
|
||||
|
||||
| Element | Desktop | Mobile |
|
||||
|---------|---------|--------|
|
||||
| Body padding | `20px` | `10px` |
|
||||
| Body font size | `20px` | `20px` |
|
||||
| Container width | `600px` | `100%` |
|
||||
| Container height | `800px` | `auto` (min `80vh`) |
|
||||
| Content area padding | `50px` | `30px` |
|
||||
| H1 font size | `42px` | `36px` |
|
||||
| H2 font size | `26px` | `24px` |
|
||||
| List font size | `20px` | `20px` |
|
||||
| Code block font size | `17px` | `15px` |
|
||||
| Code block padding | `20px` | `15px` |
|
||||
|
||||
---
|
||||
|
||||
## 8. Output Requirements
|
||||
|
||||
1. **Generate complete HTML5 document** with `<!DOCTYPE>`, `<html>`, `<head>`, `<body>` tags
|
||||
2. **All styles inline in `<style>` tag**, no external CSS file needed
|
||||
3. **Correctly import Google Fonts**
|
||||
4. **Ensure semantic HTML structure**
|
||||
5. **Clean code formatting** with proper indentation
|
||||
6. **Include viewport meta tag** for responsive support
|
||||
7. **Set page character encoding to UTF-8**
|
||||
8. Strictly follow the original content obtained, do not modify, simplify, or delete on your own.
|
||||
|
||||
---
|
||||
|
||||
## 9. Usage
|
||||
|
||||
User will provide article content in `<user_content>` tags, which may contain:
|
||||
|
||||
- Cover image
|
||||
- Titles (h1-h6)
|
||||
- Paragraph text
|
||||
- Lists (ordered/unordered)
|
||||
- Code blocks
|
||||
- Links
|
||||
- Emphasis/highlight text
|
||||
- Blockquotes
|
||||
|
||||
Please convert these contents into a beautifully formatted HTML page according to the above specifications.
|
||||
|
||||
---
|
||||
|
||||
## 10. Special Requirements
|
||||
|
||||
Please strictly follow the provided original content, do not modify, delete, or re-polish on your own.
|
||||
|
||||
---
|
||||
Executable
+263
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cover Image Generation Script - Using Gemini REST API
|
||||
|
||||
Usage:
|
||||
python generate_images.py <prompts.json>
|
||||
|
||||
The prompts JSON file should be located in output/<article-folder>/ directory,
|
||||
and generated images will be saved to output/<article-folder>/images/ directory.
|
||||
|
||||
JSON Format:
|
||||
{
|
||||
"article": {
|
||||
"title": "Article Title",
|
||||
"theme": "Brief description of article theme"
|
||||
},
|
||||
"cover": {
|
||||
"prompt": "Full image generation prompt",
|
||||
"aspect_ratio": "600x350"
|
||||
}
|
||||
}
|
||||
|
||||
Or simplified format:
|
||||
{
|
||||
"theme": "Article theme description for cover image generation"
|
||||
}
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
import base64
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file in script's parent directory
|
||||
script_dir = Path(__file__).parent.parent
|
||||
load_dotenv(script_dir / ".env")
|
||||
|
||||
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
||||
if not GEMINI_API_KEY:
|
||||
print("Error: GEMINI_API_KEY environment variable not found")
|
||||
print("Please create a .env file with GEMINI_API_KEY=your_key")
|
||||
print("Get your API key from: https://aistudio.google.com/app/apikey")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Cover image template - New Yorker editorial cartoon style
|
||||
COVER_TEMPLATE = """Generate a hand-drawn illustration in the style of *The New Yorker* editorial cartoons.
|
||||
|
||||
Style specifications:
|
||||
- Hand-drawn, sketch-like quality
|
||||
- Minimalist and thoughtful composition
|
||||
- Subtle humor or irony if appropriate
|
||||
- Clean lines with cross-hatching for shading
|
||||
- Limited color palette or black and white
|
||||
- Editorial cartoon aesthetic
|
||||
|
||||
Image dimensions: 600px width × 350px height (landscape)
|
||||
|
||||
Theme to illustrate:
|
||||
{theme}
|
||||
|
||||
The illustration should visually capture the essence and main message of this theme in a clever, artistic way typical of New Yorker cartoons."""
|
||||
|
||||
|
||||
def sanitize_prompt(prompt: str) -> str:
|
||||
"""
|
||||
Sanitize prompt text to handle newlines, paragraph separators, and special characters.
|
||||
"""
|
||||
if not prompt:
|
||||
return prompt
|
||||
|
||||
# Normalize line endings: CRLF -> LF, CR -> LF
|
||||
text = prompt.replace('\r\n', '\n').replace('\r', '\n')
|
||||
|
||||
# Replace paragraph separator (U+2029) and line separator (U+2028) with newlines
|
||||
text = text.replace('\u2029', '\n\n').replace('\u2028', '\n')
|
||||
|
||||
# Split into lines and strip each line
|
||||
lines = [line.strip() for line in text.split('\n')]
|
||||
|
||||
# Collapse multiple empty lines into single empty line
|
||||
collapsed_lines = []
|
||||
prev_empty = False
|
||||
for line in lines:
|
||||
is_empty = len(line) == 0
|
||||
if is_empty:
|
||||
if not prev_empty:
|
||||
collapsed_lines.append('')
|
||||
prev_empty = True
|
||||
else:
|
||||
collapsed_lines.append(line)
|
||||
prev_empty = False
|
||||
|
||||
# Join with single newlines
|
||||
text = '\n'.join(collapsed_lines)
|
||||
|
||||
# Strip leading/trailing whitespace from the entire text
|
||||
text = text.strip()
|
||||
|
||||
# Collapse multiple spaces into single space (but preserve newlines)
|
||||
text = re.sub(r'[ \t]+', ' ', text)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def generate_image(prompt: str, filename: str, images_dir: Path) -> str:
|
||||
"""Generate image using Gemini REST API"""
|
||||
print(f"Generating: {filename}...")
|
||||
|
||||
# Sanitize the prompt to handle newlines and special characters
|
||||
clean_prompt = sanitize_prompt(prompt)
|
||||
|
||||
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key={GEMINI_API_KEY}"
|
||||
|
||||
payload = {
|
||||
"contents": [{
|
||||
"parts": [{"text": clean_prompt}]
|
||||
}],
|
||||
"generationConfig": {
|
||||
"responseModalities": ["TEXT", "IMAGE"]
|
||||
}
|
||||
}
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(url, data=json.dumps(payload).encode(), headers=headers)
|
||||
with urllib.request.urlopen(req, timeout=180) as response:
|
||||
result = json.loads(response.read().decode())
|
||||
|
||||
# Extract image
|
||||
for part in result.get("candidates", [{}])[0].get("content", {}).get("parts", []):
|
||||
if "inlineData" in part:
|
||||
image_data = base64.b64decode(part["inlineData"]["data"])
|
||||
filepath = images_dir / filename
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(image_data)
|
||||
print(f" Saved: {filepath}")
|
||||
return str(filepath)
|
||||
|
||||
print(f" {filename} generation failed: No image returned")
|
||||
return None
|
||||
|
||||
except urllib.error.HTTPError as e:
|
||||
error_body = e.read().decode() if e.fp else ""
|
||||
print(f" {filename} generation failed: HTTP {e.code}")
|
||||
print(f" Error details: {error_body[:200]}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f" {filename} generation failed: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def load_prompts_json(json_path: Path) -> dict:
|
||||
"""
|
||||
Load prompts JSON file.
|
||||
|
||||
Supports two formats:
|
||||
1. Full format: {"article": {...}, "cover": {"prompt": "..."}}
|
||||
2. Simple format: {"theme": "..."}
|
||||
|
||||
Returns:
|
||||
Dictionary with cover prompt ready to use
|
||||
"""
|
||||
with open(json_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Determine prompt
|
||||
if "cover" in data and "prompt" in data["cover"]:
|
||||
# Full prompt provided
|
||||
prompt = data["cover"]["prompt"]
|
||||
elif "theme" in data:
|
||||
# Use template with theme
|
||||
prompt = COVER_TEMPLATE.format(theme=data["theme"])
|
||||
elif "article" in data and "theme" in data["article"]:
|
||||
# Theme in article object
|
||||
prompt = COVER_TEMPLATE.format(theme=data["article"]["theme"])
|
||||
else:
|
||||
raise ValueError("JSON must contain 'cover.prompt' or 'theme' field")
|
||||
|
||||
return {
|
||||
"title": data.get("article", {}).get("title", "Untitled"),
|
||||
"prompt": prompt
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
# Check command line arguments
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python generate_images.py <prompts.json>")
|
||||
print()
|
||||
print("The prompts JSON file should be in output/<article-folder>/ directory.")
|
||||
print("Generated cover image will be saved to output/<article-folder>/images/cover.png")
|
||||
print()
|
||||
print("JSON format examples:")
|
||||
print()
|
||||
print("Simple format:")
|
||||
print('''
|
||||
{
|
||||
"theme": "The irony of social media bringing people together while isolating them"
|
||||
}
|
||||
''')
|
||||
print("Full format:")
|
||||
print('''
|
||||
{
|
||||
"article": {
|
||||
"title": "Article Title",
|
||||
"theme": "Article theme description"
|
||||
},
|
||||
"cover": {
|
||||
"prompt": "Full custom image generation prompt..."
|
||||
}
|
||||
}
|
||||
''')
|
||||
sys.exit(1)
|
||||
|
||||
json_path = Path(sys.argv[1]).resolve()
|
||||
|
||||
if not json_path.exists():
|
||||
print(f"Error: File does not exist: {json_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Determine output directory (images subdirectory of JSON file's directory)
|
||||
output_dir = json_path.parent
|
||||
images_dir = output_dir / "images"
|
||||
images_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Load prompts
|
||||
try:
|
||||
config = load_prompts_json(json_path)
|
||||
except Exception as e:
|
||||
print(f"Error: Failed to load prompts file: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
print("=" * 60)
|
||||
print("Cover Image Generation")
|
||||
print(f"Article: {config['title']}")
|
||||
print(f"Prompts file: {json_path}")
|
||||
print(f"Output directory: {images_dir}")
|
||||
print("=" * 60)
|
||||
|
||||
# Generate cover image
|
||||
result = generate_image(config["prompt"], "cover.png", images_dir)
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
if result:
|
||||
print("Cover image generated successfully!")
|
||||
print(f"Location: {result}")
|
||||
else:
|
||||
print("Cover image generation failed.")
|
||||
print("=" * 60)
|
||||
|
||||
return 0 if result else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Executable
+264
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Screenshot Capture Script for Xiaohongshu Images
|
||||
|
||||
Captures sequential screenshots of an HTML page at 3:4 aspect ratio,
|
||||
ensuring no text is cut off at boundaries.
|
||||
|
||||
Usage:
|
||||
python screenshot.py <html_file_path>
|
||||
|
||||
Output:
|
||||
Screenshots saved to <html_folder>/screenshots/01.png, 02.png, etc.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
except ImportError:
|
||||
print("Error: Playwright is not installed.")
|
||||
print("Install it with: pip install playwright && playwright install chromium")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Screenshot dimensions for Xiaohongshu 3:4 ratio
|
||||
SCREENSHOT_WIDTH = 1080
|
||||
SCREENSHOT_HEIGHT = 1440 # 3:4 ratio
|
||||
|
||||
|
||||
def find_safe_cut_point(page, y_position: int, search_range: int = 100) -> int:
|
||||
"""
|
||||
Find a safe cutting point near y_position where no text is cut.
|
||||
|
||||
Searches upward from y_position to find a gap between text elements.
|
||||
|
||||
Args:
|
||||
page: Playwright page object
|
||||
y_position: Target Y position to cut
|
||||
search_range: How far to search upward for safe point
|
||||
|
||||
Returns:
|
||||
Safe Y position to cut at
|
||||
"""
|
||||
# JavaScript to find text boundaries near the cut point
|
||||
script = f"""
|
||||
() => {{
|
||||
const targetY = {y_position};
|
||||
const searchRange = {search_range};
|
||||
|
||||
// Get all text-containing elements
|
||||
const textElements = document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, span, a, code, pre, blockquote');
|
||||
|
||||
let safeCutPoint = targetY;
|
||||
let minGap = Infinity;
|
||||
|
||||
// Find elements that might be cut
|
||||
for (const el of textElements) {{
|
||||
const rect = el.getBoundingClientRect();
|
||||
const elTop = rect.top + window.scrollY;
|
||||
const elBottom = rect.bottom + window.scrollY;
|
||||
|
||||
// Check if this element would be cut at targetY
|
||||
if (elTop < targetY && elBottom > targetY) {{
|
||||
// This element would be cut, find gap above it
|
||||
const gapAbove = targetY - elTop;
|
||||
if (gapAbove < searchRange && gapAbove < minGap) {{
|
||||
// Cut above this element instead
|
||||
safeCutPoint = Math.max(0, elTop - 10);
|
||||
minGap = gapAbove;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
// Also check for line breaks within text blocks
|
||||
// Find the nearest paragraph boundary
|
||||
const paragraphs = document.querySelectorAll('p, li, h1, h2, h3, h4, h5, h6');
|
||||
for (const p of paragraphs) {{
|
||||
const rect = p.getBoundingClientRect();
|
||||
const pBottom = rect.bottom + window.scrollY;
|
||||
|
||||
// If paragraph ends near our cut point, prefer cutting there
|
||||
if (pBottom < targetY && targetY - pBottom < searchRange) {{
|
||||
const gap = targetY - pBottom;
|
||||
if (gap < minGap) {{
|
||||
safeCutPoint = pBottom + 5;
|
||||
minGap = gap;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
return Math.max(0, Math.floor(safeCutPoint));
|
||||
}}
|
||||
"""
|
||||
|
||||
try:
|
||||
safe_y = page.evaluate(script)
|
||||
return safe_y if safe_y > 0 else y_position
|
||||
except Exception as e:
|
||||
print(f" Warning: Could not find safe cut point: {e}")
|
||||
return y_position
|
||||
|
||||
|
||||
def capture_screenshots(html_path: Path, output_dir: Path):
|
||||
"""
|
||||
Capture sequential screenshots of an HTML page.
|
||||
|
||||
Args:
|
||||
html_path: Path to the HTML file
|
||||
output_dir: Directory to save screenshots
|
||||
"""
|
||||
screenshots_dir = output_dir / "screenshots"
|
||||
screenshots_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print(f"Opening: {html_path}")
|
||||
print(f"Screenshots will be saved to: {screenshots_dir}")
|
||||
print(f"Screenshot size: {SCREENSHOT_WIDTH}x{SCREENSHOT_HEIGHT} (3:4 ratio)")
|
||||
print()
|
||||
|
||||
with sync_playwright() as p:
|
||||
# Launch browser
|
||||
browser = p.chromium.launch(headless=True)
|
||||
|
||||
# Create page with specific viewport
|
||||
context = browser.new_context(
|
||||
viewport={"width": SCREENSHOT_WIDTH, "height": SCREENSHOT_HEIGHT},
|
||||
device_scale_factor=2 # Retina quality
|
||||
)
|
||||
page = context.new_page()
|
||||
|
||||
# Navigate to the HTML file
|
||||
file_url = f"file://{html_path.resolve()}"
|
||||
page.goto(file_url, wait_until="networkidle")
|
||||
|
||||
# Wait for fonts and images to load
|
||||
page.wait_for_timeout(2000)
|
||||
|
||||
# Get total page height
|
||||
total_height = page.evaluate("() => document.documentElement.scrollHeight")
|
||||
print(f"Total page height: {total_height}px")
|
||||
|
||||
# Calculate number of screenshots needed
|
||||
current_y = 0
|
||||
screenshot_index = 1
|
||||
captured_screenshots = []
|
||||
|
||||
while current_y < total_height:
|
||||
# Calculate the target end position for this screenshot
|
||||
target_end_y = current_y + SCREENSHOT_HEIGHT
|
||||
|
||||
if target_end_y >= total_height:
|
||||
# Last screenshot - capture whatever remains
|
||||
actual_height = total_height - current_y
|
||||
|
||||
# Scroll to position
|
||||
page.evaluate(f"window.scrollTo(0, {current_y})")
|
||||
page.wait_for_timeout(200)
|
||||
|
||||
# Take screenshot
|
||||
filename = f"{screenshot_index:02d}.png"
|
||||
filepath = screenshots_dir / filename
|
||||
|
||||
# For the last screenshot, we might have less than full height
|
||||
# Add white padding if needed
|
||||
page.screenshot(
|
||||
path=str(filepath),
|
||||
clip={
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": SCREENSHOT_WIDTH,
|
||||
"height": min(SCREENSHOT_HEIGHT, actual_height + 50) # Small buffer
|
||||
}
|
||||
)
|
||||
|
||||
print(f" Captured: {filename} (final, {actual_height}px of content)")
|
||||
captured_screenshots.append(str(filepath))
|
||||
break
|
||||
else:
|
||||
# Find safe cut point to avoid cutting text
|
||||
safe_end_y = find_safe_cut_point(page, target_end_y)
|
||||
actual_height = safe_end_y - current_y
|
||||
|
||||
# Ensure we make progress even if safe cut point is the same
|
||||
if actual_height < SCREENSHOT_HEIGHT * 0.5:
|
||||
actual_height = SCREENSHOT_HEIGHT
|
||||
safe_end_y = current_y + actual_height
|
||||
|
||||
# Scroll to position
|
||||
page.evaluate(f"window.scrollTo(0, {current_y})")
|
||||
page.wait_for_timeout(200)
|
||||
|
||||
# Take screenshot
|
||||
filename = f"{screenshot_index:02d}.png"
|
||||
filepath = screenshots_dir / filename
|
||||
|
||||
page.screenshot(
|
||||
path=str(filepath),
|
||||
clip={
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": SCREENSHOT_WIDTH,
|
||||
"height": SCREENSHOT_HEIGHT
|
||||
}
|
||||
)
|
||||
|
||||
print(f" Captured: {filename} (y: {current_y} to {safe_end_y})")
|
||||
captured_screenshots.append(str(filepath))
|
||||
|
||||
# Move to next section
|
||||
current_y = safe_end_y
|
||||
screenshot_index += 1
|
||||
|
||||
browser.close()
|
||||
|
||||
return captured_screenshots
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python screenshot.py <html_file_path>")
|
||||
print()
|
||||
print("Captures sequential 3:4 ratio screenshots of an HTML page.")
|
||||
print("Screenshots are saved to <html_folder>/screenshots/")
|
||||
sys.exit(1)
|
||||
|
||||
html_path = Path(sys.argv[1]).resolve()
|
||||
|
||||
if not html_path.exists():
|
||||
print(f"Error: File does not exist: {html_path}")
|
||||
sys.exit(1)
|
||||
|
||||
if not html_path.suffix.lower() in ['.html', '.htm']:
|
||||
print(f"Warning: File does not appear to be HTML: {html_path}")
|
||||
|
||||
# Output directory is the same as HTML file's directory
|
||||
output_dir = html_path.parent
|
||||
|
||||
print("=" * 60)
|
||||
print("Xiaohongshu Screenshot Capture")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
screenshots = capture_screenshots(html_path, output_dir)
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(f"Screenshot capture complete!")
|
||||
print(f"Total screenshots: {len(screenshots)}")
|
||||
print(f"Location: {output_dir / 'screenshots'}")
|
||||
print("=" * 60)
|
||||
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during screenshot capture: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user