pywin32 で起動中のパワポに画像を連続挿入する


このような感じになります↓

Windows での使用を想定し、下記の powershell コマンドレットから .py ファイルを呼び出して実行します。コマンドレット名がやたら長いですが補完が効くので問題はない……はずです。

起動中の Office 製品の操作には もっと直接的な方法 もありますが、powershell 6 以降は [System.Runtime.InteropServices.Marshal]::GetActiveObject() が使用できなくなっているので pywin32 で代用しています。

function Add-Image2ActivePptSlideWithPython {
    <#
        .SYNOPSIS
        pywin32 を利用して現在開いているパワポのスライドに画像を連続挿入する
    #>
    if ((Get-Process | Where-Object ProcessName -EQ "POWERPNT").Count -lt 1) {
        return
    }
    $targetPath = @($input).Fullname
    if (-not $targetPath) {
        return
    }

    if ((Read-Host "「ファイル内のイメージを圧縮しない」の設定はオンになっていますか? (y/n)") -ne "y") {
        return
    }

    $tmp = New-TemporaryFile
    $targetPath | Out-File -Encoding utf8 -FilePath $tmp.FullName # BOMつき

    # このスクリプトと同じディレクトリに後述の python スクリプトを配置しておく
    $pyCodePath = "{0}\activeppt_insert-image.py" -f $PSScriptRoot
    'python -B "{0}" "{1}"' -f $pyCodePath, $tmp.FullName | Invoke-Expression
    Remove-Item -Path $tmp.FullName
}

最初のほうでは 画像の圧縮を無効化 できているか確認しています(これを忘れると挿入された画像の解像度が落ちてしまうため)。

実際のコマンドライン上では下記のようにパイプライン経由で挿入したい画像オブジェクトを渡してやります。

ls -File | Add-Image2ActivePptSlideWithPython

パイプライン経由で渡された画像のパスを引数として渡す方法もありますが、それだと特殊文字のエスケープや引数の上限などが厄介なので New-TemporaryFile で作成した一時ファイルに書き出しています。
この際、powershell の仕様により UTF8 を指定しても BOM つきになるので文字コードには注意が必要。

呼び出し先の python ファイルは下記のようにします。パワポを立ち上げた状態で実行すると ActivePresentation で起動中のプロセスを捕まえられます。
前もって呼び出し側で PowerPoint が起動していることを確認していますが、それでもプレゼンテーションが開かれていなかった場合は何もしないようにしています。また、不可視のゾンビプロセスを捕まえてしまったときはプロセス自体を終了させます。

activeppt_insert-image.py
"""
pywin32 を使用して現在開いている PowerPoint スライドに画像を連続を挿入する
"""
import win32com.client
import argparse

class vb:
    msoFalse        = 0
    msoTrue         = -1
    ppLayoutBlank   = 12

def main(file_list_path):
    pptApp = win32com.client.Dispatch("PowerPoint.Application")
    if pptApp.Presentations.Count < 1:
        if not pptApp.Visible:
            pptApp.Quit()
        return

    with open(file_list_path, "r", encoding="utf_8_sig") as f:
        all_lines = f.read()
    image_path_list = all_lines.splitlines()

    presen = pptApp.ActivePresentation
    slide_width = presen.PageSetup.SlideWidth
    slide_height = presen.PageSetup.SlideHeight

    for img_path in image_path_list:
        slide_index = presen.Slides.Count + 1
        presen.Slides.Add(slide_index, vb.ppLayoutBlank)
        last_slide = presen.Slides(slide_index)
        inserted_img = last_slide.Shapes.AddPicture(
            FileName = img_path,
            LinkToFile = vb.msoFalse,
            SaveWithDocument = vb.msoTrue,
            Left = 0,
            Top = 0
            )
        inserted_img.Left = (slide_width - inserted_img.Width) / 2
        inserted_img.Top = (slide_height - inserted_img.Height) / 2
        print(f"inserted: {img_path}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("file_list_path")
    args = parser.parse_args()
    main(args.file_list_path)

最後に

(´-`).。oO(GetActiveObject() はやく実装されないかな……)