converter.save_clipboard のソースコード

"""
クリップボードの内容(テキストまたは画像)を取得し、ファイルに保存するスクリプト。

このスクリプトはWindows API (User32, Ole32, Kernel32) を使用してクリップボードデータを処理し、
PIL (Pillow) ライブラリを使って画像を保存します。
テキストデータはUTF-8エンコーディングで保存され、DIB (Device Independent Bitmap) 形式の画像はPNG形式で保存されます。
ファイル保存後、reStructuredText (rst) 形式でそのファイルを参照するためのスニペットを標準出力に表示します。

Usage:
    python save_clipboard.py [出力ファイル名のベース (拡張子なし)]

例:
    # クリップボードの内容を 'my_output.txt' または 'my_output.png' として保存
    python save_clipboard.py my_output
    # 引数なしの場合、'clipboard_output.txt' または 'clipboard_output.png' として保存
    python save_clipboard.py

:doc:`save_clipboard_usage`
"""
import ctypes
import ctypes.wintypes as wintypes
from ctypes import POINTER, byref, windll, c_void_p
from PIL import Image
import io
import struct
import sys
import os

# Windows APIの準備
ole32 = windll.ole32
kernel32 = windll.kernel32
user32 = windll.user32 # テキスト取得用に追加

# --- 64bitアドレスを正しく扱うための型定義 ---
kernel32.GlobalLock.argtypes = [c_void_p]
kernel32.GlobalLock.restype = c_void_p
kernel32.GlobalUnlock.argtypes = [c_void_p]
kernel32.GlobalSize.argtypes = [c_void_p]
kernel32.GlobalSize.restype = ctypes.c_size_t

# テキスト取得用API定義
user32.OpenClipboard.argtypes = [wintypes.HWND]
user32.OpenClipboard.restype = wintypes.BOOL
user32.CloseClipboard.restype = wintypes.BOOL
user32.GetClipboardData.argtypes = [wintypes.UINT]
user32.GetClipboardData.restype = wintypes.HANDLE
user32.IsClipboardFormatAvailable.argtypes = [wintypes.UINT]
user32.IsClipboardFormatAvailable.restype = wintypes.BOOL

# 定数定義
TYMED_HGLOBAL = 1
DVASPECT_CONTENT = 1
CF_DIB = 8
CF_UNICODETEXT = 13 # WindowsのUnicodeテキスト形式
GMEM_MOVEABLE = 0x0002

[ドキュメント] class FORMATETC(ctypes.Structure): """ OLEデータ転送で使用される形式記述構造体。 クリップボードやOLEオブジェクトからのデータ取得時に、どのような形式 (クリップボード形式、メディアタイプ、表示側面など)のデータを要求するかを指定します。 """ _fields_ = [ ("cfFormat", wintypes.WORD), ("ptd", c_void_p), ("dwAspect", wintypes.DWORD), ("lindex", wintypes.LONG), ("tymed", wintypes.DWORD), ]
[ドキュメント] class STGMEDIUM(ctypes.Structure): """ OLEデータ転送で使用されるストレージメディア構造体。 クリップボードやOLEオブジェクトからのデータ転送時に、実際のデータ (ハンドル、ポインタ、ストリームなど)とそのタイプを保持します。 `u` メンバは、`tymed` の値に応じてHGLOBALハンドルなどを格納するために `c_void_p` として定義されています。 """ _fields_ = [ ("tymed", wintypes.DWORD), ("u", c_void_p), # data handle (HGLOBAL) ("pUnkForRelease", c_void_p), ]
[ドキュメント] class IDataObject(ctypes.Structure): """ OLEデータ転送インターフェースの抽象基底クラス。 クリップボードやドラッグ&ドロップ操作など、データ転送オブジェクトが提供するデータを 管理するためのCOMインターフェースです。主に `GetData` メソッドを通じてデータを取得します。 """ pass
IDataObject._fields_ = [("lpVtbl", POINTER(c_void_p))]
[ドキュメント] def get_clipboard_text(): """ クリップボードからUnicodeテキストを取得します。 User32.dllのOpenClipboard, GetClipboardData, GlobalLock, GlobalUnlock, CloseClipboard関数を使用します。 CF_UNICODETEXT形式のデータを取得し、Pythonの文字列として返します。 クリップボードがロックされている場合や、テキストデータが見つからない場合はNoneを返します。 :returns: クリップボード上のテキストデータ (`str`)、またはNone。 :rtype: str | None """ if not user32.OpenClipboard(None): return None try: if not user32.IsClipboardFormatAvailable(CF_UNICODETEXT): return None h_global = user32.GetClipboardData(CF_UNICODETEXT) if not h_global: return None ptr = kernel32.GlobalLock(c_void_p(h_global)) if not ptr: return None try: # Unicode (UTF-16) としてデータを読み込む text = ctypes.c_wchar_p(ptr).value return text finally: kernel32.GlobalUnlock(c_void_p(h_global)) finally: user32.CloseClipboard()
[ドキュメント] def get_clipboard_image_fixed(): """ クリップボードからDIB (Device Independent Bitmap) 形式の画像を取得し、PIL Imageオブジェクトとして返します。 Ole32.dllのOleGetClipboardとReleaseStgMedium関数、およびIDataObjectインターフェースのGetDataメソッドを使用します。 取得したDIBデータは、BMPファイルヘッダを追加してPIL (Pillow) で読み込み可能な形式に変換されます。 この関数を呼び出す前に `ole32.OleInitialize(None)` が呼び出されている必要があります。 :returns: クリップボード上の画像データ (`PIL.Image.Image`)、またはNone。 :rtype: PIL.Image.Image | None """ # 既にOleInitializeされている前提、または明示的に呼ぶ # ole32.OleInitialize(None) # メイン側で制御 data_obj_ptr = POINTER(IDataObject)() hr = ole32.OleGetClipboard(byref(data_obj_ptr)) if hr != 0: return None try: # IDataObject::GetData のVtableインデックスは通常3 get_data_addr = data_obj_ptr.contents.lpVtbl[3] GetDataProto = ctypes.WINFUNCTYPE( ctypes.HRESULT, POINTER(IDataObject), POINTER(FORMATETC), POINTER(STGMEDIUM) ) get_data_func = GetDataProto(get_data_addr) except (IndexError, AttributeError): return None fmt = FORMATETC() fmt.cfFormat = CF_DIB fmt.ptd = None fmt.dwAspect = DVASPECT_CONTENT fmt.lindex = -1 fmt.tymed = TYMED_HGLOBAL stg = STGMEDIUM() hr = get_data_func(data_obj_ptr, byref(fmt), byref(stg)) img = None if hr == 0 and stg.tymed == TYMED_HGLOBAL: h_global = stg.u ptr = kernel32.GlobalLock(c_void_p(h_global)) size = kernel32.GlobalSize(c_void_p(h_global)) if ptr: try: dib_data = ctypes.string_at(ptr, size) if len(dib_data) >= 40: header_size = struct.unpack("<I", dib_data[0:4])[0] offset_to_pixels = 14 + header_size file_size = 14 + len(dib_data) bmp_header = struct.pack("<2sIHHI", b"BM", file_size, 0, 0, offset_to_pixels) img = Image.open(io.BytesIO(bmp_header + dib_data)) img.load() # メモリ上にデータをロード finally: kernel32.GlobalUnlock(c_void_p(h_global)) ole32.ReleaseStgMedium(byref(stg)) return img
if __name__ == "__main__": # OleInitializeはプロセス内で1回呼ぶ必要がある(OLE API使用のため) ole32.OleInitialize(None) # デフォルトのファイル名ベース base_filename = "clipboard_output" # 引数がある場合はそれをファイル名にする if len(sys.argv) > 1: # 安全なファイル名にするための簡易処理(パス区切り文字などを除去) requested_name = sys.argv[1] base_filename = os.path.splitext(os.path.basename(requested_name))[0] try: print(f"--- クリップボードの内容を確認中... ---") # 1. まずテキストがあるか確認(優先度が高い場合が多い) text_content = get_clipboard_text() if text_content is not None: print("[情報] クリップボード内にテキストが見つかりました。") # テキストファイルとして保存 out_name = f"{base_filename}.txt" # Windowsの改行コード(CRLF)で保存 with open(out_name, "w", encoding="utf-8", newline='\r\n') as f: f.write(text_content) print(f"成功: テキストを '{out_name}' として保存しました (UTF-8)。") print(f"文字数: {len(text_content)}") # rstスニペット表示 print_rst_snippets_for_text(out_name) sys.exit(0) # 2. 次に画像があるか確認 img = get_clipboard_image_fixed() if img: print("[情報] クリップボード内に画像(DIB)が見つかりました。") # 画像ファイルとして保存 (PNG推奨) out_name = f"{base_filename}.png" img.save(out_name, "PNG") print(f"成功: 画像を '{out_name}' として保存しました。") print(f"画像サイズ: {img.size[0]}x{img.size[1]}, モード: {img.mode}") # rstスニペット表示 print_rst_snippets_for_image(out_name) sys.exit(0) # 3. どちらもない場合 print("[情報] クリップボードにテキストまたは画像(CF_DIB)は見つかりませんでした。") # 形式リストを取得して表示(デバッグ用) if user32.OpenClipboard(None): try: formats = [] fnum = user32.EnumClipboardFormats(0) while fnum: formats.append(fnum) fnum = user32.EnumClipboardFormats(fnum) if formats: print(f"利用可能な形式ID: {formats}") finally: user32.CloseClipboard() except Exception as e: import traceback print(f"\n[エラー] 予期せぬエラーが発生しました。") traceback.print_exc() finally: # OleUninitializeはプロセス終了時に呼ぶ ole32.OleUninitialize()