aidentity / index

OpenSenseNova/SenseNova-Skills · MIT

ppt-analysis

PPT (.pptx/.ppt) 全量解析。覆盖:所有 slide 文本/表格/图表提取、嵌入图片 caption、纯图片 slide 渲染识别、数据标签提取。

stable

Contents

PPT Analysis — .pptx / .ppt

Environment

from pptx import Presentation
from pptx.util import Inches
import os, subprocess, json

# python-pptx is available
# For .ppt (old binary format): convert via libreoffice
def load_pptx(path):
    if path.lower().endswith('.ppt'):
        import subprocess
        out_dir = os.path.dirname(path)
        subprocess.run(
            ['libreoffice', '--headless', '--convert-to', 'pptx', '--outdir', out_dir, path],
            check=True, capture_output=True
        )
        path = path.rsplit('.', 1)[0] + '.pptx'
    return Presentation(path), path


Core Method 1: Full Text Extraction (ALL slides)

def extract_all_slides_text(pptx_path):
    """
    Extract text from every slide: text frames, tables, chart titles.
    For slides with no extractable text, flag them for image captioning.
    """
    prs, _ = load_pptx(pptx_path)
    slides_data = []

    for slide_num, slide in enumerate(prs.slides, start=1):
        slide_texts = []
        has_text = False

        for shape in slide.shapes:
            # Text frame (most common)
            if shape.has_text_frame:
                for para in shape.text_frame.paragraphs:
                    text = para.text.strip()
                    if text:
                        slide_texts.append(text)
                        has_text = True

            # Table
            if shape.has_table:
                tbl = shape.table
                for row in tbl.rows:
                    row_text = '\t'.join(cell.text.strip() for cell in row.cells)
                    if row_text.strip():
                        slide_texts.append(row_text)
                        has_text = True

            # Chart title
            if shape.shape_type == 3:  # MSO_SHAPE_TYPE.CHART
                try:
                    if shape.chart.has_title:
                        title = shape.chart.chart_title.text_frame.text
                        slide_texts.append(f"[Chart: {title}]")
                        has_text = True
                except Exception:
                    pass

        slides_data.append({
            'slide': slide_num,
            'text': '\n'.join(slide_texts),
            'has_text': has_text,
            'needs_caption': not has_text  # flag image-only slides
        })

    print(f"Total slides: {len(slides_data)}")
    image_only = sum(1 for s in slides_data if s['needs_caption'])
    print(f"Slides with text: {len(slides_data) - image_only}, image-only: {image_only}")
    return slides_data


Core Method 2: Table Extraction (Structured)

import pandas as pd

def extract_pptx_tables(pptx_path):
    """Extract all tables from all slides as DataFrames."""
    prs, _ = load_pptx(pptx_path)
    all_tables = []

    for slide_num, slide in enumerate(prs.slides, start=1):
        for shape in slide.shapes:
            if not shape.has_table:
                continue
            tbl = shape.table
            rows = []
            for row in tbl.rows:
                rows.append([cell.text.strip() for cell in row.cells])

            if not rows:
                continue

            # Use first row as header
            try:
                df = pd.DataFrame(rows[1:], columns=rows[0])
            except Exception:
                df = pd.DataFrame(rows)

            all_tables.append({'slide': slide_num, 'df': df})
            print(f"  Slide {slide_num}: table {df.shape[0]}r × {df.shape[1]}c")
            print(df.head(3).to_string())

    return all_tables


Core Method 3: Chart Data Extraction

python-pptx can read Chart data when it's stored as embedded Excel data.

If that fails, fall back to captioning the slide image.

def extract_chart_data(pptx_path):
    """
    Extract data series from Chart shapes.
    Returns list of {slide, chart_title, series_name, categories, values}.
    """
    prs, _ = load_pptx(pptx_path)
    charts = []

    for slide_num, slide in enumerate(prs.slides, start=1):
        for shape in slide.shapes:
            if shape.shape_type != 3:  # not a chart
                continue
            try:
                chart = shape.chart
                title = chart.chart_title.text_frame.text if chart.has_title else f"Chart_S{slide_num}"

                for plot in chart.plots:
                    for series in plot.series:
                        try:
                            categories = [str(pt.label) for pt in series.data_labels] if hasattr(series, 'data_labels') else []
                            values = [pt.value for pt in series.values] if hasattr(series, 'values') else []
                            # Alternative: use xChart data
                            if not values:
                                values = list(series.values)
                        except Exception as e:
                            values = []
                            categories = []

                        charts.append({
                            'slide': slide_num,
                            'chart_title': title,
                            'series': getattr(series, 'name', ''),
                            'categories': categories,
                            'values': values
                        })
            except Exception as e:
                print(f"  Slide {slide_num}: chart extraction failed ({e}) — will use caption")

    return charts


Core Method 4: Render Image-Only Slides → Caption

When a slide has no extractable text (pure image/screenshot slides):

import fitz  # PyMuPDF can also render PPTX via LibreOffice conversion

CAPTION = "/path/to/skills/sn-da-image-caption/scripts/caption.py"

def caption_image_slides(pptx_path, slides_data, prompt=None):
    """
    For slides flagged as 'needs_caption', render to PNG and caption.
    Uses LibreOffice to convert PPTX to PDF first, then renders pages.
    """
    image_slides = [s for s in slides_data if s['needs_caption']]
    if not image_slides:
        print("No image-only slides to caption.")
        return slides_data

    # Convert PPTX → PDF (preserves slide visuals)
    out_dir = "/tmp"
    r = subprocess.run(
        ['libreoffice', '--headless', '--convert-to', 'pdf', '--outdir', out_dir, pptx_path],
        capture_output=True, text=True
    )
    pdf_name = os.path.basename(pptx_path).rsplit('.', 1)[0] + '.pdf'
    pdf_path = os.path.join(out_dir, pdf_name)

    if not os.path.exists(pdf_path):
        print(f"LibreOffice conversion failed: {r.stderr[:200]}")
        return slides_data

    # Render each image-only slide
    doc = fitz.open(pdf_path)
    for s in image_slides:
        page_idx = s['slide'] - 1  # 0-indexed
        if page_idx >= len(doc):
            continue
        page = doc[page_idx]
        mat = fitz.Matrix(150/72, 150/72)
        pix = page.get_pixmap(matrix=mat)
        img_path = f"/tmp/slide_{s['slide']}.png"
        pix.save(img_path)

        # Caption the slide image
        cmd = ["python3", CAPTION, img_path, "--json"]
        p = prompt or "提取幻灯片中所有文字、数值和表格内容,保持结构,Markdown格式输出。"
        cmd += ["--prompt", p]
        cr = subprocess.run(cmd, capture_output=True, text=True, timeout=90)
        if cr.returncode == 0:
            desc = json.loads(cr.stdout).get("description", "")
            s['text'] = desc
            s['needs_caption'] = False
            print(f"  Slide {s['slide']}: captioned ({len(desc)} chars)")
        else:
            print(f"  Slide {s['slide']}: caption failed — {cr.stderr[:80]}")

    doc.close()
    return slides_data


Common Patterns

Keyword search across all slides

def find_in_pptx(pptx_path, keyword, slides_data=None):
    """Find keyword across all slides (after text extraction + captioning)."""
    if slides_data is None:
        slides_data = extract_all_slides_text(pptx_path)

    results = []
    for s in slides_data:
        if keyword in s.get('text', ''):
            idx = s['text'].find(keyword)
            context = s['text'][max(0, idx-100):idx+200]
            results.append({'slide': s['slide'], 'context': context})

    print(f"'{keyword}' found in {len(results)} slides: {[r['slide'] for r in results]}")
    return results

Time-line / process extraction from PPT

def extract_timeline(pptx_path, date_pattern=r'\d{4}[年/\-]\d{1,2}'):
    """Extract date-tagged events from slide text."""
    import re
    slides_data = extract_all_slides_text(pptx_path)
    events = []
    for s in slides_data:
        for line in s['text'].split('\n'):
            if re.search(date_pattern, line):
                events.append({'slide': s['slide'], 'event': line.strip()})
    return events

Statistics from PPT tables (e.g., 录用占比)

def compute_ratio_from_pptx_table(pptx_path, numerator_col, denominator_col):
    """Example: compute ratio = col_A / col_B for all rows."""
    tables = extract_pptx_tables(pptx_path)
    for item in tables:
        df = item['df']
        # Try to find columns (flexible matching)
        num_col = next((c for c in df.columns if numerator_col in c), None)
        den_col = next((c for c in df.columns if denominator_col in c), None)
        if num_col and den_col:
            df[num_col] = pd.to_numeric(df[num_col].str.replace('人', '').str.strip(), errors='coerce')
            df[den_col] = pd.to_numeric(df[den_col].str.replace('人', '').str.strip(), errors='coerce')
            df['ratio'] = (df[num_col] / df[den_col] * 100).round(0).astype(str) + '%'
            print(df[['slide' if 'slide' in df.columns else df.columns[0], num_col, den_col, 'ratio']].to_string())


Full Workflow Example

pptx_path = "/mnt/data/report.pptx"

# 1. Extract text from all slides
slides_data = extract_all_slides_text(pptx_path)

# 2. Caption image-only slides
slides_data = caption_image_slides(pptx_path, slides_data)

# 3. Combine all text for analysis
all_text = '\n\n'.join(
    f"[Slide {s['slide']}]\n{s['text']}"
    for s in slides_data if s.get('text')
)

# 4. Search or analyze
results = find_in_pptx(pptx_path, '录用占比', slides_data)

# 5. Extract tables if needed
tables = extract_pptx_tables(pptx_path)


Pitfalls

PitfallFix
Skip slides with no text → miss chart dataFlag needs_caption, render & caption (Method 4)
shape.chart.plots[0].series fails → no dataCatch exception, fall back to captioning the slide
Table columns misread (企业名 vs 岗位名)Print headers + first 3 rows before computing; verify column meaning
Only read first N slidesAlways for slide in prs.slides — no index limit
.ppt format → python-pptx can't openConvert to .pptx via libreoffice first
PPT has overlapping text boxes → garbled orderSort shapes by top-left position: sorted(slide.shapes, key=lambda s: (s.top, s.left))

Provenance

Authored by OpenSenseNova in OpenSenseNova/SenseNova-Skills. The content is mirrored here under its upstream license.

Related

social When the user wants help creating, scheduling, or optimizing social media content for LinkedIn, Twitter/X, Instagram, TikTok, or Facebook, or wants to do social listening and engagement triage. Also use when the user mentions 'LinkedIn post,' 'Twitter thread,' 'social media,' 'content calendar,' 'social scheduling,' 'engagement,' 'viral content,' 'what should I post,' 'repurpose this content,' 'tweet ideas,' 'LinkedIn carousel,' 'social media strategy,' 'grow my following,' 'TikTok video,' 'Reels,' 'Shorts,' 'video script,' 'video hook,' 'short-form video,' 'create a reel,' 'social listening,' 'brand mentions,' 'competitor monitoring,' 'top posts to comment on,' 'find people asking for,' 'carousel,' 'slide-by-slide,' or 'document post.' Use this for social content, repurposing, scheduling, video scripts, and listening. Posts avoid AI tells like 'it's not X, it's Y' reveals and broetry. For broader content strategy, see content-strategy. For paid ads, see ad-creative. For earned media, see public-relations. coreyhaines31/marketingskills MIT sn-da-image-caption 图片理解与数据提取 skill。当图片文件(.png/.jpg/.jpeg/.gif/.webp/.bmp)是主要输入且用户需要理解、提取数据或分析图片内容时使用。提供预配置的 caption 脚本(scripts/caption.py),通过 vision 模型将图片转为文本描述,无需额外配置 API Key。覆盖:(1) 通过 scripts/caption.py 对图表/表格/截图/流程图进行 caption,(2) 将 caption 文本解析为结构化 DataFrame,(3) 基于提取数据重新生成可视化图表,(4) 导出为 Excel/CSV。**遇到以下任一情况就主动使用本 skill,不要自行猜测图片内容**:①用户出现触发词:图片分析 / 图表提取 / 表格识别 / OCR / 图片描述 / 截图分析 / 图表数据 / 提取图片中的数据 / 图片转表格 / 识别图片 / image caption / extract data from image / chart analysis / table OCR;②用户上传或指定了图片文件(.png / .jpg / .jpeg / .gif / .webp / .bmp)并要求理解、提取数据或分析内容;③任务需要从图表截图、表格截图、UI 截图、流程图中提取结构化信息;④用户要求将图片中的数据转为 Excel/CSV 或重新生成可视化图表。仅不用于:图片编辑(裁剪、滤镜、缩放)、图片生成、不含数据的风景/人物照片描述。 OpenSenseNova/SenseNova-Skills MIT sn-da-excel-workflow Excel 数据分析多步编排器。覆盖:(1) 读取多 Sheet Excel 文件并统计行数,(2) 大文件检测(≥10k 行自动 Parquet 优化),(3) 数据清洗(缺失值、文本标准化、无效字符),(4) 条件筛选与分类提取,(5) 跨 Sheet 统计聚合,(6) 导出 Excel/CSV 并提供下载链接。覆盖从数据读取到报告生成全流程,按步骤编排 capability 子 skill。**遇到以下任一情况就主动使用本 skill,不要自行写几行 pandas 就回答**:①用户出现触发词:Excel 分析 / 表格分析 / 数据分析 / 数据清洗 / 数据统计 / 数据筛选 / 数据可视化 / 数据导出 / 汇总统计 / 透视表 / 分组统计 / 交叉分析 / 趋势分析 / 对比分析 / 异常值检测 / 去重 / 缺失值处理 / Excel 报告 / 生成报表 / analyze Excel / data analysis / data cleaning / pivot table;②用户上传或指定了 .xlsx / .xls / .csv 文件并要求分析、清洗、统计或可视化;③任务涉及多 Sheet 读取、条件筛选、分类汇总、图表生成中的任意一项;④用户要求导出带格式的 Excel 报告或下载链接。仅不用于:不涉及表格数据的纯文本处理、图片分析(使用 sn-da-image-caption)、单个公式计算的简单问答。 OpenSenseNova/SenseNova-Skills MIT large-file-parquet-analysis-and-highlight 当Excel文件总行数超过1万行时,通过转换为Parquet格式提升读取性能,提取目标指标并计算最大值,最后将结果输出为Excel并对特定行进行高亮标注。 OpenSenseNova/SenseNova-Skills MIT excel-conditional-comparison-and-large-file-processing 对比Excel多表中的特定系数并对异常值进行颜色标记。 OpenSenseNova/SenseNova-Skills MIT