all repos — newspaper @ 613924b643ce6e5ed2bee9ce6819d1c5695f3d74

Unnamed repository; edit this file 'description' to name the repository.

generate_magazine.py (view raw)

  1#!/usr/bin/env python3
  2"""
  3Newspaper Magazine Generator
  4Proof of concept: generates a magazine-style PDF from RSS feeds
  5"""
  6
  7import re
  8import subprocess
  9from concurrent.futures import ThreadPoolExecutor
 10from pathlib import Path
 11
 12SCRIPT_DIR = Path(__file__).parent
 13LLM_AGGREGATOR = SCRIPT_DIR.parent / "llm_aggregator" / "llm_aggregator"
 14OUTPUT_DIR = SCRIPT_DIR / "output"
 15TEX_FILE = OUTPUT_DIR / "magazine.tex"
 16PDF_FILE = OUTPUT_DIR / "magazine.pdf"
 17
 18# Strip think tags
 19STRIP_TAG_START = "<think>"
 20STRIP_TAG_END = "</think>"
 21STRIP_TAG_REPLACEMENT = ""
 22
 23# Base prompt ending
 24PROMPT_ENDING = r"""Output in LaTeX format suitable for an editorial section.
 25Use asterisk section headers (\section*{...}) to prevent numbering. Use the
 26correct 1st January 2001 date format. Use English; not American dialect (color
 27-> colour, formalize -> formalise, etc.). Use ``...'' for quotation marks,
 28\emph{...} for italics and \textbf{...} for bold text. Do not attempt
 29citations."""
 30
 31CATEGORIES = [
 32    {
 33        "name": "LINUX",
 34        "icon": r"\faLinux",
 35        "feed": SCRIPT_DIR / "feeds" / "linux.txt",
 36        "prompt": f"""Summarise the latest Linux kernel discussions and
 37        technical developments from the LKML feed. Focus on significant
 38        patches, kernel updates, and developer discussions. {PROMPT_ENDING}""",
 39    },
 40    {
 41        "name": "FREE SOFTWARE",
 42        "icon": r"\faCogs",
 43        "feed": SCRIPT_DIR / "feeds" / "free_software.txt",
 44        "prompt": f"""Summarise the latest news from the free and open source
 45        software world. Cover new releases, project updates, and community
 46        news. {PROMPT_ENDING}""",
 47    },
 48    {
 49        "name": r"AI \& SCIENCE",
 50        "icon": r"\faFlask",
 51        "feed": SCRIPT_DIR / "feeds" / "ai_science.txt",
 52        "prompt": f"""Summarise the latest news regarding science and AI. Pay
 53        attention to esoteric and difficult topics, rather than what is likely
 54        to be easy pop-science without substance. {PROMPT_ENDING}""",
 55    },
 56    {
 57        "name": "POLAND",
 58        "icon": r"\faFlag",
 59        "feed": SCRIPT_DIR / "feeds" / "poland.txt",
 60        "prompt": f"""Summarise today's top news from Poland covering politics,
 61        finance, and current events. Include major stories and their
 62        significance. {PROMPT_ENDING}""",
 63    },
 64    {
 65        "name": "ESOTERIC",
 66        "icon": r"\faUserSecret",
 67        "feed": SCRIPT_DIR / "feeds" / "esoteric.txt",
 68        "prompt": f"""Summarise news from sources that are not mainstream,
 69        regarding topics not often discussed in the public square. The readers
 70        enjoy this section, so maintain a supportive tone, rather than
 71        accusative. Seek out topics that specifically challenge popular
 72        preconceptions. Do not write disclaimers or observations.
 73        {PROMPT_ENDING}""",
 74    },
 75]
 76
 77
 78def strip_tags(content: str) -> str:
 79    """Strip content between (and including) two tags. Leaves placeholder."""
 80    if STRIP_TAG_START and STRIP_TAG_END:
 81        pattern = f"{re.escape(STRIP_TAG_START)}.*?{re.escape(STRIP_TAG_END)}"
 82        return re.sub(pattern, STRIP_TAG_REPLACEMENT, content, flags=re.DOTALL)
 83    return content
 84
 85
 86def run_llm_aggregator(category: dict) -> dict:
 87    """Run llm_aggregator for a category and return the result."""
 88    output_file = OUTPUT_DIR / f"{category['name'].lower()}.txt"
 89
 90    cmd = [
 91        str(LLM_AGGREGATOR),
 92        "-f", str(category["feed"]),
 93        "-p", category["prompt"],
 94        "-P",
 95        "--output-file", str(output_file),
 96        "-v",
 97    ]
 98
 99    print(f"📰 Processing: {category['name']}")
100    result = subprocess.run(cmd, capture_output=True, text=True)
101
102    if result.returncode != 0:
103        print(f"   ❌ Error: {result.stderr}")
104        raise RuntimeError(f"llm_aggregator failed for {category['name']}")
105
106    content = output_file.read_text()
107    content = strip_tags(content)
108
109    print(f"   ✓ Done: {category['name']}")
110    return {"name": category["name"], "icon": category["icon"], "content": content}
111
112
113def generate_latex(content_files: list[dict]) -> None:
114    """Generate the LaTeX document."""
115    latex = r"""\documentclass[12pt,a4paper]{article}
116\usepackage{fancyhdr, fontawesome, fontspec, graphicx, geometry, sectsty, setspace, parskip}
117\usepackage[UKenglish]{isodate}
118\setmainfont[Ligatures=TeX]{Liberation Sans}
119\geometry{margin=2.5cm}
120\sectionfont{\centering\bfseries\scshape}
121\subsectionfont{\large\bfseries}
122\pagestyle{fancy}
123\fancyhf{}
124\fancyhead[C]{\today}
125\fancyfoot[C]{\thepage}
126
127\begin{document}
128\onehalfspacing
129
130\vspace*{2cm}
131{\LARGE\bfseries The Daily Digest}\\[0.5em]
132{\Large \today}\\[2em]
133\hrule
134\vspace{2em}
135"""
136
137    for item in content_files:
138        name = item["name"]
139        icon = item["icon"]
140        content = item["content"]
141
142        latex += f"{{\\centering\\fontsize{{64}}{{12}}{icon}\\par}}\n"
143        latex += f"\\section*{{{name}}}\n"
144        latex += content
145        latex += "\n\n\\vspace{1.5em}\n\n"
146
147    latex += r"\end{document}"
148
149    with open(TEX_FILE, "w") as f:
150        f.write(latex)
151
152    print(f"   Created: {TEX_FILE}")
153
154
155def compile_pdf() -> bool:
156    """Compile LaTeX to PDF."""
157    print()
158    print("🔄 Compiling PDF...")
159
160    result = subprocess.run(
161        ["xelatex", "-interaction=batchmode", "magazine.tex"],
162        cwd=OUTPUT_DIR,
163        capture_output=True,
164    )
165
166    return PDF_FILE.exists()
167
168
169def main():
170    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
171
172    print("🗞️  Newspaper Magazine Generator")
173    print("===================================")
174    print()
175
176    # Run all feeds through llm_aggregator in parallel
177    print("📰 Processing feeds in parallel...")
178    print()
179    with ThreadPoolExecutor(max_workers=8) as executor:
180        content_files = list(executor.map(run_llm_aggregator, CATEGORIES))
181
182    print()
183    print("📄 Generating LaTeX magazine...")
184    print()
185
186    generate_latex(content_files)
187    print()
188
189    if compile_pdf():
190        print("✅ Magazine generated successfully!")
191        print()
192        print(f"   PDF: {PDF_FILE}")
193        print()
194        print("📖 Opening PDF viewer...")
195        subprocess.run(["xdg-open", str(PDF_FILE)])
196    else:
197        print("❌ PDF compilation failed")
198        return 1
199
200    return 0
201
202
203if __name__ == "__main__":
204    exit(main())