convert_literature.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. #!/usr/bin/env python3
  2. """
  3. Convert scientific literature PDFs to Markdown for analysis and review.
  4. This script is specifically designed for converting academic papers,
  5. organizing them, and preparing them for literature review workflows.
  6. """
  7. import argparse
  8. import json
  9. import re
  10. import sys
  11. from pathlib import Path
  12. from typing import List, Dict, Optional
  13. from markitdown import MarkItDown
  14. from datetime import datetime
  15. def extract_metadata_from_filename(filename: str) -> Dict[str, str]:
  16. """
  17. Try to extract metadata from filename.
  18. Supports patterns like: Author_Year_Title.pdf
  19. """
  20. metadata = {}
  21. # Remove extension
  22. name = Path(filename).stem
  23. # Try to extract year
  24. year_match = re.search(r'\b(19|20)\d{2}\b', name)
  25. if year_match:
  26. metadata['year'] = year_match.group()
  27. # Split by underscores or dashes
  28. parts = re.split(r'[_\-]', name)
  29. if len(parts) >= 2:
  30. metadata['author'] = parts[0].replace('_', ' ')
  31. metadata['title'] = ' '.join(parts[1:]).replace('_', ' ')
  32. else:
  33. metadata['title'] = name.replace('_', ' ')
  34. return metadata
  35. def convert_paper(
  36. md: MarkItDown,
  37. input_file: Path,
  38. output_dir: Path,
  39. organize_by_year: bool = False
  40. ) -> tuple[bool, Dict]:
  41. """
  42. Convert a single paper to Markdown with metadata extraction.
  43. Args:
  44. md: MarkItDown instance
  45. input_file: Path to PDF file
  46. output_dir: Output directory
  47. organize_by_year: Organize into year subdirectories
  48. Returns:
  49. Tuple of (success, metadata_dict)
  50. """
  51. try:
  52. print(f"Converting: {input_file.name}")
  53. # Convert to Markdown
  54. result = md.convert(str(input_file))
  55. # Extract metadata from filename
  56. metadata = extract_metadata_from_filename(input_file.name)
  57. metadata['source_file'] = input_file.name
  58. metadata['converted_date'] = datetime.now().isoformat()
  59. # Try to extract title from content if not in filename
  60. if 'title' not in metadata and result.title:
  61. metadata['title'] = result.title
  62. # Create output path
  63. if organize_by_year and 'year' in metadata:
  64. output_subdir = output_dir / metadata['year']
  65. output_subdir.mkdir(parents=True, exist_ok=True)
  66. else:
  67. output_subdir = output_dir
  68. output_subdir.mkdir(parents=True, exist_ok=True)
  69. output_file = output_subdir / f"{input_file.stem}.md"
  70. # Create formatted Markdown with front matter
  71. content = "---\n"
  72. content += f"title: \"{metadata.get('title', input_file.stem)}\"\n"
  73. if 'author' in metadata:
  74. content += f"author: \"{metadata['author']}\"\n"
  75. if 'year' in metadata:
  76. content += f"year: {metadata['year']}\n"
  77. content += f"source: \"{metadata['source_file']}\"\n"
  78. content += f"converted: \"{metadata['converted_date']}\"\n"
  79. content += "---\n\n"
  80. # Add title
  81. content += f"# {metadata.get('title', input_file.stem)}\n\n"
  82. # Add metadata section
  83. content += "## Document Information\n\n"
  84. if 'author' in metadata:
  85. content += f"**Author**: {metadata['author']}\n"
  86. if 'year' in metadata:
  87. content += f"**Year**: {metadata['year']}\n"
  88. content += f"**Source File**: {metadata['source_file']}\n"
  89. content += f"**Converted**: {metadata['converted_date']}\n\n"
  90. content += "---\n\n"
  91. # Add content
  92. content += result.text_content
  93. # Write to file
  94. output_file.write_text(content, encoding='utf-8')
  95. print(f"✓ Saved to: {output_file}")
  96. return True, metadata
  97. except Exception as e:
  98. print(f"✗ Error converting {input_file.name}: {str(e)}")
  99. return False, {'source_file': input_file.name, 'error': str(e)}
  100. def create_index(papers: List[Dict], output_dir: Path):
  101. """Create an index/catalog of all converted papers."""
  102. # Sort by year (if available) and title
  103. papers_sorted = sorted(
  104. papers,
  105. key=lambda x: (x.get('year', '9999'), x.get('title', ''))
  106. )
  107. # Create Markdown index
  108. index_content = "# Literature Review Index\n\n"
  109. index_content += f"**Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
  110. index_content += f"**Total Papers**: {len(papers)}\n\n"
  111. index_content += "---\n\n"
  112. # Group by year
  113. by_year = {}
  114. for paper in papers_sorted:
  115. year = paper.get('year', 'Unknown')
  116. if year not in by_year:
  117. by_year[year] = []
  118. by_year[year].append(paper)
  119. # Write by year
  120. for year in sorted(by_year.keys()):
  121. index_content += f"## {year}\n\n"
  122. for paper in by_year[year]:
  123. title = paper.get('title', paper.get('source_file', 'Unknown'))
  124. author = paper.get('author', 'Unknown Author')
  125. source = paper.get('source_file', '')
  126. # Create link to markdown file
  127. md_file = Path(source).stem + ".md"
  128. if 'year' in paper and paper['year'] != 'Unknown':
  129. md_file = f"{paper['year']}/{md_file}"
  130. index_content += f"- **{title}**\n"
  131. index_content += f" - Author: {author}\n"
  132. index_content += f" - Source: {source}\n"
  133. index_content += f" - [Read Markdown]({md_file})\n\n"
  134. # Write index
  135. index_file = output_dir / "INDEX.md"
  136. index_file.write_text(index_content, encoding='utf-8')
  137. print(f"\n✓ Created index: {index_file}")
  138. # Also create JSON catalog
  139. catalog_file = output_dir / "catalog.json"
  140. with open(catalog_file, 'w', encoding='utf-8') as f:
  141. json.dump(papers_sorted, f, indent=2, ensure_ascii=False)
  142. print(f"✓ Created catalog: {catalog_file}")
  143. def main():
  144. parser = argparse.ArgumentParser(
  145. description="Convert scientific literature PDFs to Markdown",
  146. formatter_class=argparse.RawDescriptionHelpFormatter,
  147. epilog="""
  148. Examples:
  149. # Convert all PDFs in a directory
  150. python convert_literature.py papers/ output/
  151. # Organize by year
  152. python convert_literature.py papers/ output/ --organize-by-year
  153. # Create index of all papers
  154. python convert_literature.py papers/ output/ --create-index
  155. Filename Conventions:
  156. For best results, name your PDFs using this pattern:
  157. Author_Year_Title.pdf
  158. Examples:
  159. Smith_2023_Machine_Learning_Applications.pdf
  160. Jones_2022_Climate_Change_Analysis.pdf
  161. """
  162. )
  163. parser.add_argument('input_dir', type=Path, help='Directory with PDF files')
  164. parser.add_argument('output_dir', type=Path, help='Output directory for Markdown files')
  165. parser.add_argument(
  166. '--organize-by-year', '-y',
  167. action='store_true',
  168. help='Organize output into year subdirectories'
  169. )
  170. parser.add_argument(
  171. '--create-index', '-i',
  172. action='store_true',
  173. help='Create an index/catalog of all papers'
  174. )
  175. parser.add_argument(
  176. '--recursive', '-r',
  177. action='store_true',
  178. help='Search subdirectories recursively'
  179. )
  180. args = parser.parse_args()
  181. # Validate input
  182. if not args.input_dir.exists():
  183. print(f"Error: Input directory '{args.input_dir}' does not exist")
  184. sys.exit(1)
  185. if not args.input_dir.is_dir():
  186. print(f"Error: '{args.input_dir}' is not a directory")
  187. sys.exit(1)
  188. # Find PDF files
  189. if args.recursive:
  190. pdf_files = list(args.input_dir.rglob("*.pdf"))
  191. else:
  192. pdf_files = list(args.input_dir.glob("*.pdf"))
  193. if not pdf_files:
  194. print("No PDF files found")
  195. sys.exit(1)
  196. print(f"Found {len(pdf_files)} PDF file(s)")
  197. # Create MarkItDown instance
  198. md = MarkItDown()
  199. # Convert all papers
  200. results = []
  201. success_count = 0
  202. for pdf_file in pdf_files:
  203. success, metadata = convert_paper(
  204. md,
  205. pdf_file,
  206. args.output_dir,
  207. args.organize_by_year
  208. )
  209. if success:
  210. success_count += 1
  211. results.append(metadata)
  212. # Create index if requested
  213. if args.create_index and results:
  214. create_index(results, args.output_dir)
  215. # Print summary
  216. print("\n" + "="*50)
  217. print("CONVERSION SUMMARY")
  218. print("="*50)
  219. print(f"Total papers: {len(pdf_files)}")
  220. print(f"Successful: {success_count}")
  221. print(f"Failed: {len(pdf_files) - success_count}")
  222. print(f"Success rate: {success_count/len(pdf_files)*100:.1f}%")
  223. sys.exit(0 if success_count == len(pdf_files) else 1)
  224. if __name__ == '__main__':
  225. main()