Add File
This commit is contained in:
511
src/landppt/services/pdf_to_pptx_converter.py
Normal file
511
src/landppt/services/pdf_to_pptx_converter.py
Normal file
@@ -0,0 +1,511 @@
|
|||||||
|
"""
|
||||||
|
PDF to PPTX Converter Service for LandPPT
|
||||||
|
Uses Apryse SDK to convert PDF files to PowerPoint presentations
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import asyncio
|
||||||
|
import tempfile
|
||||||
|
import logging
|
||||||
|
import platform
|
||||||
|
import zipfile
|
||||||
|
import tarfile
|
||||||
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SDKDownloadManager:
|
||||||
|
"""Manages automatic download and extraction of Apryse SDK files"""
|
||||||
|
|
||||||
|
# SDK download URLs for different platforms
|
||||||
|
SDK_URLS = {
|
||||||
|
'windows': 'https://www.pdftron.com/downloads/StructuredOutputWindows.zip',
|
||||||
|
'linux': 'https://pdftron.s3.amazonaws.com/downloads/StructuredOutputLinux.tar.gz',
|
||||||
|
'macos': 'https://pdftron.s3.amazonaws.com/downloads/StructuredOutputMac.zip'
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, lib_dir: Path):
|
||||||
|
self.lib_dir = lib_dir
|
||||||
|
self.platform_name = self._get_platform_name()
|
||||||
|
self.platform_dir = lib_dir / self._get_platform_dir_name()
|
||||||
|
|
||||||
|
def _get_platform_name(self) -> str:
|
||||||
|
"""Get normalized platform name"""
|
||||||
|
system = platform.system().lower()
|
||||||
|
if system == 'windows':
|
||||||
|
return 'windows'
|
||||||
|
elif system == 'linux':
|
||||||
|
return 'linux'
|
||||||
|
elif system == 'darwin':
|
||||||
|
return 'macos'
|
||||||
|
else:
|
||||||
|
# Default to linux for unknown platforms
|
||||||
|
return 'linux'
|
||||||
|
|
||||||
|
def _get_platform_dir_name(self) -> str:
|
||||||
|
"""Get platform directory name in lib folder"""
|
||||||
|
if self.platform_name == 'windows':
|
||||||
|
return 'Windows'
|
||||||
|
elif self.platform_name == 'linux':
|
||||||
|
return 'Linux'
|
||||||
|
elif self.platform_name == 'macos':
|
||||||
|
return 'MacOS'
|
||||||
|
else:
|
||||||
|
return 'Linux'
|
||||||
|
|
||||||
|
def is_sdk_available(self) -> bool:
|
||||||
|
"""Check if SDK files are available for current platform"""
|
||||||
|
if not self.platform_dir.exists():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check for key SDK files based on platform
|
||||||
|
if self.platform_name == 'windows':
|
||||||
|
# Windows SDK has nested structure: Windows/Lib/Windows/StructuredOutput.exe
|
||||||
|
nested_windows_dir = self.platform_dir / 'Lib' / 'Windows'
|
||||||
|
required_files = [nested_windows_dir / 'StructuredOutput.exe']
|
||||||
|
else:
|
||||||
|
required_files = [self.platform_dir / 'StructuredOutput']
|
||||||
|
|
||||||
|
for file_path in required_files:
|
||||||
|
if not file_path.exists():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def download_and_extract_sdk(self) -> bool:
|
||||||
|
"""Download and extract SDK for current platform"""
|
||||||
|
if self.platform_name not in self.SDK_URLS:
|
||||||
|
logger.error(f"No SDK download URL available for platform: {self.platform_name}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
url = self.SDK_URLS[self.platform_name]
|
||||||
|
logger.info(f"Downloading SDK for {self.platform_name} from: {url}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create lib directory if it doesn't exist
|
||||||
|
self.lib_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Download the file
|
||||||
|
response = requests.get(url, stream=True, timeout=300)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Determine file extension and create temporary file
|
||||||
|
if url.endswith('.zip'):
|
||||||
|
suffix = '.zip'
|
||||||
|
elif url.endswith('.tar.gz'):
|
||||||
|
suffix = '.tar.gz'
|
||||||
|
else:
|
||||||
|
suffix = '.zip' # Default to zip
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as temp_file:
|
||||||
|
temp_path = temp_file.name
|
||||||
|
|
||||||
|
# Download with progress logging
|
||||||
|
total_size = int(response.headers.get('content-length', 0))
|
||||||
|
downloaded = 0
|
||||||
|
|
||||||
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
|
if chunk:
|
||||||
|
temp_file.write(chunk)
|
||||||
|
downloaded += len(chunk)
|
||||||
|
|
||||||
|
if total_size > 0:
|
||||||
|
progress = (downloaded / total_size) * 100
|
||||||
|
if downloaded % (1024 * 1024) == 0: # Log every MB
|
||||||
|
logger.info(f"Download progress: {progress:.1f}%")
|
||||||
|
|
||||||
|
logger.info(f"Download completed: {temp_path}")
|
||||||
|
|
||||||
|
# Extract the downloaded file
|
||||||
|
success = self._extract_file(temp_path)
|
||||||
|
|
||||||
|
# Clean up temporary file
|
||||||
|
try:
|
||||||
|
os.unlink(temp_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to download SDK: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _extract_file(self, file_path: str) -> bool:
|
||||||
|
"""Extract downloaded SDK file"""
|
||||||
|
try:
|
||||||
|
logger.info(f"Extracting SDK to: {self.platform_dir}")
|
||||||
|
|
||||||
|
# Create platform directory
|
||||||
|
self.platform_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
if file_path.endswith('.zip'):
|
||||||
|
with zipfile.ZipFile(file_path, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(self.platform_dir)
|
||||||
|
elif file_path.endswith('.tar.gz'):
|
||||||
|
with tarfile.open(file_path, 'r:gz') as tar_ref:
|
||||||
|
tar_ref.extractall(self.platform_dir)
|
||||||
|
else:
|
||||||
|
logger.error(f"Unsupported file format: {file_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.info("SDK extraction completed successfully")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to extract SDK: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def ensure_sdk_available(self) -> bool:
|
||||||
|
"""Ensure SDK is available, download if necessary"""
|
||||||
|
if self.is_sdk_available():
|
||||||
|
logger.info(f"SDK already available for {self.platform_name}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
logger.info(f"SDK not found for {self.platform_name}, downloading...")
|
||||||
|
return self.download_and_extract_sdk()
|
||||||
|
|
||||||
|
|
||||||
|
class PDFToPPTXConverter:
|
||||||
|
"""Converts PDF files to PPTX using Apryse SDK"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._sdk_initialized = False
|
||||||
|
self._sdk_available = None
|
||||||
|
|
||||||
|
# Initialize SDK download manager
|
||||||
|
current_file = Path(__file__).resolve()
|
||||||
|
project_root = current_file.parent.parent.parent.parent # Go up from services -> landppt -> src -> project_root
|
||||||
|
lib_dir = project_root / "lib"
|
||||||
|
self.sdk_manager = SDKDownloadManager(lib_dir)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def license_key(self):
|
||||||
|
"""Dynamically get license key to ensure latest config"""
|
||||||
|
from ..core.config import ai_config
|
||||||
|
return ai_config.apryse_license_key or 'your_apryse_license_key_here'
|
||||||
|
|
||||||
|
def _check_sdk_availability(self) -> bool:
|
||||||
|
"""Check if Apryse SDK is available and can be initialized"""
|
||||||
|
if self._sdk_available is not None:
|
||||||
|
return self._sdk_available
|
||||||
|
|
||||||
|
# First, ensure SDK files are downloaded
|
||||||
|
if not self.sdk_manager.ensure_sdk_available():
|
||||||
|
self._sdk_available = False
|
||||||
|
logger.error("Failed to download or extract SDK files")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
from apryse_sdk.PDFNetPython import PDFNet, StructuredOutputModule, Convert
|
||||||
|
self._sdk_available = True
|
||||||
|
logger.info("Apryse SDK is available")
|
||||||
|
return True
|
||||||
|
except ImportError as e:
|
||||||
|
self._sdk_available = False
|
||||||
|
logger.warning(f"Apryse SDK not available: {e}")
|
||||||
|
logger.warning("SDK files may be downloaded but Python package is not installed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _initialize_sdk(self) -> bool:
|
||||||
|
"""Initialize Apryse SDK with license key"""
|
||||||
|
if self._sdk_initialized:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not self._check_sdk_availability():
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
from apryse_sdk.PDFNetPython import PDFNet, StructuredOutputModule
|
||||||
|
|
||||||
|
# Initialize PDFNet with license key
|
||||||
|
PDFNet.Initialize(self.license_key)
|
||||||
|
|
||||||
|
# Use SDK manager to get the correct platform directory
|
||||||
|
platform_dir = self.sdk_manager.platform_dir
|
||||||
|
|
||||||
|
if self.sdk_manager.platform_name == 'windows':
|
||||||
|
sdk_resource_dir = platform_dir / 'Lib/Windows'
|
||||||
|
elif self.sdk_manager.platform_name == 'linux':
|
||||||
|
sdk_resource_dir = platform_dir / 'Lib/Linux'
|
||||||
|
elif self.sdk_manager.platform_name == 'macos':
|
||||||
|
sdk_resource_dir = platform_dir / 'Lib/MacOS'
|
||||||
|
else:
|
||||||
|
logger.error(f"Unsupported platform: {self.sdk_manager.platform_name}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# Add resource search path for the downloaded SDK
|
||||||
|
if sdk_resource_dir.exists():
|
||||||
|
PDFNet.AddResourceSearchPath(str(sdk_resource_dir))
|
||||||
|
logger.info(f"Added SDK resource search path: {sdk_resource_dir}")
|
||||||
|
else:
|
||||||
|
logger.error(f"SDK resource directory not found: {sdk_resource_dir}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if Structured Output module is available
|
||||||
|
if not StructuredOutputModule.IsModuleAvailable():
|
||||||
|
logger.error("PDFTron SDK Structured Output module not available")
|
||||||
|
logger.error("The Structured Output module is required for PDF to PowerPoint conversion")
|
||||||
|
logger.error("Please ensure the module is installed and accessible")
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._sdk_initialized = True
|
||||||
|
logger.info("Apryse SDK initialized successfully")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to initialize Apryse SDK: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def reload_config(self):
|
||||||
|
"""Reload configuration and reinitialize SDK if needed"""
|
||||||
|
logger.info("Reloading PDF to PPTX converter configuration...")
|
||||||
|
# Force reinitialization with new license key
|
||||||
|
self._sdk_initialized = False
|
||||||
|
return self._initialize_sdk()
|
||||||
|
|
||||||
|
def is_available(self) -> bool:
|
||||||
|
"""Check if the converter is available and ready to use"""
|
||||||
|
return self._check_sdk_availability() and self._initialize_sdk()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def convert_pdf_to_pptx_async(
|
||||||
|
self,
|
||||||
|
pdf_path: str,
|
||||||
|
output_path: Optional[str] = None,
|
||||||
|
timeout: Optional[float] = None,
|
||||||
|
) -> Tuple[bool, str]:
|
||||||
|
if not self.is_available():
|
||||||
|
error_msg = "PDF to PPTX converter is not available. Please check Apryse SDK installation and license."
|
||||||
|
logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
|
pdf_path_obj = Path(pdf_path).expanduser().resolve()
|
||||||
|
if not pdf_path_obj.exists():
|
||||||
|
error_msg = f"Input PDF file not found: {pdf_path_obj}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
|
if output_path is None:
|
||||||
|
output_path_obj = pdf_path_obj.with_suffix('.pptx')
|
||||||
|
else:
|
||||||
|
output_path_obj = Path(output_path).expanduser()
|
||||||
|
if not output_path_obj.is_absolute():
|
||||||
|
output_path_obj = output_path_obj.resolve()
|
||||||
|
|
||||||
|
output_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
command = [
|
||||||
|
sys.executable,
|
||||||
|
'-m',
|
||||||
|
'landppt.services.pdf_to_pptx_worker',
|
||||||
|
'--input',
|
||||||
|
str(pdf_path_obj),
|
||||||
|
'--output',
|
||||||
|
str(output_path_obj),
|
||||||
|
]
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
src_path = str(Path(__file__).resolve().parents[2])
|
||||||
|
pythonpath = env.get('PYTHONPATH')
|
||||||
|
if pythonpath:
|
||||||
|
existing = pythonpath.split(os.pathsep)
|
||||||
|
if src_path not in existing:
|
||||||
|
env['PYTHONPATH'] = os.pathsep.join([src_path, pythonpath])
|
||||||
|
else:
|
||||||
|
env['PYTHONPATH'] = src_path
|
||||||
|
|
||||||
|
project_root = str(Path(__file__).resolve().parents[3])
|
||||||
|
|
||||||
|
logger.info(f"[Async Worker] Launching PDF to PPTX worker: {pdf_path_obj} -> {output_path_obj}")
|
||||||
|
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
*command,
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stderr=asyncio.subprocess.PIPE,
|
||||||
|
env=env,
|
||||||
|
cwd=project_root,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if timeout is not None:
|
||||||
|
stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
||||||
|
else:
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
process.kill()
|
||||||
|
await process.wait()
|
||||||
|
error_msg = f"PDF to PPTX conversion timed out after {timeout} seconds"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
|
stdout_text = stdout.decode('utf-8', errors='ignore').strip()
|
||||||
|
stderr_text = stderr.decode('utf-8', errors='ignore').strip()
|
||||||
|
|
||||||
|
if stdout_text:
|
||||||
|
logger.debug(f"[Async Worker] stdout: {stdout_text}")
|
||||||
|
if stderr_text:
|
||||||
|
logger.debug(f"[Async Worker] stderr: {stderr_text}")
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
if not output_path_obj.exists() or output_path_obj.stat().st_size == 0:
|
||||||
|
error_msg = f"Worker reported success but output file is missing or empty: {output_path_obj}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
|
logger.info(f"[Async Worker] PDF to PPTX conversion successful: {output_path_obj}")
|
||||||
|
return True, str(output_path_obj)
|
||||||
|
|
||||||
|
error_msg = stderr_text or stdout_text or f"Worker exited with code {process.returncode}"
|
||||||
|
logger.error(f"[Async Worker] PDF to PPTX conversion failed: {error_msg}")
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
|
def convert_pdf_to_pptx(self, pdf_path: str, output_path: Optional[str] = None) -> Tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
Convert PDF file to PPTX format
|
||||||
|
|
||||||
|
Note: This is a blocking I/O operation. Should be called via run_blocking_io() or in a thread pool.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pdf_path: Path to the input PDF file
|
||||||
|
output_path: Path for the output PPTX file (optional, will generate if not provided)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (success: bool, output_path: str)
|
||||||
|
"""
|
||||||
|
if not self.is_available():
|
||||||
|
error_msg = "PDF to PPTX converter is not available. Please check Apryse SDK installation and license."
|
||||||
|
logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
|
# Validate input file
|
||||||
|
if not os.path.exists(pdf_path):
|
||||||
|
error_msg = f"Input PDF file not found: {pdf_path}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
|
# Generate output path if not provided
|
||||||
|
if output_path is None:
|
||||||
|
pdf_name = Path(pdf_path).stem
|
||||||
|
output_path = str(Path(pdf_path).parent / f"{pdf_name}.pptx")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from apryse_sdk.PDFNetPython import Convert
|
||||||
|
|
||||||
|
logger.info(f"[Thread Pool] Converting PDF to PowerPoint: {pdf_path} -> {output_path}")
|
||||||
|
|
||||||
|
# Perform the conversion (blocking operation, but should be in thread pool)
|
||||||
|
Convert.ToPowerPoint(pdf_path, output_path)
|
||||||
|
|
||||||
|
# Verify output file was created
|
||||||
|
if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
|
||||||
|
logger.info(f"[Thread Pool] PDF to PPTX conversion successful: {output_path}")
|
||||||
|
return True, output_path
|
||||||
|
else:
|
||||||
|
error_msg = "Conversion completed but output file is empty or not created"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"PDF to PPTX conversion failed: {str(e)}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return False, error_msg
|
||||||
|
|
||||||
|
def convert_with_temp_pdf(self, pdf_content: bytes, output_filename: str = "converted.pptx") -> Tuple[bool, str, str]:
|
||||||
|
"""
|
||||||
|
Convert PDF content to PPTX using temporary files
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pdf_content: PDF file content as bytes
|
||||||
|
output_filename: Desired output filename
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (success: bool, output_path: str, error_message: str)
|
||||||
|
"""
|
||||||
|
temp_pdf_path = None
|
||||||
|
temp_pptx_path = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create temporary PDF file
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as temp_pdf:
|
||||||
|
temp_pdf.write(pdf_content)
|
||||||
|
temp_pdf_path = temp_pdf.name
|
||||||
|
|
||||||
|
# Create temporary PPTX file path with desired filename
|
||||||
|
temp_dir = tempfile.gettempdir()
|
||||||
|
# Use the provided output_filename for the temporary file
|
||||||
|
base_name = Path(output_filename).stem
|
||||||
|
temp_pptx_path = os.path.join(temp_dir, f"{base_name}_{os.getpid()}.pptx")
|
||||||
|
|
||||||
|
# Perform conversion
|
||||||
|
success, result = self.convert_pdf_to_pptx(temp_pdf_path, temp_pptx_path)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
return True, temp_pptx_path, ""
|
||||||
|
else:
|
||||||
|
return False, "", result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Temporary file conversion failed: {str(e)}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return False, "", error_msg
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up temporary PDF file
|
||||||
|
if temp_pdf_path and os.path.exists(temp_pdf_path):
|
||||||
|
try:
|
||||||
|
os.unlink(temp_pdf_path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cleanup_temp_file(self, file_path: str):
|
||||||
|
"""Clean up temporary file"""
|
||||||
|
try:
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.unlink(file_path)
|
||||||
|
logger.debug(f"Cleaned up temporary file: {file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to clean up temporary file {file_path}: {e}")
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
"""Cleanup when converter is destroyed"""
|
||||||
|
if self._sdk_initialized:
|
||||||
|
try:
|
||||||
|
from apryse_sdk.PDFNetPython import PDFNet
|
||||||
|
PDFNet.Terminate()
|
||||||
|
logger.debug("Apryse SDK terminated")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Global converter instance
|
||||||
|
_converter_instance = None
|
||||||
|
|
||||||
|
def get_pdf_to_pptx_converter() -> PDFToPPTXConverter:
|
||||||
|
"""Get the global PDF to PPTX converter instance"""
|
||||||
|
global _converter_instance
|
||||||
|
if _converter_instance is None:
|
||||||
|
_converter_instance = PDFToPPTXConverter()
|
||||||
|
return _converter_instance
|
||||||
|
|
||||||
|
def reload_pdf_to_pptx_converter():
|
||||||
|
"""Reload PDF to PPTX converter configuration"""
|
||||||
|
global _converter_instance
|
||||||
|
if _converter_instance is not None:
|
||||||
|
try:
|
||||||
|
_converter_instance.reload_config()
|
||||||
|
logger.info("PDF to PPTX converter configuration reloaded successfully")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to reload PDF to PPTX converter config: {e}")
|
||||||
|
# If reload fails, recreate the converter
|
||||||
|
_converter_instance = None
|
||||||
Reference in New Issue
Block a user