#!/usr/bin/env python3
"""
Copy image or HTML to system clipboard for X Articles publishing.
"""
from __future__ import annotations
import argparse
import io
import os
import sys
from pathlib import Path
def compress_image(image_path: str, quality: int = 85, max_size: tuple[int, int] = (2000, 2000)) -> bytes:
from PIL import Image
image = Image.open(image_path)
if image.mode in ("RGBA", "P"):
image = image.convert("RGB")
image.thumbnail(max_size, Image.Resampling.LANCZOS)
buffer = io.BytesIO()
image.save(buffer, format="JPEG", quality=quality, optimize=True)
return buffer.getvalue()
def copy_image_to_clipboard_macos(image_path: str, quality: int | None = None) -> bool:
try:
from AppKit import NSPasteboard, NSPasteboardTypePNG, NSPasteboardTypeTIFF
from Foundation import NSData
from PIL import Image
except ImportError as exc:
print(f"Error: Missing dependency: {exc}", file=sys.stderr)
print("Install with: pip install Pillow pyobjc-framework-Cocoa", file=sys.stderr)
return False
try:
if quality:
image_data = compress_image(image_path, quality)
else:
image_data = Path(image_path).read_bytes()
pasteboard = NSPasteboard.generalPasteboard()
pasteboard.clearContents()
ext = Path(image_path).suffix.lower()
if ext == ".png":
ns_data = NSData.dataWithBytes_length_(image_data, len(image_data))
pasteboard.setData_forType_(ns_data, NSPasteboardTypePNG)
else:
image = Image.open(io.BytesIO(image_data))
tiff_buffer = io.BytesIO()
image.save(tiff_buffer, format="TIFF")
tiff_data = NSData.dataWithBytes_length_(tiff_buffer.getvalue(), len(tiff_buffer.getvalue()))
pasteboard.setData_forType_(tiff_data, NSPasteboardTypeTIFF)
return True
except Exception as exc:
print(f"Error copying image: {exc}", file=sys.stderr)
return False
def copy_html_to_clipboard_macos(html: str) -> bool:
try:
from AppKit import NSPasteboard, NSPasteboardTypeHTML, NSPasteboardTypeString
from Foundation import NSData
except ImportError as exc:
print(f"Error: Missing dependency: {exc}", file=sys.stderr)
print("Install with: pip install pyobjc-framework-Cocoa", file=sys.stderr)
return False
try:
pasteboard = NSPasteboard.generalPasteboard()
pasteboard.clearContents()
html_data = html.encode("utf-8")
ns_data = NSData.dataWithBytes_length_(html_data, len(html_data))
pasteboard.setData_forType_(ns_data, NSPasteboardTypeHTML)
pasteboard.setString_forType_(html, NSPasteboardTypeString)
return True
except Exception as exc:
print(f"Error copying HTML: {exc}", file=sys.stderr)
return False
def copy_image_to_clipboard_windows(image_path: str, quality: int | None = None) -> bool:
try:
import win32clipboard
from PIL import Image
except ImportError as exc:
print(f"Error: Missing dependency: {exc}", file=sys.stderr)
print("Install with: pip install Pillow pywin32", file=sys.stderr)
return False
try:
if quality:
image_data = compress_image(image_path, quality)
image = Image.open(io.BytesIO(image_data))
else:
image = Image.open(image_path)
if image.mode in ("RGBA", "P", "LA"):
image = image.convert("RGB")
output = io.BytesIO()
image.save(output, format="BMP")
dib = output.getvalue()[14:]
output.close()
win32clipboard.OpenClipboard()
try:
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32clipboard.CF_DIB, dib)
finally:
win32clipboard.CloseClipboard()
return True
except Exception as exc:
print(f"Error copying image: {exc}", file=sys.stderr)
return False
def copy_html_to_clipboard_windows(html: str) -> bool:
try:
from clipboard import Clipboard
except ImportError as exc:
print(f"Error: Missing dependency: {exc}", file=sys.stderr)
print("Install with: pip install clip-util", file=sys.stderr)
return False
try:
with Clipboard() as clipboard:
clipboard["html"] = html
return True
except Exception as exc:
print(f"Error copying HTML: {exc}", file=sys.stderr)
return False
def copy_image_to_clipboard(image_path: str, quality: int | None = None) -> bool:
if sys.platform == "darwin":
return copy_image_to_clipboard_macos(image_path, quality)
if sys.platform == "win32":
return copy_image_to_clipboard_windows(image_path, quality)
print(f"Error: Unsupported platform: {sys.platform}", file=sys.stderr)
return False
def copy_html_to_clipboard(html: str) -> bool:
if sys.platform == "darwin":
return copy_html_to_clipboard_macos(html)
if sys.platform == "win32":
return copy_html_to_clipboard_windows(html)
print(f"Error: Unsupported platform: {sys.platform}", file=sys.stderr)
return False
def main() -> int:
parser = argparse.ArgumentParser(description="Copy image or HTML to clipboard")
subparsers = parser.add_subparsers(dest="type", required=True)
image_parser = subparsers.add_parser("image", help="Copy image to clipboard")
image_parser.add_argument("path", help="Path to image file")
image_parser.add_argument("--quality", type=int, default=None, help="JPEG quality (1-100)")
html_parser = subparsers.add_parser("html", help="Copy HTML to clipboard")
html_parser.add_argument("content", nargs="?", help="HTML content")
html_parser.add_argument("--file", "-f", help="Read HTML from file")
args = parser.parse_args()
if args.type == "image":
if not os.path.exists(args.path):
print(f"Error: Image not found: {args.path}", file=sys.stderr)
return 1
ok = copy_image_to_clipboard(args.path, args.quality)
if ok:
print(f"Image copied to clipboard: {args.path}")
return 0 if ok else 1
if args.file:
if not os.path.exists(args.file):
print(f"Error: File not found: {args.file}", file=sys.stderr)
return 1
html = Path(args.file).read_text(encoding="utf-8")
elif args.content:
html = args.content
else:
html = sys.stdin.read()
ok = copy_html_to_clipboard(html)
if ok:
print(f"HTML copied to clipboard ({len(html)} chars)")
return 0 if ok else 1
if __name__ == "__main__":
raise SystemExit(main())