276 lines · 8.9 KB
Raw Download
1
#!/usr/bin/env python3
2
"""
3
PDF Review Script
4
Identifies PDFs containing specified keywords, copies them to a review folder,
5
and generates a report with page numbers where keywords were found.
6
"""
7
8
import os
9
import shutil
10
import subprocess
11
import sys
12
from pathlib import Path
13
import PyPDF2
14
import fitz  # PyMuPDF
15
import re
16
from collections import defaultdict
17
import argparse
18
19
20
class PDFReviewScript:
21
    def __init__(self, input_dir, keywords_file, review_folder="review"):
22
        self.input_dir = Path(input_dir)
23
        self.keywords_file = Path(keywords_file)
24
        self.review_folder = Path(review_folder)
25
        self.keywords = self.load_keywords()
26
        self.matches = defaultdict(list)
27
28
        # Create review folder if it doesn't exist
29
        self.review_folder.mkdir(exist_ok=True)
30
31
    def load_keywords(self):
32
        """Load keywords from file (one per line)"""
33
        try:
34
            with open(self.keywords_file, "r", encoding="utf-8") as f:
35
                keywords = [line.strip().lower() for line in f if line.strip()]
36
            print(f"Loaded {len(keywords)} keywords from {self.keywords_file}")
37
            return keywords
38
        except FileNotFoundError:
39
            print(f"Keywords file {self.keywords_file} not found!")
40
            sys.exit(1)
41
42
    def run_ocr_on_pdf(self, pdf_path):
43
        """Run OCRmyPDF on a PDF file"""
44
        output_path = pdf_path.with_suffix(".ocr.pdf")
45
        temp_path = pdf_path.with_suffix(".temp.pdf")
46
47
        try:
48
            # Run OCRmyPDF
49
            cmd = [
50
                "ocrmypdf",
51
                "--skip-text",  # Only OCR pages without text
52
                "--optimize",
53
                "1",
54
                "--output-type",
55
                "pdf",
56
                str(pdf_path),
57
                str(temp_path),
58
            ]
59
60
            result = subprocess.run(cmd, capture_output=True, text=True)
61
62
            if result.returncode == 0:
63
                # Replace original with OCR'd version
64
                shutil.move(str(temp_path), str(pdf_path))
65
                print(f"OCR completed for {pdf_path.name}")
66
                return True
67
            else:
68
                print(f"OCR failed for {pdf_path.name}: {result.stderr}")
69
                # Clean up temp file if it exists
70
                if temp_path.exists():
71
                    temp_path.unlink()
72
                return False
73
74
        except FileNotFoundError:
75
            print("OCRmyPDF not found. Please install it first:")
76
            print("pip install ocrmypdf")
77
            sys.exit(1)
78
        except Exception as e:
79
            print(f"Error running OCR on {pdf_path.name}: {e}")
80
            return False
81
82
    def extract_text_from_pdf(self, pdf_path):
83
        """Extract text from PDF using PyMuPDF"""
84
        try:
85
            doc = fitz.open(str(pdf_path))
86
            pages_text = {}
87
88
            for page_num in range(len(doc)):
89
                page = doc.load_page(page_num)
90
                text = page.get_text().lower()
91
                pages_text[page_num + 1] = text  # Page numbers start from 1
92
93
            doc.close()
94
            return pages_text
95
96
        except Exception as e:
97
            print(f"Error extracting text from {pdf_path.name}: {e}")
98
            return {}
99
100
    def find_keywords_in_text(self, pages_text, pdf_name):
101
        """Find keywords in extracted text and return matches with page numbers"""
102
        matches = []
103
104
        for page_num, text in pages_text.items():
105
            page_matches = []
106
            for keyword in self.keywords:
107
                # Use word boundaries to match whole words
108
                pattern = r"\b" + re.escape(keyword) + r"\b"
109
                if re.search(pattern, text, re.IGNORECASE):
110
                    page_matches.append(keyword)
111
112
            if page_matches:
113
                matches.append({"page": page_num, "keywords": page_matches})
114
115
        return matches
116
117
    def process_pdf(self, pdf_path):
118
        """Process a single PDF file"""
119
        print(f"Processing {pdf_path.name}...")
120
121
        # Run OCR first
122
        ocr_success = self.run_ocr_on_pdf(pdf_path)
123
124
        # Extract text
125
        pages_text = self.extract_text_from_pdf(pdf_path)
126
127
        if not pages_text:
128
            print(f"No text extracted from {pdf_path.name}")
129
            return False
130
131
        # Find keyword matches
132
        matches = self.find_keywords_in_text(pages_text, pdf_path.name)
133
134
        if matches:
135
            self.matches[pdf_path.name] = matches
136
137
            # Copy file to review folder
138
            review_path = self.review_folder / pdf_path.name
139
            shutil.copy2(str(pdf_path), str(review_path))
140
            print(f"Copied {pdf_path.name} to review folder")
141
142
            return True
143
144
        return False
145
146
    def generate_report(self):
147
        """Generate a text report of matches"""
148
        report_path = self.review_folder / "review_report.txt"
149
150
        with open(report_path, "w", encoding="utf-8") as f:
151
            f.write("PDF REVIEW REPORT\n")
152
            f.write("=" * 50 + "\n\n")
153
            f.write(f"Generated on: {Path().cwd()}\n")
154
            f.write(f"Keywords searched: {len(self.keywords)}\n")
155
            f.write(f"PDFs requiring review: {len(self.matches)}\n\n")
156
157
            for pdf_name, matches in self.matches.items():
158
                f.write(f"FILE: {pdf_name}\n")
159
                f.write("-" * 40 + "\n")
160
161
                for match in matches:
162
                    f.write(f"  Page {match['page']}: {', '.join(match['keywords'])}\n")
163
164
                f.write("\n")
165
166
            # Summary by keyword
167
            f.write("\nKEYWORD SUMMARY\n")
168
            f.write("=" * 30 + "\n")
169
170
            keyword_counts = defaultdict(int)
171
            for matches in self.matches.values():
172
                for match in matches:
173
                    for keyword in match["keywords"]:
174
                        keyword_counts[keyword] += 1
175
176
            for keyword, count in sorted(keyword_counts.items()):
177
                f.write(f"{keyword}: {count} occurrences\n")
178
179
        print(f"Report generated: {report_path}")
180
181
    def run(self):
182
        """Main execution method"""
183
        print(f"Starting PDF review process...")
184
        print(f"Input directory: {self.input_dir}")
185
        print(f"Review folder: {self.review_folder}")
186
187
        # Find all PDF files
188
        pdf_files = list(self.input_dir.glob("*.pdf"))
189
190
        if not pdf_files:
191
            print("No PDF files found in input directory!")
192
            return
193
194
        print(f"Found {len(pdf_files)} PDF files")
195
196
        processed_count = 0
197
        matched_count = 0
198
199
        for pdf_file in pdf_files:
200
            if self.process_pdf(pdf_file):
201
                matched_count += 1
202
            processed_count += 1
203
204
        print(f"\nProcessing complete!")
205
        print(f"Processed: {processed_count} files")
206
        print(f"Matches found: {matched_count} files")
207
208
        if matched_count > 0:
209
            self.generate_report()
210
        else:
211
            print("No keyword matches found in any PDF files.")
212
213
214
def create_sample_keywords_file():
215
    """Create a sample keywords file"""
216
    keywords = [
217
        "confidential",
218
        "urgent",
219
        "review required",
220
        "attention",
221
        "important",
222
        "deadline",
223
        "action needed",
224
    ]
225
226
    with open("pdf-scan-words-keywords.txt", "w") as f:
227
        for keyword in keywords:
228
            f.write(keyword + "\n")
229
230
    print("Sample keywords.txt file created")
231
232
233
def main():
234
    parser = argparse.ArgumentParser(description="PDF Review Script")
235
    parser.add_argument("input_dir", nargs="?", help="Directory containing PDF files")
236
    parser.add_argument(
237
        "keywords_file", nargs="?", help="File containing keywords (one per line)"
238
    )
239
    parser.add_argument(
240
        "--review-folder", default="review", help="Output folder for matched PDFs"
241
    )
242
    parser.add_argument(
243
        "--create-sample", action="store_true", help="Create sample keywords file"
244
    )
245
246
    args = parser.parse_args()
247
248
    # Handle create-sample case first
249
    if args.create_sample:
250
        create_sample_keywords_file()
251
        return
252
253
    # Validate that required arguments are provided for normal operation
254
    if not args.input_dir or not args.keywords_file:
255
        parser.error(
256
            "input_dir and keywords_file are required unless using --create-sample"
257
        )
258
259
    # Validate inputs
260
    if not Path(args.input_dir).exists():
261
        print(f"Input directory {args.input_dir} does not exist!")
262
        sys.exit(1)
263
264
    if not Path(args.keywords_file).exists():
265
        print(f"Keywords file {args.keywords_file} does not exist!")
266
        print("Use --create-sample to create a sample keywords file")
267
        sys.exit(1)
268
269
    # Run the script
270
    script = PDFReviewScript(args.input_dir, args.keywords_file, args.review_folder)
271
    script.run()
272
273
274
if __name__ == "__main__":
275
    main()
276