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.
- imagetopdf_gui-2.0.0/LICENSE +21 -0
- imagetopdf_gui-2.0.0/MANIFEST.in +22 -0
- imagetopdf_gui-2.0.0/PKG-INFO +159 -0
- imagetopdf_gui-2.0.0/README.md +127 -0
- imagetopdf_gui-2.0.0/Screenshot.png +0 -0
- imagetopdf_gui-2.0.0/imagetopdf/__init__.py +21 -0
- imagetopdf_gui-2.0.0/imagetopdf/__main__.py +10 -0
- imagetopdf_gui-2.0.0/imagetopdf/app.py +283 -0
- imagetopdf_gui-2.0.0/imagetopdf/cli.py +94 -0
- imagetopdf_gui-2.0.0/imagetopdf/constants.py +64 -0
- imagetopdf_gui-2.0.0/imagetopdf/logger.py +54 -0
- imagetopdf_gui-2.0.0/imagetopdf/utils.py +162 -0
- imagetopdf_gui-2.0.0/imagetopdf_gui.egg-info/SOURCES.txt +14 -0
- imagetopdf_gui-2.0.0/pyproject.toml +53 -0
- imagetopdf_gui-2.0.0/requirements.txt +14 -0
- imagetopdf_gui-2.0.0/setup.cfg +4 -0
- imagetopdf_gui-2.0.0/setup.py +11 -0
|
@@ -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
|
+

|
|
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
|
+

|
|
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,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,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()
|