batch_convert.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. #!/usr/bin/env python3
  2. """
  3. Batch convert multiple files to Markdown using MarkItDown.
  4. This script demonstrates how to efficiently convert multiple files
  5. in a directory to Markdown format.
  6. """
  7. import argparse
  8. from pathlib import Path
  9. from typing import List, Optional
  10. from markitdown import MarkItDown
  11. from concurrent.futures import ThreadPoolExecutor, as_completed
  12. import sys
  13. def convert_file(md: MarkItDown, file_path: Path, output_dir: Path, verbose: bool = False) -> tuple[bool, str, str]:
  14. """
  15. Convert a single file to Markdown.
  16. Args:
  17. md: MarkItDown instance
  18. file_path: Path to input file
  19. output_dir: Directory for output files
  20. verbose: Print detailed messages
  21. Returns:
  22. Tuple of (success, input_path, message)
  23. """
  24. try:
  25. if verbose:
  26. print(f"Converting: {file_path}")
  27. result = md.convert(str(file_path))
  28. # Create output path
  29. output_file = output_dir / f"{file_path.stem}.md"
  30. # Write content with metadata header
  31. content = f"# {result.title or file_path.stem}\n\n"
  32. content += f"**Source**: {file_path.name}\n"
  33. content += f"**Format**: {file_path.suffix}\n\n"
  34. content += "---\n\n"
  35. content += result.text_content
  36. output_file.write_text(content, encoding='utf-8')
  37. return True, str(file_path), f"✓ Converted to {output_file.name}"
  38. except Exception as e:
  39. return False, str(file_path), f"✗ Error: {str(e)}"
  40. def batch_convert(
  41. input_dir: Path,
  42. output_dir: Path,
  43. extensions: Optional[List[str]] = None,
  44. recursive: bool = False,
  45. workers: int = 4,
  46. verbose: bool = False,
  47. enable_plugins: bool = False
  48. ) -> dict:
  49. """
  50. Batch convert files in a directory.
  51. Args:
  52. input_dir: Input directory
  53. output_dir: Output directory
  54. extensions: List of file extensions to convert (e.g., ['.pdf', '.docx'])
  55. recursive: Search subdirectories
  56. workers: Number of parallel workers
  57. verbose: Print detailed messages
  58. enable_plugins: Enable MarkItDown plugins
  59. Returns:
  60. Dictionary with conversion statistics
  61. """
  62. # Create output directory
  63. output_dir.mkdir(parents=True, exist_ok=True)
  64. # Default extensions if not specified
  65. if extensions is None:
  66. extensions = ['.pdf', '.docx', '.pptx', '.xlsx', '.html', '.jpg', '.png']
  67. # Find files
  68. files = []
  69. if recursive:
  70. for ext in extensions:
  71. files.extend(input_dir.rglob(f"*{ext}"))
  72. else:
  73. for ext in extensions:
  74. files.extend(input_dir.glob(f"*{ext}"))
  75. if not files:
  76. print(f"No files found with extensions: {', '.join(extensions)}")
  77. return {'total': 0, 'success': 0, 'failed': 0}
  78. print(f"Found {len(files)} file(s) to convert")
  79. # Create MarkItDown instance
  80. md = MarkItDown(enable_plugins=enable_plugins)
  81. # Convert files in parallel
  82. results = {
  83. 'total': len(files),
  84. 'success': 0,
  85. 'failed': 0,
  86. 'details': []
  87. }
  88. with ThreadPoolExecutor(max_workers=workers) as executor:
  89. futures = {
  90. executor.submit(convert_file, md, file_path, output_dir, verbose): file_path
  91. for file_path in files
  92. }
  93. for future in as_completed(futures):
  94. success, path, message = future.result()
  95. if success:
  96. results['success'] += 1
  97. else:
  98. results['failed'] += 1
  99. results['details'].append({
  100. 'file': path,
  101. 'success': success,
  102. 'message': message
  103. })
  104. print(message)
  105. return results
  106. def main():
  107. parser = argparse.ArgumentParser(
  108. description="Batch convert files to Markdown using MarkItDown",
  109. formatter_class=argparse.RawDescriptionHelpFormatter,
  110. epilog="""
  111. Examples:
  112. # Convert all PDFs in a directory
  113. python batch_convert.py papers/ output/ --extensions .pdf
  114. # Convert multiple formats recursively
  115. python batch_convert.py documents/ markdown/ --extensions .pdf .docx .pptx -r
  116. # Use 8 parallel workers
  117. python batch_convert.py input/ output/ --workers 8
  118. # Enable plugins
  119. python batch_convert.py input/ output/ --plugins
  120. """
  121. )
  122. parser.add_argument('input_dir', type=Path, help='Input directory')
  123. parser.add_argument('output_dir', type=Path, help='Output directory')
  124. parser.add_argument(
  125. '--extensions', '-e',
  126. nargs='+',
  127. help='File extensions to convert (e.g., .pdf .docx)'
  128. )
  129. parser.add_argument(
  130. '--recursive', '-r',
  131. action='store_true',
  132. help='Search subdirectories recursively'
  133. )
  134. parser.add_argument(
  135. '--workers', '-w',
  136. type=int,
  137. default=4,
  138. help='Number of parallel workers (default: 4)'
  139. )
  140. parser.add_argument(
  141. '--verbose', '-v',
  142. action='store_true',
  143. help='Verbose output'
  144. )
  145. parser.add_argument(
  146. '--plugins', '-p',
  147. action='store_true',
  148. help='Enable MarkItDown plugins'
  149. )
  150. args = parser.parse_args()
  151. # Validate input directory
  152. if not args.input_dir.exists():
  153. print(f"Error: Input directory '{args.input_dir}' does not exist")
  154. sys.exit(1)
  155. if not args.input_dir.is_dir():
  156. print(f"Error: '{args.input_dir}' is not a directory")
  157. sys.exit(1)
  158. # Run batch conversion
  159. results = batch_convert(
  160. input_dir=args.input_dir,
  161. output_dir=args.output_dir,
  162. extensions=args.extensions,
  163. recursive=args.recursive,
  164. workers=args.workers,
  165. verbose=args.verbose,
  166. enable_plugins=args.plugins
  167. )
  168. # Print summary
  169. print("\n" + "="*50)
  170. print("CONVERSION SUMMARY")
  171. print("="*50)
  172. print(f"Total files: {results['total']}")
  173. print(f"Successful: {results['success']}")
  174. print(f"Failed: {results['failed']}")
  175. print(f"Success rate: {results['success']/results['total']*100:.1f}%" if results['total'] > 0 else "N/A")
  176. # Show failed files if any
  177. if results['failed'] > 0:
  178. print("\nFailed conversions:")
  179. for detail in results['details']:
  180. if not detail['success']:
  181. print(f" - {detail['file']}: {detail['message']}")
  182. sys.exit(0 if results['failed'] == 0 else 1)
  183. if __name__ == '__main__':
  184. main()