Coverage for ppt_utils.py: 100%

46 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-09 04:47 +0000

1""" 

2Utilities to process PPTX files. 

3""" 

4 

5import logging 

6import fitz 

7import subprocess 

8import os 

9 

10from pptx import Presentation 

11 

12from typing import Optional 

13from typing import List 

14 

15 

16IMAGE_DPI = 100 

17 

18 

19def get_num_slides( 

20 pptx_path: str, 

21 count_hidden: bool = False 

22) -> int: 

23 """ 

24 Get the number of slides in a PPTX file. 

25 """ 

26 presentation = Presentation(pptx_path) 

27 num_slides = 0 

28 for slide in presentation.slides: 

29 is_hidden = slide._element.get("show") == "0" 

30 if count_hidden or not is_hidden: 

31 num_slides += 1 

32 return num_slides 

33 

34 

35def pptx_to_images( 

36 pptx_path: str, 

37 output_path: str, 

38 dpi: int = IMAGE_DPI, 

39 width: Optional[int] = 1280, 

40 height: Optional[int] = 800, 

41 logger: Optional[logging.Logger] = None, 

42) -> List[str]: 

43 """ 

44 Render PPTX slides to images using libreoffice. 

45 """ 

46 # PPTX to PDF 

47 cmd = [ 

48 "libreoffice", 

49 "--headless", 

50 "--convert-to", "pdf", 

51 "--outdir", output_path, 

52 pptx_path 

53 ] 

54 if logger: 

55 logger.debug(f"Rendering PPTX slides to PDF with command: {' '.join(cmd)}") 

56 result = subprocess.run( 

57 cmd, 

58 stdout=subprocess.PIPE, 

59 stderr=subprocess.PIPE, 

60 text=True) 

61 if logger and result.stdout: 

62 logger.debug("LibreOffice:\n%s", result.stdout) 

63 if logger and result.stderr: 

64 logger.warning("LibreOffice:\n%s", result.stderr) 

65 if result.returncode != 0: 

66 raise RuntimeError(f"Failed to generate images from PPTX. Return code: {result.returncode}") 

67 

68 # PDF to PNG 

69 image_paths = [] 

70 pdf_path = pptx_path.replace(".pptx", ".pdf") 

71 

72 if not os.path.exists(pdf_path): 

73 raise FileNotFoundError(f"Expected PDF file not found: {pdf_path}") 

74 

75 doc = fitz.open(pdf_path) 

76 

77 doc_rect = doc[0].rect 

78 if width and height: 

79 matrix_width = width / doc_rect.width 

80 matrix_height = height / doc_rect.height 

81 matrix = fitz.Matrix(matrix_width, matrix_height) 

82 else: 

83 matrix = fitz.Matrix(dpi / 72, dpi / 72) 

84 

85 for page_number in range(len(doc)): 

86 page = doc.load_page(page_number) 

87 pix = page.get_pixmap(matrix=matrix) 

88 image_path = f"{output_path}/slide_{page_number + 1:03d}.png" 

89 pix.save(image_path) 

90 image_paths.append(image_path) 

91 doc.close() 

92 

93 return image_paths