pptx2mp4.py ダウンロード/コピー

pptx2mp4.py をダウンロード

pptx2mp4.py
pptx2mp4.py
  1#!/usr/bin/env python
  2"""
  3概要:
  4    Windows版PowerPointを使って、PPTXファイルをMP4へ変換します。
  5詳細説明:
  6    必要条件として pip install pywin32 を実行してモジュールをインストールしてください。
  7関連リンク:
  8    pptx2mp4_usage
  9"""
 10
 11from __future__ import annotations
 12
 13import argparse
 14import sys
 15import time
 16from pathlib import Path
 17
 18import pythoncom
 19import win32com.client
 20
 21
 22# PowerPoint.PpMediaTaskStatus
 23STATUS_NONE = 0
 24STATUS_IN_PROGRESS = 1
 25STATUS_QUEUED = 2
 26STATUS_DONE = 3
 27STATUS_FAILED = 4
 28
 29STATUS_NAMES = {
 30    STATUS_NONE: "準備中",
 31    STATUS_IN_PROGRESS: "変換中",
 32    STATUS_QUEUED: "待機中",
 33    STATUS_DONE: "完了",
 34    STATUS_FAILED: "失敗",
 35}
 36
 37
 38def convert_pptx_to_mp4(
 39    input_path: Path,
 40    output_path: Path,
 41    *,
 42    resolution: int = 720,
 43    fps: int = 30,
 44    quality: int = 100,
 45    default_slide_duration: int = 5,
 46    use_timings_and_narrations: bool = True,
 47    overwrite: bool = False,
 48) -> None:
 49    """
 50    概要:
 51        PowerPointのCreateVideoを使ってPPTXをMP4に変換します。
 52    詳細説明:
 53        入力されたPowerPointファイルを指定された設定でMP4動画ファイルに変換します。
 54        変換の進捗状況を標準出力に表示し、完了するまで待機します。
 55    引数:
 56        :param input_path: 入力となるPowerPointファイルのパス。
 57        :type input_path: pathlib.Path
 58        :param output_path: 出力するMP4ファイルのパス。
 59        :type output_path: pathlib.Path
 60        :param resolution: 動画の縦解像度。
 61        :type resolution: int
 62        :param fps: 動画のフレームレート。
 63        :type fps: int
 64        :param quality: 動画の品質設定。
 65        :type quality: int
 66        :param default_slide_duration: タイミング未設定時の1スライドの秒数。
 67        :type default_slide_duration: int
 68        :param use_timings_and_narrations: 記録済みのタイミングとナレーションを使用するかどうか。
 69        :type use_timings_and_narrations: bool
 70        :param overwrite: 既存の出力ファイルがある場合に上書きするかどうか。
 71        :type overwrite: bool
 72    戻り値:
 73        :returns: なし。
 74        :rtype: None
 75    例外:
 76        :raises FileNotFoundError: 入力ファイルが存在しない場合に発生します。
 77        :raises ValueError: 入出力ファイルの拡張子が適切でない場合に発生します。
 78        :raises FileExistsError: 出力ファイルがすでに存在し、overwriteがFalseの場合に発生します。
 79        :raises RuntimeError: PowerPointのビデオ作成処理に失敗した場合、またはMP4ファイルが作成されなかった場合に発生します。
 80    """
 81
 82    input_path = input_path.resolve()
 83    output_path = output_path.resolve()
 84
 85    if not input_path.is_file():
 86        raise FileNotFoundError(f"入力ファイルが見つかりません: {input_path}")
 87
 88    if input_path.suffix.lower() not in {".pptx", ".pptm", ".ppt"}:
 89        raise ValueError(f"PowerPointファイルではありません: {input_path}")
 90
 91    if output_path.suffix.lower() != ".mp4":
 92        raise ValueError("出力ファイルの拡張子は .mp4 にしてください。")
 93
 94    output_path.parent.mkdir(parents=True, exist_ok=True)
 95
 96    if output_path.exists():
 97        if not overwrite:
 98            raise FileExistsError(
 99                f"出力ファイルがすでに存在します: {output_path}\n"
100                "--overwrite を指定すると上書きできます。"
101            )
102        output_path.unlink()
103
104    pythoncom.CoInitialize()
105
106    powerpoint = None
107    presentation = None
108
109    try:
110        # 独立したPowerPointプロセスを起動
111        powerpoint = win32com.client.DispatchEx("PowerPoint.Application")
112        powerpoint.Visible = True
113
114        print(f"入力: {input_path}")
115        print(f"出力: {output_path}")
116        print(
117            f"設定: {resolution}p, {fps} fps, "
118            f"quality={quality}, timings={use_timings_and_narrations}"
119        )
120
121        # Open(FileName, ReadOnly, Untitled, WithWindow)
122        presentation = powerpoint.Presentations.Open(
123            str(input_path),
124            True,   # ReadOnly
125            False,  # Untitled
126            True,   # WithWindow
127        )
128
129        # CreateVideo(
130        #     FileName,
131        #     UseTimingsAndNarrations,
132        #     DefaultSlideDuration,
133        #     VertResolution,
134        #     FramesPerSecond,
135        #     Quality
136        # )
137        presentation.CreateVideo(
138            str(output_path),
139            use_timings_and_narrations,
140            default_slide_duration,
141            resolution,
142            fps,
143            quality,
144        )
145
146        print("ビデオの作成を開始しました。")
147
148        previous_status = None
149
150        while True:
151            status = int(presentation.CreateVideoStatus)
152
153            if status != previous_status:
154                print(f"状態: {STATUS_NAMES.get(status, f'不明 ({status})')}")
155                previous_status = status
156
157            if status == STATUS_DONE:
158                break
159
160            if status == STATUS_FAILED:
161                raise RuntimeError("PowerPointによるビデオ作成に失敗しました。")
162
163            time.sleep(2)
164
165        if not output_path.exists():
166            raise RuntimeError(
167                "PowerPointは完了を返しましたが、MP4ファイルが見つかりません。"
168            )
169
170        size_mb = output_path.stat().st_size / (1024 * 1024)
171
172        print("変換が完了しました。")
173        print(f"MP4: {output_path}")
174        print(f"サイズ: {size_mb:.1f} MB")
175
176    finally:
177        if presentation is not None:
178            try:
179                presentation.Close()
180            except Exception:
181                pass
182
183        if powerpoint is not None:
184            try:
185                powerpoint.Quit()
186            except Exception:
187                pass
188
189        pythoncom.CoUninitialize()
190
191
192def parse_args() -> argparse.Namespace:
193    """
194    概要:
195        コマンドライン引数を解析します。
196    詳細説明:
197        argparseを使って、入力ファイル、出力ファイル、解像度などの変換オプションを読み取ります。
198    戻り値:
199        :returns: 解析されたコマンドライン引数を格納した名前空間オブジェクト。
200        :rtype: argparse.Namespace
201    """
202    parser = argparse.ArgumentParser(
203        description="Windows版PowerPointを使ってPPTXをMP4に変換します。"
204    )
205
206    parser.add_argument(
207        "input",
208        type=Path,
209        help="入力するPowerPointファイル",
210    )
211
212    parser.add_argument(
213        "-o",
214        "--output",
215        type=Path,
216        help="出力MP4ファイル。省略時は入力ファイルと同じ場所に作成",
217    )
218
219    parser.add_argument(
220        "--resolution",
221        type=int,
222        default=720,
223        choices=(480, 720, 1080, 2160),
224        help="動画の縦解像度。デフォルト: 720",
225    )
226
227    parser.add_argument(
228        "--fps",
229        type=int,
230        default=30,
231        help="フレームレート。デフォルト: 30",
232    )
233
234    parser.add_argument(
235        "--quality",
236        type=int,
237        default=100,
238        choices=range(1, 101),
239        metavar="1-100",
240        help="動画品質。デフォルト: 100",
241    )
242
243    parser.add_argument(
244        "--default-duration",
245        type=int,
246        default=5,
247        help="タイミング未設定時の1スライドの秒数。デフォルト: 5",
248    )
249
250    parser.add_argument(
251        "--ignore-timings",
252        action="store_true",
253        help="記録済みタイミングとナレーションを使わない",
254    )
255
256    parser.add_argument(
257        "--overwrite",
258        type=int,
259        default=0,
260        help="既存のMP4ファイルを上書きするか [0|1]",
261    )
262
263    parser.add_argument(
264        "--pause",
265        type=int,
266        default=0,
267        help="既存のMP4ファイルを上書きするか [0|1]",
268    )
269
270    return parser.parse_args()
271
272
273def main() -> int:
274    """
275    概要:
276        スクリプトのメイン処理を実行します。
277    詳細説明:
278        コマンドライン引数を取得し、変換関数を呼び出します。
279        キーボード割り込みや予期せぬエラーが発生した場合はキャッチして終了コードを返します。
280    戻り値:
281        :returns: 終了コードとコマンドライン引数のタプル。
282        :rtype: tuple
283    """
284    args = parse_args()
285
286    output = args.output
287    if output is None:
288        output = args.input.with_suffix(".mp4")
289
290    try:
291        convert_pptx_to_mp4(
292            args.input,
293            output,
294            resolution=args.resolution,
295            fps=args.fps,
296            quality=args.quality,
297            default_slide_duration=args.default_duration,
298            use_timings_and_narrations=not args.ignore_timings,
299            overwrite=args.overwrite,
300        )
301        return 0, args
302
303    except KeyboardInterrupt:
304        print("\n中断しました。", file=sys.stderr)
305        return 130, args
306
307    except Exception as exc:
308        print(f"エラー: {exc}", file=sys.stderr)
309        return 1, args
310
311
312if __name__ == "__main__":
313    ret, args = main()
314    if args.pause: input("\nPress ENTER to terminate>>\n")
315    raise SystemExit(ret)