imagetopdf-gui 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WAEL SAHLI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,22 @@
1
+ # Include documentation
2
+ include README.md
3
+ include LICENSE
4
+
5
+ # Include screenshot for documentation
6
+ include Screenshot.png
7
+
8
+ # Include requirements files
9
+ include requirements.txt
10
+
11
+ # Exclude tests from distribution
12
+ prune tests
13
+ prune __pycache__
14
+ prune *.egg-info
15
+
16
+ # Exclude development files
17
+ exclude .gitignore
18
+ exclude .gitattributes
19
+
20
+ # Exclude output and logs directories
21
+ prune output
22
+ prune logs
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: imagetopdf-gui
3
+ Version: 2.0.0
4
+ Summary: A lightweight GUI application for converting images to PDF and PDF files to images
5
+ Author: WAEL SAHLI
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/overcrash66/ImageToPDF
8
+ Project-URL: Bug Tracker, https://github.com/overcrash66/ImageToPDF/issues
9
+ Project-URL: Source, https://github.com/overcrash66/ImageToPDF
10
+ Keywords: pdf,image,converter,gui,pyside6
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: End Users/Desktop
13
+ Classifier: Topic :: Office/Business
14
+ Classifier: Topic :: Multimedia :: Graphics
15
+ Classifier: Topic :: Utilities
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Operating System :: OS Independent
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: PySide6>=6.5.0
28
+ Requires-Dist: PyPDF2>=3.0.0
29
+ Requires-Dist: Pillow>=10.0.0
30
+ Requires-Dist: pymupdf>=1.23.0
31
+ Dynamic: license-file
32
+
33
+ # ImageToPDF
34
+
35
+ A lightweight GUI application for converting images to PDF and PDF files to images. Built with PySide6, PyPDF2, and PyMuPDF.
36
+
37
+ ![Screenshot](Screenshot.png)
38
+
39
+ ## Features
40
+
41
+ - **Image to PDF Conversion**: Convert multiple images (JPEG, PNG) into a single merged PDF document
42
+ - **PDF to Image Conversion**: Extract individual pages from PDF files as JPEG images
43
+ - **Batch Processing**: Handle multiple images at once
44
+ - **Automatic Duplicate Handling**: Prevents overwriting files with automatic naming
45
+ - **User-Friendly GUI**: Intuitive interface with visual feedback
46
+ - **File Validation**: Validates file types, sizes, and paths before processing
47
+ - **Logging System**: Comprehensive logging for debugging and monitoring
48
+ - **Output Organization**: All converted files are saved in a dedicated output folder
49
+ - **Error Handling**: Robust error handling with user-friendly messages
50
+
51
+ ## Requirements
52
+
53
+ - Python 3.6 or higher
54
+ - PyMuPDF (fitz) - Required for PDF to JPEG conversion
55
+
56
+ ## Installation
57
+
58
+ ### 1. Install Python Dependencies
59
+
60
+ ```bash
61
+ pip install -r requirements.txt
62
+ ```
63
+
64
+ ### 2. Install PyMuPDF (Required for CLI tool)
65
+
66
+ ```bash
67
+ pip install pymupdf
68
+ ```
69
+
70
+ ### 3. Run the Application
71
+
72
+ ```bash
73
+ python ImageToPdf.py
74
+ ```
75
+
76
+ ## Usage
77
+
78
+ ### GUI Application (ImageToPdf.py)
79
+
80
+ 1. **Convert Images to PDF**:
81
+ - Click on any "Image 1-12" button to select image files
82
+ - Selected images will be highlighted in green
83
+ - Click "Convert Images To PDF" to merge all selected images into a single PDF
84
+ - Output file will be saved as `Merged_PDF_[timestamp].pdf` in the `output` folder
85
+
86
+ 2. **Convert PDF to JPEG**:
87
+ - Click "Convert PDF To JPEG" button
88
+ - Select a PDF file
89
+ - Each PDF page will be converted to a separate JPEG image
90
+ - Output images will be saved in the `output` folder with naming format: `Converted-PDF-page[number]_[timestamp].jpeg`
91
+
92
+ 3. **Clear Selection**:
93
+ - Click "Clear All Selection" to reset all selected images
94
+
95
+ ### Command Line Tool (Python_Lib.py)
96
+
97
+ Convert PDF files to JPEG images from the command line:
98
+
99
+ ```bash
100
+ python Python_Lib.py /path/to/your/file.pdf
101
+ ```
102
+
103
+ This will extract each page as a separate JPEG image in the `output` folder.
104
+
105
+ ## Project Structure
106
+
107
+ ```
108
+ ImageToPDF/
109
+ ├── ImageToPdf.py # GUI Application
110
+ ├── Python_Lib.py # CLI Tool
111
+ ├── constants.py # Application constants
112
+ ├── logger.py # Logging configuration
113
+ ├── utils.py # Utility functions
114
+ ├── requirements.txt # Python dependencies
115
+ ├── README.md # This file
116
+ ├── Screenshot.png # Application screenshot
117
+ └── logs/ # Log files (auto-created)
118
+ └── image_to_pdf.log
119
+ ```
120
+
121
+ ## Technology Stack
122
+
123
+ - **GUI Framework**: PySide6
124
+ - **PDF Processing**: PyPDF2, PyMuPDF (fitz)
125
+ - **Image Processing**: Pillow (PIL)
126
+ - **Logging**: Python logging module
127
+ - **CLI**: argparse
128
+
129
+ ## Improvements
130
+
131
+ This version includes the following improvements over the original:
132
+
133
+ - **Security**: Added file path validation and sanitization
134
+ - **Error Handling**: Comprehensive error handling with user-friendly messages
135
+ - **Logging**: Full logging system for debugging and monitoring
136
+ - **Code Quality**: Type hints, docstrings, and consistent code style
137
+ - **Configuration**: Centralized constants file for easy maintenance
138
+ - **Output Organization**: Dedicated output folder for all converted files
139
+ - **File Size Limits**: Maximum file size validation to prevent memory issues
140
+ - **Code Comments**: Detailed comments explaining functionality
141
+ - **Type Safety**: Type hints for better code clarity and IDE support
142
+ - **Modularity**: Separated concerns into utility modules
143
+ - **Documentation**: Improved README with project structure and usage examples
144
+
145
+ ## Notes
146
+
147
+ - The application has been tested on Windows 10 and Windows 11
148
+ - Both GUI ([`ImageToPdf.py`](ImageToPdf.py:1)) and command-line tool ([`Python_Lib.py`](Python_Lib.py:1)) can be used independently
149
+ - Converted files are saved in the `output` folder
150
+ - Log files are saved in the `logs` folder
151
+ - Last updated: 2026-02-08
152
+
153
+ ## License
154
+
155
+ This project is created by WAEL SAHLI.
156
+
157
+ ## Support
158
+
159
+ For issues or questions, please refer to the log files in the `logs` directory for detailed error information.
@@ -0,0 +1,127 @@
1
+ # ImageToPDF
2
+
3
+ A lightweight GUI application for converting images to PDF and PDF files to images. Built with PySide6, PyPDF2, and PyMuPDF.
4
+
5
+ ![Screenshot](Screenshot.png)
6
+
7
+ ## Features
8
+
9
+ - **Image to PDF Conversion**: Convert multiple images (JPEG, PNG) into a single merged PDF document
10
+ - **PDF to Image Conversion**: Extract individual pages from PDF files as JPEG images
11
+ - **Batch Processing**: Handle multiple images at once
12
+ - **Automatic Duplicate Handling**: Prevents overwriting files with automatic naming
13
+ - **User-Friendly GUI**: Intuitive interface with visual feedback
14
+ - **File Validation**: Validates file types, sizes, and paths before processing
15
+ - **Logging System**: Comprehensive logging for debugging and monitoring
16
+ - **Output Organization**: All converted files are saved in a dedicated output folder
17
+ - **Error Handling**: Robust error handling with user-friendly messages
18
+
19
+ ## Requirements
20
+
21
+ - Python 3.6 or higher
22
+ - PyMuPDF (fitz) - Required for PDF to JPEG conversion
23
+
24
+ ## Installation
25
+
26
+ ### 1. Install Python Dependencies
27
+
28
+ ```bash
29
+ pip install -r requirements.txt
30
+ ```
31
+
32
+ ### 2. Install PyMuPDF (Required for CLI tool)
33
+
34
+ ```bash
35
+ pip install pymupdf
36
+ ```
37
+
38
+ ### 3. Run the Application
39
+
40
+ ```bash
41
+ python ImageToPdf.py
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ### GUI Application (ImageToPdf.py)
47
+
48
+ 1. **Convert Images to PDF**:
49
+ - Click on any "Image 1-12" button to select image files
50
+ - Selected images will be highlighted in green
51
+ - Click "Convert Images To PDF" to merge all selected images into a single PDF
52
+ - Output file will be saved as `Merged_PDF_[timestamp].pdf` in the `output` folder
53
+
54
+ 2. **Convert PDF to JPEG**:
55
+ - Click "Convert PDF To JPEG" button
56
+ - Select a PDF file
57
+ - Each PDF page will be converted to a separate JPEG image
58
+ - Output images will be saved in the `output` folder with naming format: `Converted-PDF-page[number]_[timestamp].jpeg`
59
+
60
+ 3. **Clear Selection**:
61
+ - Click "Clear All Selection" to reset all selected images
62
+
63
+ ### Command Line Tool (Python_Lib.py)
64
+
65
+ Convert PDF files to JPEG images from the command line:
66
+
67
+ ```bash
68
+ python Python_Lib.py /path/to/your/file.pdf
69
+ ```
70
+
71
+ This will extract each page as a separate JPEG image in the `output` folder.
72
+
73
+ ## Project Structure
74
+
75
+ ```
76
+ ImageToPDF/
77
+ ├── ImageToPdf.py # GUI Application
78
+ ├── Python_Lib.py # CLI Tool
79
+ ├── constants.py # Application constants
80
+ ├── logger.py # Logging configuration
81
+ ├── utils.py # Utility functions
82
+ ├── requirements.txt # Python dependencies
83
+ ├── README.md # This file
84
+ ├── Screenshot.png # Application screenshot
85
+ └── logs/ # Log files (auto-created)
86
+ └── image_to_pdf.log
87
+ ```
88
+
89
+ ## Technology Stack
90
+
91
+ - **GUI Framework**: PySide6
92
+ - **PDF Processing**: PyPDF2, PyMuPDF (fitz)
93
+ - **Image Processing**: Pillow (PIL)
94
+ - **Logging**: Python logging module
95
+ - **CLI**: argparse
96
+
97
+ ## Improvements
98
+
99
+ This version includes the following improvements over the original:
100
+
101
+ - **Security**: Added file path validation and sanitization
102
+ - **Error Handling**: Comprehensive error handling with user-friendly messages
103
+ - **Logging**: Full logging system for debugging and monitoring
104
+ - **Code Quality**: Type hints, docstrings, and consistent code style
105
+ - **Configuration**: Centralized constants file for easy maintenance
106
+ - **Output Organization**: Dedicated output folder for all converted files
107
+ - **File Size Limits**: Maximum file size validation to prevent memory issues
108
+ - **Code Comments**: Detailed comments explaining functionality
109
+ - **Type Safety**: Type hints for better code clarity and IDE support
110
+ - **Modularity**: Separated concerns into utility modules
111
+ - **Documentation**: Improved README with project structure and usage examples
112
+
113
+ ## Notes
114
+
115
+ - The application has been tested on Windows 10 and Windows 11
116
+ - Both GUI ([`ImageToPdf.py`](ImageToPdf.py:1)) and command-line tool ([`Python_Lib.py`](Python_Lib.py:1)) can be used independently
117
+ - Converted files are saved in the `output` folder
118
+ - Log files are saved in the `logs` folder
119
+ - Last updated: 2026-02-08
120
+
121
+ ## License
122
+
123
+ This project is created by WAEL SAHLI.
124
+
125
+ ## Support
126
+
127
+ For issues or questions, please refer to the log files in the `logs` directory for detailed error information.
Binary file
@@ -0,0 +1,21 @@
1
+ """
2
+ ImageToPDF-GUI - A lightweight GUI application for converting images to PDF and PDFs to images.
3
+
4
+ This package provides both a graphical user interface and command-line tools
5
+ for image and PDF conversion operations.
6
+ """
7
+
8
+ __version__ = "2.0.0"
9
+ __author__ = "WAEL SAHLI"
10
+ __package_name__ = "imagetopdf-gui"
11
+
12
+ from imagetopdf.constants import APP_NAME, APP_VERSION, APP_AUTHOR
13
+
14
+ __all__ = [
15
+ "__version__",
16
+ "__author__",
17
+ "__package_name__",
18
+ "APP_NAME",
19
+ "APP_VERSION",
20
+ "APP_AUTHOR",
21
+ ]
@@ -0,0 +1,10 @@
1
+ """
2
+ Entry point for running the package as a module: python -m imagetopdf
3
+
4
+ This launches the GUI application.
5
+ """
6
+
7
+ from imagetopdf.app import main
8
+
9
+ if __name__ == "__main__":
10
+ main()
@@ -0,0 +1,283 @@
1
+ """
2
+ ImageToPDF - GUI Application for converting images to PDF and PDFs to images.
3
+ Built with PySide6, PyPDF2, and PyMuPDF.
4
+ """
5
+
6
+ import sys
7
+ import os
8
+ import subprocess
9
+ from typing import List, Optional
10
+ from PySide6.QtWidgets import (
11
+ QFileDialog, QWidget, QLabel, QPushButton, QApplication, QMainWindow,
12
+ QHBoxLayout, QVBoxLayout, QMessageBox, QProgressBar
13
+ )
14
+ from PySide6.QtGui import QFont
15
+ from PySide6 import QtCore
16
+ from PyPDF2 import PdfMerger, PdfReader
17
+ from PIL import Image
18
+ from datetime import datetime
19
+
20
+ from imagetopdf.constants import (
21
+ APP_NAME, APP_VERSION, MAX_IMAGE_BUTTONS, BUTTON_TEXT_COLOR,
22
+ BUTTON_SELECTED_COLOR, BUTTON_TEXT_COLOR_SELECTED, WINDOW_MINIMIZE_FLAG,
23
+ PDF_OUTPUT_PREFIX, PDF_OUTPUT_SUFFIX, JPEG_OUTPUT_PREFIX, JPEG_OUTPUT_SUFFIX,
24
+ SUCCESS_IMAGE_TO_PDF, SUCCESS_PDF_TO_JPEG, TITLE_INFO, TITLE_WARNING,
25
+ TITLE_ERROR, TITLE_DONE
26
+ )
27
+ from imagetopdf.logger import default_logger
28
+ from imagetopdf.utils import (
29
+ validate_file_path, get_file_extension, sanitize_filename,
30
+ generate_unique_filename, create_output_directory, get_timestamp,
31
+ log_file_operation
32
+ )
33
+
34
+ CREATE_NO_WINDOW = WINDOW_MINIMIZE_FLAG
35
+
36
+
37
+ class ImageToPdfConverter(QMainWindow):
38
+ """
39
+ Main GUI application for ImageToPDF conversion.
40
+ """
41
+
42
+ def __init__(self):
43
+ super().__init__()
44
+ self.logger = default_logger
45
+ self.initUI()
46
+
47
+ def initUI(self):
48
+ """Initialize the user interface."""
49
+ self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
50
+ self.setWindowFlags(QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowType.WindowCloseButtonHint)
51
+ self.setStyleSheet("#myWidget {background-color:#002633;}")
52
+
53
+ self.central_widget = QWidget()
54
+ self.setCentralWidget(self.central_widget)
55
+
56
+ self.qv_box = QVBoxLayout(self.central_widget)
57
+ self.qh_box = QHBoxLayout()
58
+
59
+ # Create image selection buttons and labels
60
+ self.buttons = [self.create_button(f"Image {i}") for i in range(1, MAX_IMAGE_BUTTONS + 1)]
61
+ self.labels = [QLabel() for _ in range(MAX_IMAGE_BUTTONS)]
62
+
63
+ for button in self.buttons:
64
+ self.qv_box.addWidget(button)
65
+
66
+ for label in self.labels:
67
+ label.setStyleSheet('color: red;')
68
+ self.qv_box.addWidget(label)
69
+
70
+ # Create action buttons with specific handlers
71
+ self.button_clear = self.create_button("Clear All Selection", self.clear_all_selection)
72
+ self.button_clear.setEnabled(False)
73
+ self.button_pdf_to_image = self.create_button("Convert PDF To JPEG", self.convert_pdf_to_jpeg)
74
+ self.button_image_to_pdf = self.create_button("Convert Images To PDF", self.convert_images_to_pdf)
75
+
76
+ self.qh_box.addWidget(self.button_clear)
77
+ self.qh_box.addWidget(self.button_pdf_to_image)
78
+ self.qh_box.addWidget(self.button_image_to_pdf)
79
+ self.qv_box.addLayout(self.qh_box)
80
+
81
+ # Initialize state
82
+ self.image_paths: List[str] = [""] * MAX_IMAGE_BUTTONS
83
+ self.pdf_path: Optional[str] = None
84
+
85
+ self.logger.info("Application initialized successfully")
86
+
87
+ def create_button(self, text: str, handler=None) -> QPushButton:
88
+ """
89
+ Create a styled button.
90
+
91
+ Args:
92
+ text: Button text
93
+ handler: Optional click handler function
94
+
95
+ Returns:
96
+ Configured QPushButton
97
+ """
98
+ button = QPushButton(text)
99
+ button.setFont(QFont('Times', 10, weight=QFont.Bold))
100
+ # Only connect handler if provided (explicit connection in initUI handles the rest)
101
+ if handler:
102
+ button.clicked.connect(handler)
103
+ else:
104
+ button.clicked.connect(self.button_clicked)
105
+ button.setStyleSheet(f'QPushButton {{background-color: {BUTTON_TEXT_COLOR}; color: white;}}')
106
+ return button
107
+
108
+ def button_clicked(self):
109
+ """Handle button click events."""
110
+ button = self.sender()
111
+ button_text = button.text()
112
+
113
+ try:
114
+ image_number = int(button_text.split()[-1])
115
+ except (ValueError, IndexError):
116
+ self.logger.warning(f"Invalid button text: {button_text}")
117
+ return
118
+
119
+ if 1 <= image_number <= MAX_IMAGE_BUTTONS:
120
+ self.select_image(image_number)
121
+ else:
122
+ self.logger.warning(f"Image number out of range: {image_number}")
123
+
124
+ def select_image(self, image_number: int):
125
+ """
126
+ Select an image file for conversion.
127
+
128
+ Args:
129
+ image_number: Image button number (1-12)
130
+ """
131
+ file_path, _ = QFileDialog.getOpenFileName(
132
+ self, 'Open file', '', "Image files (*.jpeg *.jpg *.png)"
133
+ )
134
+
135
+ if file_path:
136
+ is_valid, error_msg = validate_file_path(file_path)
137
+
138
+ if not is_valid:
139
+ self.logger.error(f"Invalid file selected: {file_path} - {error_msg}")
140
+ QMessageBox.warning(self, TITLE_WARNING, error_msg)
141
+ return
142
+
143
+ self.image_paths[image_number - 1] = file_path
144
+ filename = os.path.basename(file_path)
145
+ self.labels[image_number - 1].setText(f"Selected Image is: {filename}")
146
+ self.buttons[image_number - 1].setStyleSheet(
147
+ f'QPushButton {{background-color: {BUTTON_SELECTED_COLOR}; color: {BUTTON_TEXT_COLOR_SELECTED};}}'
148
+ )
149
+ self.logger.info(f"Image {image_number} selected: {file_path}")
150
+ self.update_button_state()
151
+
152
+ def update_button_state(self):
153
+ """Update button enable states based on selected images."""
154
+ has_selection = any(self.image_paths)
155
+ self.button_clear.setEnabled(has_selection)
156
+ self.logger.debug(f"Button state updated: has_selection={has_selection}")
157
+
158
+ def clear_all_selection(self):
159
+ """Clear all selected images."""
160
+ self.image_paths = [""] * MAX_IMAGE_BUTTONS
161
+ for label in self.labels:
162
+ label.clear()
163
+ for button in self.buttons:
164
+ button.setStyleSheet(f'QPushButton {{background-color: {BUTTON_TEXT_COLOR}; color: white;}}')
165
+ self.update_button_state()
166
+ self.logger.info("All selections cleared")
167
+
168
+ def convert_pdf_to_jpeg(self):
169
+ """Convert PDF file to JPEG images."""
170
+ file_dialog = QFileDialog(self, 'Open file', '', "PDF files (*.pdf)")
171
+ pdf_path, _ = file_dialog.getOpenFileName()
172
+
173
+ if pdf_path:
174
+ is_valid, error_msg = validate_file_path(pdf_path)
175
+
176
+ if not is_valid:
177
+ self.logger.error(f"Invalid PDF file: {pdf_path} - {error_msg}")
178
+ QMessageBox.warning(self, TITLE_WARNING, error_msg)
179
+ return
180
+
181
+ try:
182
+ self.logger.info(f"Starting PDF to JPEG conversion: {pdf_path}")
183
+ subprocess.Popen([sys.executable, '-m', 'imagetopdf.cli', pdf_path], creationflags=CREATE_NO_WINDOW)
184
+ QMessageBox.information(self, TITLE_INFO, SUCCESS_PDF_TO_JPEG)
185
+ except Exception as e:
186
+ error_msg = f"Failed to convert PDF: {str(e)}"
187
+ self.logger.error(error_msg)
188
+ QMessageBox.critical(self, TITLE_ERROR, error_msg)
189
+
190
+ def convert_images_to_pdf(self):
191
+ """Convert selected images to a single PDF file."""
192
+ selected_images = [path for path in self.image_paths if path]
193
+
194
+ if not selected_images:
195
+ self.logger.warning("No images selected for conversion")
196
+ QMessageBox.warning(self, TITLE_WARNING, "Please select at least one image to convert to PDF.")
197
+ return
198
+
199
+ try:
200
+ self.logger.info(f"Starting image to PDF conversion: {len(selected_images)} images")
201
+ output_dir = create_output_directory()
202
+
203
+ # Convert each image to PDF
204
+ pdf_paths = []
205
+ for image_path in selected_images:
206
+ try:
207
+ image = Image.open(image_path)
208
+ image = image.convert('RGB')
209
+ base_name = sanitize_filename(os.path.splitext(os.path.basename(image_path))[0])
210
+ pdf_path = generate_unique_filename(base_name, PDF_OUTPUT_SUFFIX, output_dir)
211
+
212
+ image.save(pdf_path)
213
+ pdf_paths.append(pdf_path)
214
+ log_file_operation(self.logger, "Image saved as PDF", pdf_path, True)
215
+
216
+ except Exception as e:
217
+ error_msg = f"Failed to convert image {image_path}: {str(e)}"
218
+ self.logger.error(error_msg)
219
+ QMessageBox.critical(self, TITLE_ERROR, error_msg)
220
+ return
221
+
222
+ # Merge all PDFs
223
+ try:
224
+ merger = PdfMerger()
225
+ for pdf_path in pdf_paths:
226
+ merger.append(pdf_path)
227
+
228
+ timestamp = get_timestamp()
229
+ self.pdf_path = os.path.join(
230
+ output_dir,
231
+ f"{PDF_OUTPUT_PREFIX}{timestamp}{PDF_OUTPUT_SUFFIX}"
232
+ )
233
+ merger.write(self.pdf_path)
234
+ merger.close()
235
+ log_file_operation(self.logger, "PDF merged", self.pdf_path, True)
236
+
237
+ # Clean up temporary PDFs
238
+ for pdf_path in pdf_paths:
239
+ try:
240
+ os.remove(pdf_path)
241
+ log_file_operation(self.logger, "Temporary PDF removed", pdf_path, True)
242
+ except Exception as e:
243
+ self.logger.warning(f"Failed to remove temporary PDF {pdf_path}: {str(e)}")
244
+
245
+ self.update_button_state()
246
+ QMessageBox.information(self, TITLE_INFO, SUCCESS_IMAGE_TO_PDF)
247
+ self.logger.info(f"Successfully created merged PDF: {self.pdf_path}")
248
+
249
+ except Exception as e:
250
+ error_msg = f"Failed to merge PDFs: {str(e)}"
251
+ self.logger.error(error_msg)
252
+ QMessageBox.critical(self, TITLE_ERROR, error_msg)
253
+ return
254
+
255
+ except Exception as e:
256
+ error_msg = f"Unexpected error during conversion: {str(e)}"
257
+ self.logger.error(error_msg)
258
+ QMessageBox.critical(self, TITLE_ERROR, error_msg)
259
+
260
+ def error(self, message: str):
261
+ """
262
+ Display error message.
263
+
264
+ Args:
265
+ message: Error message to display
266
+ """
267
+ QMessageBox.critical(self, TITLE_ERROR, message)
268
+
269
+
270
+ def main():
271
+ """Main entry point for the GUI application."""
272
+ try:
273
+ myApp = QApplication(sys.argv)
274
+ converter = ImageToPdfConverter()
275
+ converter.show()
276
+ sys.exit(myApp.exec())
277
+ except Exception as e:
278
+ print(f"Fatal error: {str(e)}")
279
+ sys.exit(1)
280
+
281
+
282
+ if __name__ == '__main__':
283
+ main()
@@ -0,0 +1,94 @@
1
+ """
2
+ PDF to JPEG Converter - Command-line utility for converting PDF files to JPEG images.
3
+ """
4
+
5
+ import fitz # PyMuPDF
6
+ import argparse
7
+ import os
8
+ from datetime import datetime
9
+ from typing import Optional
10
+
11
+ from imagetopdf.constants import (
12
+ APP_NAME, APP_VERSION, APP_AUTHOR, JPEG_OUTPUT_PREFIX, JPEG_OUTPUT_SUFFIX,
13
+ SUCCESS_PDF_TO_JPEG, TITLE_DONE
14
+ )
15
+ from imagetopdf.logger import default_logger
16
+ from imagetopdf.utils import (
17
+ validate_file_path, create_output_directory, get_pdf_timestamp,
18
+ log_file_operation
19
+ )
20
+
21
+
22
+ def convert_to_jpeg(pdf_path: str) -> None:
23
+ """
24
+ Convert a PDF file to JPEG images using PyMuPDF.
25
+
26
+ Args:
27
+ pdf_path: Path to the PDF file
28
+ """
29
+ logger = default_logger
30
+
31
+ # Validate input file
32
+ is_valid, error_msg = validate_file_path(pdf_path)
33
+ if not is_valid:
34
+ logger.error(f"Invalid PDF file: {pdf_path} - {error_msg}")
35
+ print(f"Error: {error_msg}")
36
+ return
37
+
38
+ try:
39
+ logger.info(f"Starting PDF to JPEG conversion: {pdf_path}")
40
+ doc = fitz.open(pdf_path)
41
+ print("Processing... Please Wait...")
42
+
43
+ output_dir = create_output_directory()
44
+
45
+ for page_index in range(len(doc)):
46
+ page = doc.load_page(page_index)
47
+ pix = page.get_pixmap()
48
+
49
+ timestamp = get_pdf_timestamp()
50
+ output_filename = f"{JPEG_OUTPUT_PREFIX}{page_index + 1}_{timestamp}{JPEG_OUTPUT_SUFFIX}"
51
+ output_path = os.path.join(output_dir, output_filename)
52
+
53
+ pix.save(output_path)
54
+ print(f"Saved: {output_path}")
55
+ log_file_operation(logger, "Page converted to JPEG", output_path, True)
56
+
57
+ doc.close()
58
+
59
+ message = f"{SUCCESS_PDF_TO_JPEG}\n\n{APP_NAME} v{APP_VERSION}\nCreated by {APP_AUTHOR}\nLast update: {datetime.now().strftime('%Y-%m-%d')}"
60
+ print(f"\n{message}")
61
+
62
+ except Exception as e:
63
+ error_msg = f"Error occurred: {str(e)}"
64
+ logger.error(error_msg)
65
+ print(f"Error: {error_msg}")
66
+
67
+
68
+ def main():
69
+ """Main entry point for the CLI tool."""
70
+ parser = argparse.ArgumentParser(
71
+ description=f'{APP_NAME} v{APP_VERSION} - Convert PDF files to JPEG images',
72
+ formatter_class=argparse.RawDescriptionHelpFormatter,
73
+ epilog=f"""
74
+ Examples:
75
+ pdf-to-jpeg document.pdf
76
+ pdf-to-jpeg /path/to/document.pdf
77
+
78
+ {APP_NAME} v{APP_VERSION} - {APP_AUTHOR}
79
+ """
80
+ )
81
+
82
+ parser.add_argument(
83
+ 'pdf_path',
84
+ metavar='PDF_PATH',
85
+ type=str,
86
+ help='Path to the PDF file to convert'
87
+ )
88
+
89
+ args = parser.parse_args()
90
+ convert_to_jpeg(args.pdf_path)
91
+
92
+
93
+ if __name__ == "__main__":
94
+ main()
@@ -0,0 +1,64 @@
1
+ """
2
+ Constants configuration for ImageToPDF application.
3
+ """
4
+
5
+ # Application Configuration
6
+ APP_NAME = "ImageToPDF"
7
+ APP_VERSION = "2.0.0"
8
+ APP_AUTHOR = "WAEL SAHLI"
9
+
10
+ # GUI Configuration
11
+ MAX_IMAGE_BUTTONS = 12
12
+ BUTTON_TEXT_COLOR = "#007399"
13
+ BUTTON_SELECTED_COLOR = "#B2D17C"
14
+ BUTTON_TEXT_COLOR_SELECTED = "#000000"
15
+ WINDOW_MINIMIZE_FLAG = 0x08000000
16
+
17
+ # File Configuration
18
+ IMAGE_EXTENSIONS = {'.jpeg', '.jpg', '.png'}
19
+ PDF_EXTENSIONS = {'.pdf'}
20
+ DEFAULT_OUTPUT_DIR = "output"
21
+
22
+ # PDF Configuration
23
+ PDF_MERGER_APPEND_MODE = 'a'
24
+ PDF_MERGER_WRITE_MODE = 'wb'
25
+
26
+ # Image Configuration
27
+ IMAGE_FORMAT_RGB = 'RGB'
28
+ IMAGE_FORMAT_PNG = 'PNG'
29
+ IMAGE_FORMAT_JPEG = 'JPEG'
30
+
31
+ # Naming Configuration
32
+ TIMESTAMP_FORMAT = "%Y%m%d%H%M%S"
33
+ PDF_TIMESTAMP_FORMAT = "%d-%m-%Y_%H-%M-%S-%f"
34
+ PDF_OUTPUT_PREFIX = "Merged_PDF_"
35
+ PDF_OUTPUT_SUFFIX = ".pdf"
36
+ JPEG_OUTPUT_PREFIX = "Converted-PDF-page"
37
+ JPEG_OUTPUT_SUFFIX = ".jpeg"
38
+
39
+ # File Size Limits (in bytes)
40
+ MAX_FILE_SIZE = 100 * 1024 * 1024 # 100MB
41
+ MAX_IMAGE_SIZE = 50 * 1024 * 1024 # 50MB
42
+
43
+ # Logging Configuration
44
+ LOG_LEVEL = "INFO"
45
+ LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
46
+ LOG_FILE = "image_to_pdf.log"
47
+
48
+ # Error Messages
49
+ ERROR_INVALID_FILE = "Invalid file format. Please select a valid image or PDF file."
50
+ ERROR_FILE_TOO_LARGE = "File is too large. Maximum size is {max_size}."
51
+ ERROR_NO_FILES_SELECTED = "Please select at least one image to convert to PDF."
52
+ ERROR_PDF_CONVERSION_FAILED = "Failed to convert PDF to JPEG."
53
+ ERROR_IMAGE_CONVERSION_FAILED = "Failed to convert images to PDF."
54
+ ERROR_UNKNOWN = "An unknown error occurred."
55
+
56
+ # Success Messages
57
+ SUCCESS_IMAGE_TO_PDF = "Images converted to PDF successfully.\nAll images are available in the output folder."
58
+ SUCCESS_PDF_TO_JPEG = "PDF file converted successfully.\nAll images are available in the output folder."
59
+
60
+ # UI Messages
61
+ TITLE_INFO = "Info"
62
+ TITLE_WARNING = "Warning"
63
+ TITLE_ERROR = "Error"
64
+ TITLE_DONE = "Done"
@@ -0,0 +1,54 @@
1
+ """
2
+ Logging configuration for ImageToPDF application.
3
+ """
4
+
5
+ import logging
6
+ import os
7
+ from datetime import datetime
8
+ from imagetopdf.constants import LOG_LEVEL, LOG_FORMAT, LOG_FILE
9
+
10
+
11
+ def setup_logger(name: str = __name__) -> logging.Logger:
12
+ """
13
+ Set up and configure a logger with file and console handlers.
14
+
15
+ Args:
16
+ name: Name of the logger
17
+
18
+ Returns:
19
+ Configured logger instance
20
+ """
21
+ logger = logging.getLogger(name)
22
+ logger.setLevel(getattr(logging, LOG_LEVEL))
23
+
24
+ # Avoid adding multiple handlers if logger already has them
25
+ if logger.handlers:
26
+ return logger
27
+
28
+ # Create formatters
29
+ file_formatter = logging.Formatter(LOG_FORMAT)
30
+ console_formatter = logging.Formatter('%(levelname)s: %(message)s')
31
+
32
+ # File handler
33
+ log_dir = "logs"
34
+ os.makedirs(log_dir, exist_ok=True)
35
+ log_path = os.path.join(log_dir, LOG_FILE)
36
+
37
+ file_handler = logging.FileHandler(log_path)
38
+ file_handler.setLevel(logging.DEBUG)
39
+ file_handler.setFormatter(file_formatter)
40
+
41
+ # Console handler
42
+ console_handler = logging.StreamHandler()
43
+ console_handler.setLevel(logging.INFO)
44
+ console_handler.setFormatter(console_formatter)
45
+
46
+ # Add handlers to logger
47
+ logger.addHandler(file_handler)
48
+ logger.addHandler(console_handler)
49
+
50
+ return logger
51
+
52
+
53
+ # Create a default logger instance
54
+ default_logger = setup_logger()
@@ -0,0 +1,162 @@
1
+ """
2
+ Utility functions for file operations and validation.
3
+ """
4
+
5
+ import os
6
+ import logging
7
+ from pathlib import Path
8
+ from typing import List, Tuple, Optional
9
+ from imagetopdf.constants import (
10
+ IMAGE_EXTENSIONS, PDF_EXTENSIONS, MAX_FILE_SIZE, MAX_IMAGE_SIZE,
11
+ ERROR_INVALID_FILE, ERROR_FILE_TOO_LARGE, ERROR_UNKNOWN
12
+ )
13
+ from imagetopdf.logger import default_logger
14
+
15
+
16
+ def validate_file_path(file_path: str) -> Tuple[bool, Optional[str]]:
17
+ """
18
+ Validate a file path and check if it exists and has a valid extension.
19
+
20
+ Args:
21
+ file_path: Path to the file
22
+
23
+ Returns:
24
+ Tuple of (is_valid, error_message)
25
+ """
26
+ if not file_path:
27
+ return False, "File path cannot be empty"
28
+
29
+ if not os.path.exists(file_path):
30
+ return False, "File does not exist"
31
+
32
+ if not os.path.isfile(file_path):
33
+ return False, "Path is not a file"
34
+
35
+ file_ext = os.path.splitext(file_path)[1].lower()
36
+
37
+ if file_ext in IMAGE_EXTENSIONS:
38
+ file_size = os.path.getsize(file_path)
39
+ if file_size > MAX_IMAGE_SIZE:
40
+ return False, ERROR_FILE_TOO_LARGE.format(max_size=MAX_IMAGE_SIZE)
41
+ elif file_ext in PDF_EXTENSIONS:
42
+ file_size = os.path.getsize(file_path)
43
+ if file_size > MAX_FILE_SIZE:
44
+ return False, ERROR_FILE_TOO_LARGE.format(max_size=MAX_FILE_SIZE)
45
+ else:
46
+ return False, ERROR_INVALID_FILE
47
+
48
+ return True, None
49
+
50
+
51
+ def get_file_extension(file_path: str) -> str:
52
+ """
53
+ Get the file extension from a file path.
54
+
55
+ Args:
56
+ file_path: Path to the file
57
+
58
+ Returns:
59
+ File extension including the dot
60
+ """
61
+ return os.path.splitext(file_path)[1].lower()
62
+
63
+
64
+ def sanitize_filename(filename: str) -> str:
65
+ """
66
+ Sanitize a filename by removing invalid characters.
67
+
68
+ Args:
69
+ filename: Original filename
70
+
71
+ Returns:
72
+ Sanitized filename
73
+ """
74
+ # Remove invalid characters (including spaces for cross-platform compatibility)
75
+ invalid_chars = '<>:"/\\|?* '
76
+ for char in invalid_chars:
77
+ filename = filename.replace(char, '_')
78
+
79
+ # Remove leading/trailing spaces and dots
80
+ filename = filename.strip('. ')
81
+
82
+ return filename
83
+
84
+
85
+ def generate_unique_filename(base_name: str, extension: str, output_dir: str = ".") -> str:
86
+ """
87
+ Generate a unique filename by appending a counter if the file already exists.
88
+
89
+ Args:
90
+ base_name: Base filename without extension
91
+ extension: File extension including the dot
92
+ output_dir: Output directory path
93
+
94
+ Returns:
95
+ Unique filename
96
+ """
97
+ output_path = os.path.join(output_dir, base_name + extension)
98
+
99
+ if not os.path.exists(output_path):
100
+ return output_path
101
+
102
+ counter = 1
103
+ while True:
104
+ new_name = f"{base_name}_{counter}{extension}"
105
+ new_path = os.path.join(output_dir, new_name)
106
+ if not os.path.exists(new_path):
107
+ return new_path
108
+ counter += 1
109
+
110
+
111
+ def create_output_directory() -> str:
112
+ """
113
+ Create output directory if it doesn't exist.
114
+
115
+ Returns:
116
+ Path to the output directory
117
+ """
118
+ output_dir = "output"
119
+ os.makedirs(output_dir, exist_ok=True)
120
+ return output_dir
121
+
122
+
123
+ def get_timestamp(format_str: str = "%Y%m%d%H%M%S") -> str:
124
+ """
125
+ Get current timestamp in specified format.
126
+
127
+ Args:
128
+ format_str: Timestamp format string
129
+
130
+ Returns:
131
+ Formatted timestamp
132
+ """
133
+ from datetime import datetime
134
+ return datetime.now().strftime(format_str)
135
+
136
+
137
+ def get_pdf_timestamp(format_str: str = "%d-%m-%Y_%H-%M-%S-%f") -> str:
138
+ """
139
+ Get current timestamp in PDF-specific format.
140
+
141
+ Args:
142
+ format_str: Timestamp format string
143
+
144
+ Returns:
145
+ Formatted timestamp
146
+ """
147
+ from datetime import datetime
148
+ return datetime.now().strftime(format_str)
149
+
150
+
151
+ def log_file_operation(logger: logging.Logger, operation: str, file_path: str, success: bool = True):
152
+ """
153
+ Log file operation with context.
154
+
155
+ Args:
156
+ logger: Logger instance
157
+ operation: Operation description
158
+ file_path: Path to the file
159
+ success: Whether the operation was successful
160
+ """
161
+ status = "SUCCESS" if success else "FAILED"
162
+ logger.info(f"{operation}: {file_path} [{status}]")
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ Screenshot.png
5
+ pyproject.toml
6
+ requirements.txt
7
+ setup.py
8
+ imagetopdf/__init__.py
9
+ imagetopdf/__main__.py
10
+ imagetopdf/app.py
11
+ imagetopdf/cli.py
12
+ imagetopdf/constants.py
13
+ imagetopdf/logger.py
14
+ imagetopdf/utils.py
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "imagetopdf-gui"
7
+ version = "2.0.0"
8
+ authors = [
9
+ { name = "WAEL SAHLI" }
10
+ ]
11
+ description = "A lightweight GUI application for converting images to PDF and PDF files to images"
12
+ readme = "README.md"
13
+ license = { text = "MIT" }
14
+ requires-python = ">=3.8"
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: End Users/Desktop",
18
+ "Topic :: Office/Business",
19
+ "Topic :: Multimedia :: Graphics",
20
+ "Topic :: Utilities",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.8",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Operating System :: OS Independent",
29
+ ]
30
+ keywords = ["pdf", "image", "converter", "gui", "pyside6"]
31
+ dependencies = [
32
+ "PySide6>=6.5.0",
33
+ "PyPDF2>=3.0.0",
34
+ "Pillow>=10.0.0",
35
+ "pymupdf>=1.23.0",
36
+ ]
37
+
38
+ [project.urls]
39
+ "Homepage" = "https://github.com/overcrash66/ImageToPDF"
40
+ "Bug Tracker" = "https://github.com/overcrash66/ImageToPDF/issues"
41
+ "Source" = "https://github.com/overcrash66/ImageToPDF"
42
+
43
+ [project.scripts]
44
+ imagetopdf = "imagetopdf.app:main"
45
+ pdf-to-jpeg = "imagetopdf.cli:main"
46
+
47
+ [project.gui-scripts]
48
+ imagetopdf-gui = "imagetopdf.app:main"
49
+
50
+ [tool.setuptools.packages.find]
51
+ where = ["."]
52
+ include = ["imagetopdf*"]
53
+ exclude = ["tests*"]
@@ -0,0 +1,14 @@
1
+ # ImageToPDF Requirements
2
+ # Python 3.6+
3
+
4
+ # GUI Framework
5
+ PySide6>=6.5.0
6
+
7
+ # PDF Processing
8
+ PyPDF2>=3.0.0
9
+
10
+ # Image Processing
11
+ Pillow>=10.0.0
12
+
13
+ # PDF to Image
14
+ pymupdf>=1.23.0
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ """
2
+ Setup script for imagetopdf-gui package.
3
+
4
+ This file is maintained for backwards compatibility with older pip versions.
5
+ The primary configuration is in pyproject.toml.
6
+ """
7
+
8
+ from setuptools import setup
9
+
10
+ if __name__ == "__main__":
11
+ setup()