File size: 3,713 Bytes
01abd01
 
 
 
 
 
 
f6a6455
 
01abd01
f6a6455
 
 
 
 
 
01abd01
 
 
f6a6455
 
 
 
 
 
 
01abd01
 
 
 
 
 
 
 
 
f6a6455
 
 
 
01abd01
 
f6a6455
01abd01
f6a6455
 
 
01abd01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f6a6455
 
 
01abd01
 
f6a6455
 
 
 
 
 
 
 
 
 
 
 
01abd01
f6a6455
 
 
 
 
01abd01
 
 
 
 
 
 
 
 
 
 
 
 
 
f6a6455
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
"""Stage 9: assemble the .docx tutorial (text + captioned screenshots).

Layout follows answer-engine best practices: H1 title, an SEO metadata line
(slug / meta description / last-updated), an answer-first "Quick answer" block, the
intro, H2 step sections with captioned screenshots, an FAQ section, and a sources/
trust footer citing the source video.
"""
from __future__ import annotations

import datetime
import os

from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Inches, Pt, RGBColor

_GREY = RGBColor(0x66, 0x66, 0x66)
_LIGHT = RGBColor(0x88, 0x88, 0x88)


def _fmt_ts(seconds: float) -> str:
    m, s = divmod(int(seconds), 60)
    h, m = divmod(m, 60)
    return f"{h:d}:{m:02d}:{s:02d}" if h else f"{m:d}:{s:02d}"


def _grey_line(doc, text: str, size: int = 9, color: RGBColor = _GREY):
    p = doc.add_paragraph()
    run = p.add_run(text)
    run.italic = True
    run.font.size = Pt(size)
    run.font.color.rgb = color
    return p


def build_docx(tutorial: dict, selected: dict[int, dict], captions: dict[int, str],
               out_path: str, source_url: str = "") -> str:
    """Write the tutorial to ``out_path`` and return it.

    ``tutorial`` = {title, slug, meta_description, answer, intro, steps, faqs};
    ``selected`` = {step_idx: {time, path}}; ``captions`` = {step_idx: caption}.
    """
    today = datetime.date.today().isoformat()
    doc = Document()
    doc.add_heading(tutorial["title"], level=0)

    # SEO metadata line (slug + last-updated) and meta description.
    meta_bits = []
    if tutorial.get("slug"):
        meta_bits.append(f"Slug: /{tutorial['slug']}/")
    meta_bits.append(f"Last updated: {today}")
    _grey_line(doc, "  •  ".join(meta_bits))
    if tutorial.get("meta_description"):
        _grey_line(doc, f"Meta description: {tutorial['meta_description']}")

    # Answer-first block (quotable by AI assistants).
    if tutorial.get("answer"):
        doc.add_heading("Quick answer", level=2)
        ans = doc.add_paragraph()
        ans.add_run(tutorial["answer"]).bold = True

    if tutorial.get("intro"):
        doc.add_paragraph(tutorial["intro"])

    # Step sections.
    for i, step in enumerate(tutorial["steps"]):
        doc.add_heading(f"{i + 1}. {step['heading']}", level=1)
        if step.get("body"):
            doc.add_paragraph(step["body"])

        sel = selected.get(i)
        if sel and os.path.exists(sel["path"]):
            pic_par = doc.add_paragraph()
            pic_par.alignment = WD_ALIGN_PARAGRAPH.CENTER
            pic_par.add_run().add_picture(sel["path"], width=Inches(6))

            cap_par = doc.add_paragraph()
            cap_par.alignment = WD_ALIGN_PARAGRAPH.CENTER
            cap_run = cap_par.add_run(f"{captions.get(i, '')}  ")
            cap_run.italic = True
            cap_run.font.size = Pt(9)
            ts_run = cap_par.add_run(f"(@ {_fmt_ts(sel['time'])})")
            ts_run.italic = True
            ts_run.font.size = Pt(9)
            ts_run.font.color.rgb = _LIGHT

    # FAQ section (visible Q&A — AEO/FAQPage-friendly).
    if tutorial.get("faqs"):
        doc.add_heading("Frequently asked questions", level=1)
        for f in tutorial["faqs"]:
            doc.add_heading(f["q"], level=2)
            doc.add_paragraph(f["a"])

    # Sources / trust footer (E-E-A-T: citation + freshness).
    doc.add_heading("Sources", level=1)
    if source_url:
        _grey_line(doc, f"Adapted from the source video: {source_url}", color=_GREY)
    _grey_line(doc, f"Generated and last updated: {today}.", color=_GREY)

    os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True)
    doc.save(out_path)
    return out_path