Photo-Composition-Designer 0.0.7__py3-none-any.whl

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.
Files changed (40) hide show
  1. Photo_Composition_Designer/__init__.py +0 -0
  2. Photo_Composition_Designer/__main__.py +8 -0
  3. Photo_Composition_Designer/_version.py +34 -0
  4. Photo_Composition_Designer/cli/__init__.py +0 -0
  5. Photo_Composition_Designer/cli/__main__.py +8 -0
  6. Photo_Composition_Designer/cli/cli.py +106 -0
  7. Photo_Composition_Designer/common/Anniversaries.py +93 -0
  8. Photo_Composition_Designer/common/Locations.py +87 -0
  9. Photo_Composition_Designer/common/MoonPhase.py +85 -0
  10. Photo_Composition_Designer/common/Photo.py +113 -0
  11. Photo_Composition_Designer/common/__init__.py +0 -0
  12. Photo_Composition_Designer/common/logging.py +216 -0
  13. Photo_Composition_Designer/config/__init__.py +0 -0
  14. Photo_Composition_Designer/config/config.py +321 -0
  15. Photo_Composition_Designer/core/__init__.py +0 -0
  16. Photo_Composition_Designer/core/base.py +383 -0
  17. Photo_Composition_Designer/gui/GuiLogWriter.py +79 -0
  18. Photo_Composition_Designer/gui/__init__.py +0 -0
  19. Photo_Composition_Designer/gui/__main__.py +8 -0
  20. Photo_Composition_Designer/gui/gui.py +565 -0
  21. Photo_Composition_Designer/image/CalendarRenderer.py +319 -0
  22. Photo_Composition_Designer/image/CollageRenderer.py +433 -0
  23. Photo_Composition_Designer/image/DescriptionRenderer.py +74 -0
  24. Photo_Composition_Designer/image/MapRenderer.py +101 -0
  25. Photo_Composition_Designer/image/__init__.py +0 -0
  26. Photo_Composition_Designer/tools/DescriptionsFileGenerator.py +44 -0
  27. Photo_Composition_Designer/tools/GeoPlotter.py +211 -0
  28. Photo_Composition_Designer/tools/Helpers.py +18 -0
  29. Photo_Composition_Designer/tools/ImageDistributor.py +153 -0
  30. Photo_Composition_Designer/tools/__init__.py +0 -0
  31. __init__.py +0 -0
  32. firewall_handler.py +198 -0
  33. main.py +146 -0
  34. path_handler.py +10 -0
  35. photo_composition_designer-0.0.7.dist-info/METADATA +205 -0
  36. photo_composition_designer-0.0.7.dist-info/RECORD +40 -0
  37. photo_composition_designer-0.0.7.dist-info/WHEEL +5 -0
  38. photo_composition_designer-0.0.7.dist-info/entry_points.txt +3 -0
  39. photo_composition_designer-0.0.7.dist-info/licenses/LICENSE +24 -0
  40. photo_composition_designer-0.0.7.dist-info/top_level.txt +5 -0
@@ -0,0 +1,216 @@
1
+ """Centralized logging configuration for Photo_Composition_Designer.
2
+
3
+ This module provides a unified logging setup that supports:
4
+ - File logging with rotation
5
+ - GUI integration via custom handler
6
+ - Configurable log levels from config
7
+ - Structured logging with consistent formatting
8
+ """
9
+
10
+ import logging
11
+ import logging.handlers
12
+ import sys
13
+ from pathlib import Path
14
+
15
+ from Photo_Composition_Designer.config.config import ConfigParameterManager
16
+
17
+
18
+ class GuiLogHandler(logging.Handler):
19
+ """Custom logging handler that can write to GUI text widgets."""
20
+
21
+ def __init__(self, gui_writer=None):
22
+ super().__init__()
23
+ self.gui_writer = gui_writer
24
+ self.setFormatter(
25
+ logging.Formatter("%(asctime)s - %(levelname)s - %(message)s", datefmt="%H:%M:%S")
26
+ )
27
+
28
+ def emit(self, record):
29
+ """Emit a log record to the GUI if writer is available."""
30
+ if self.gui_writer:
31
+ try:
32
+ msg = self.format(record) + "\n"
33
+ self.gui_writer.write(msg)
34
+ except Exception:
35
+ # Fail silently to avoid recursive logging errors
36
+ pass
37
+
38
+
39
+ class LoggerManager:
40
+ """Manages all logging configuration and handlers."""
41
+
42
+ def __init__(self, config: ConfigParameterManager):
43
+ self.config = config
44
+ self.logger = logging.getLogger("Photo_Composition_Designer")
45
+ self.gui_handler = None
46
+ self.file_handler = None
47
+ self.console_handler = None
48
+ self._setup_logging()
49
+
50
+ def _setup_logging(self):
51
+ """Configure all logging handlers and formatters."""
52
+ # Clear any existing handlers
53
+ self.logger.handlers.clear()
54
+
55
+ # Set log level from config
56
+ log_level = getattr(logging, self.config.app.log_level.value.upper())
57
+ self.logger.setLevel(log_level)
58
+
59
+ # Create formatters
60
+ detailed_formatter = logging.Formatter(
61
+ "%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s"
62
+ )
63
+ simple_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
64
+
65
+ # Setup file handler with rotation
66
+ self._setup_file_handler(detailed_formatter)
67
+
68
+ # Setup console handler
69
+ self._setup_console_handler(simple_formatter)
70
+
71
+ # Setup GUI handler (will be connected later if needed)
72
+ self._setup_gui_handler()
73
+
74
+ def _setup_file_handler(self, formatter):
75
+ """Setup rotating file handler."""
76
+ log_dir = Path("logs")
77
+ log_dir.mkdir(exist_ok=True)
78
+
79
+ log_file = log_dir / "Photo_Composition_Designer.log"
80
+
81
+ # Use RotatingFileHandler to prevent huge log files
82
+ self.file_handler = logging.handlers.RotatingFileHandler(
83
+ log_file,
84
+ maxBytes=10 * 1024 * 1024, # 10MB
85
+ backupCount=5,
86
+ encoding="utf-8",
87
+ )
88
+ self.file_handler.setFormatter(formatter)
89
+ self.logger.addHandler(self.file_handler)
90
+
91
+ def _setup_console_handler(self, formatter):
92
+ """Setup console handler for CLI output."""
93
+ self.console_handler = logging.StreamHandler(sys.stdout)
94
+ self.console_handler.setFormatter(formatter)
95
+ self.logger.addHandler(self.console_handler)
96
+
97
+ def _setup_gui_handler(self):
98
+ """Setup GUI handler (initially without writer)."""
99
+ self.gui_handler = GuiLogHandler()
100
+ # Don't add to logger yet - will be done when GUI connects
101
+
102
+ def connect_gui_writer(self, gui_writer):
103
+ """Connect a GUI writer to the logging system.
104
+
105
+ Args:
106
+ gui_writer: Object with write() method (like the LogHandler from gui.py)
107
+ """
108
+ if self.gui_handler:
109
+ # Remove old handler if it exists
110
+ if self.gui_handler in self.logger.handlers:
111
+ self.logger.removeHandler(self.gui_handler)
112
+
113
+ # Create new handler with GUI writer
114
+ self.gui_handler = GuiLogHandler(gui_writer)
115
+ self.logger.addHandler(self.gui_handler)
116
+
117
+ def disconnect_gui_writer(self):
118
+ """Disconnect GUI writer (useful when GUI closes)."""
119
+ if self.gui_handler and self.gui_handler in self.logger.handlers:
120
+ self.logger.removeHandler(self.gui_handler)
121
+
122
+ def get_logger(self, name: str = None) -> logging.Logger:
123
+ """Get a logger instance.
124
+
125
+ Args:
126
+ name: Logger name (defaults to main project logger)
127
+
128
+ Returns:
129
+ Logger instance
130
+ """
131
+ if name:
132
+ return logging.getLogger(f"Photo_Composition_Designer.{name}")
133
+ return self.logger
134
+
135
+ def set_log_level(self, level: str):
136
+ """Change log level dynamically.
137
+
138
+ Args:
139
+ level: New log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
140
+ """
141
+ log_level = getattr(logging, level.upper())
142
+ self.logger.setLevel(log_level)
143
+
144
+ # Update config
145
+ self.config.app.log_level.value = level.upper()
146
+
147
+ def log_config_summary(self):
148
+ """Log current configuration summary."""
149
+ self.logger.info("=== Configuration Summary ===")
150
+ self.logger.info(f"Log level: {self.config.app.log_level.value}")
151
+ self.logger.info("==============================")
152
+
153
+
154
+ # Global logger manager instance
155
+ _logger_manager = None
156
+
157
+
158
+ def initialize_logging(config: ConfigParameterManager) -> LoggerManager:
159
+ """Initialize the global logging system.
160
+
161
+ Args:
162
+ config: Configuration manager instance
163
+
164
+ Returns:
165
+ LoggerManager instance
166
+ """
167
+ global _logger_manager
168
+ _logger_manager = LoggerManager(config)
169
+ return _logger_manager
170
+
171
+
172
+ def get_logger(name: str = None) -> logging.Logger:
173
+ """Get a logger instance.
174
+
175
+ Args:
176
+ name: Logger name (optional)
177
+
178
+ Returns:
179
+ Logger instance
180
+
181
+ Raises:
182
+ RuntimeError: If logging not initialized
183
+ """
184
+ if _logger_manager is None:
185
+ raise RuntimeError("Logging not initialized. Call initialize_logging() first.")
186
+ return _logger_manager.get_logger(name)
187
+
188
+
189
+ def get_logger_manager() -> LoggerManager:
190
+ """Get the global logger manager.
191
+
192
+ Returns:
193
+ LoggerManager instance
194
+
195
+ Raises:
196
+ RuntimeError: If logging not initialized
197
+ """
198
+ if _logger_manager is None:
199
+ raise RuntimeError("Logging not initialized. Call initialize_logging() first.")
200
+ return _logger_manager
201
+
202
+
203
+ def connect_gui_logging(gui_writer):
204
+ """Connect GUI writer to logging system.
205
+
206
+ Args:
207
+ gui_writer: GUI writer object with write() method
208
+ """
209
+ if _logger_manager:
210
+ _logger_manager.connect_gui_writer(gui_writer)
211
+
212
+
213
+ def disconnect_gui_logging():
214
+ """Disconnect GUI from logging system."""
215
+ if _logger_manager:
216
+ _logger_manager.disconnect_gui_writer()
File without changes
@@ -0,0 +1,321 @@
1
+ """
2
+ Central configuration management for the new project.
3
+
4
+ This module provides a single source of truth for all configuration parameters
5
+ organized in categories (GENERAL, CALENDAR, COLORS, GEO, SIZE, LAYOUT).
6
+ It can generate config files, CLI modules, and documentation from the parameter definitions.
7
+ """
8
+
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+
12
+ from config_cli_gui.config import Color, ConfigCategory, ConfigManager, ConfigParameter
13
+ from config_cli_gui.docs import DocumentationGenerator
14
+
15
+
16
+ class AppConfig(ConfigCategory):
17
+ """Application-specific configuration parameters."""
18
+
19
+ def get_category_name(self) -> str:
20
+ return "app"
21
+
22
+ log_level: ConfigParameter = ConfigParameter(
23
+ name="log_level",
24
+ value="INFO",
25
+ choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
26
+ help="Logging level for the application",
27
+ )
28
+
29
+
30
+ class GeneralConfig(ConfigCategory):
31
+ """GENERAL configuration parameters."""
32
+
33
+ def get_category_name(self) -> str:
34
+ return "general"
35
+
36
+ photoDirectory: ConfigParameter = ConfigParameter(
37
+ name="photoDirectory",
38
+ value=Path("images"),
39
+ help="Path to the directory containing photos "
40
+ "(absolute, or relative to this config.ini file)",
41
+ is_cli=True,
42
+ required=True,
43
+ )
44
+
45
+ anniversariesConfig: ConfigParameter = ConfigParameter(
46
+ name="anniversariesConfig",
47
+ value=Path("anniversaries.ini"),
48
+ help="Path to anniversaries.ini file (absolute, or relative to this config.ini file)",
49
+ )
50
+
51
+ locationsConfig: ConfigParameter = ConfigParameter(
52
+ name="locationsConfig",
53
+ value=Path("locations_en.ini"),
54
+ help="Path to locations.ini file (absolute, or relative to this config.ini file)",
55
+ )
56
+
57
+ compositionTitle: ConfigParameter = ConfigParameter(
58
+ name="compositionTitle",
59
+ value="This is the title of the composition",
60
+ help="This is the title of the composition on the first page. Leave empty if not required.",
61
+ )
62
+
63
+
64
+ class CalendarConfig(ConfigCategory):
65
+ """CALENDAR configuration parameters."""
66
+
67
+ def get_category_name(self) -> str:
68
+ return "calendar"
69
+
70
+ useCalendar: ConfigParameter = ConfigParameter(
71
+ name="useCalendar",
72
+ value=True,
73
+ help="True: Calendar elements are generated",
74
+ )
75
+
76
+ language: ConfigParameter = ConfigParameter(
77
+ name="language",
78
+ value="de_DE",
79
+ help="Language for the calendar (e.g., de_DE, en_US)",
80
+ )
81
+
82
+ holidayCountries: ConfigParameter = ConfigParameter(
83
+ name="holidayCountries",
84
+ value="SN",
85
+ help="Country/state codes for public holidays, e.g., NY,CA",
86
+ )
87
+
88
+ startDate: ConfigParameter = ConfigParameter(
89
+ name="startDate",
90
+ value=datetime.fromisoformat("2025-12-31"),
91
+ help="Start date of the calendar",
92
+ is_cli=True,
93
+ )
94
+
95
+ collagesToGenerate: ConfigParameter = ConfigParameter(
96
+ name="collagesToGenerate",
97
+ value=5,
98
+ help="Number of collages to be generated (e.g. number of weeks)",
99
+ )
100
+
101
+
102
+ class ColorsConfig(ConfigCategory):
103
+ """COLORS configuration parameters."""
104
+
105
+ def get_category_name(self) -> str:
106
+ return "colors"
107
+
108
+ backgroundColor: ConfigParameter = ConfigParameter(
109
+ name="backgroundColor",
110
+ value=Color(20, 20, 20),
111
+ help="Background color (RGB)",
112
+ )
113
+
114
+ textColor1: ConfigParameter = ConfigParameter(
115
+ name="textColor1",
116
+ value=Color(255, 255, 255),
117
+ help="Primary text color",
118
+ )
119
+
120
+ textColor2: ConfigParameter = ConfigParameter(
121
+ name="textColor2",
122
+ value=Color(150, 150, 150),
123
+ help="Secondary text color",
124
+ )
125
+
126
+ holidayColor: ConfigParameter = ConfigParameter(
127
+ name="holidayColor",
128
+ value=Color(255, 0, 0),
129
+ help="Color for holidays",
130
+ )
131
+
132
+
133
+ class GeoConfig(ConfigCategory):
134
+ """GEO configuration parameters."""
135
+
136
+ def get_category_name(self) -> str:
137
+ return "geo"
138
+
139
+ usePhotoLocationMaps: ConfigParameter = ConfigParameter(
140
+ name="usePhotoLocationMaps",
141
+ value=True,
142
+ help="Use GPS data to generate maps",
143
+ )
144
+
145
+ minimalExtension: ConfigParameter = ConfigParameter(
146
+ name="minimalExtension",
147
+ value=7,
148
+ help="Minimum range for map display (degrees)",
149
+ )
150
+
151
+
152
+ class SizeConfig(ConfigCategory):
153
+ """SIZE configuration parameters."""
154
+
155
+ def get_category_name(self) -> str:
156
+ return "size"
157
+
158
+ width: ConfigParameter = ConfigParameter(
159
+ name="width",
160
+ value=216,
161
+ help="Width of the collage in mm",
162
+ is_cli=True,
163
+ )
164
+
165
+ height: ConfigParameter = ConfigParameter(
166
+ name="height",
167
+ value=154,
168
+ help="Height of the collage in mm",
169
+ is_cli=True,
170
+ )
171
+
172
+ calendarHeight: ConfigParameter = ConfigParameter(
173
+ name="calendarHeight",
174
+ value=18,
175
+ help="Height of the calendar area in mm",
176
+ )
177
+
178
+ mapWidth: ConfigParameter = ConfigParameter(
179
+ name="mapWidth",
180
+ value=20,
181
+ help="Width of the locations map in mm",
182
+ )
183
+
184
+ mapHeight: ConfigParameter = ConfigParameter(
185
+ name="mapHeight",
186
+ value=20,
187
+ help="Height of the locations map in mm",
188
+ )
189
+
190
+ dpi: ConfigParameter = ConfigParameter(
191
+ name="dpi",
192
+ value=150,
193
+ help="Resolution of the image in dpi",
194
+ is_cli=True,
195
+ )
196
+
197
+ jpgQuality: ConfigParameter = ConfigParameter(
198
+ name="jpgQuality",
199
+ value=90,
200
+ help="JPG compression quality (1-100)",
201
+ )
202
+
203
+
204
+ class LayoutConfig(ConfigCategory):
205
+ """LAYOUT configuration parameters."""
206
+
207
+ def get_category_name(self) -> str:
208
+ return "layout"
209
+
210
+ fontSizeLarge: ConfigParameter = ConfigParameter(
211
+ name="fontSizeLarge",
212
+ value=0.5,
213
+ help="Font size for large text",
214
+ )
215
+
216
+ fontSizeSmall: ConfigParameter = ConfigParameter(
217
+ name="fontSizeSmall",
218
+ value=0.14,
219
+ help="Font size for small text",
220
+ )
221
+
222
+ fontSizeAnniversaries: ConfigParameter = ConfigParameter(
223
+ name="fontSizeAnniversaries",
224
+ value=0.115,
225
+ help="Font size for anniversaries",
226
+ )
227
+
228
+ marginTop: ConfigParameter = ConfigParameter(
229
+ name="marginTop",
230
+ value=6,
231
+ help="Top margin in mm",
232
+ )
233
+
234
+ marginBottom: ConfigParameter = ConfigParameter(
235
+ name="marginBottom",
236
+ value=3,
237
+ help="Bottom margin in mm",
238
+ )
239
+
240
+ marginSides: ConfigParameter = ConfigParameter(
241
+ name="marginSides",
242
+ value=3,
243
+ help="Side margins in mm",
244
+ )
245
+
246
+ spacing: ConfigParameter = ConfigParameter(
247
+ name="spacing",
248
+ value=2,
249
+ help="Spacing between elements in mm",
250
+ )
251
+
252
+ useShortDayNames: ConfigParameter = ConfigParameter(
253
+ name="useShortDayNames",
254
+ value=False,
255
+ help="Use short weekday names (e.g., Mon, Tue)",
256
+ )
257
+
258
+ useShortMonthNames: ConfigParameter = ConfigParameter(
259
+ name="useShortMonthNames",
260
+ value=True,
261
+ help="Use short month names (e.g., Jan, Feb)",
262
+ )
263
+
264
+ usePhotoDescription: ConfigParameter = ConfigParameter(
265
+ name="usePhotoDescription",
266
+ value=True,
267
+ help="Include photo descriptions in the collage",
268
+ )
269
+
270
+ generatePdf: ConfigParameter = ConfigParameter(
271
+ name="generatePdf",
272
+ value=True,
273
+ help="Combine all generated collages into one pdf",
274
+ )
275
+
276
+
277
+ class ConfigParameterManager(ConfigManager):
278
+ """Main configuration manager that handles all parameter categories."""
279
+
280
+ app: AppConfig
281
+ general: GeneralConfig
282
+ calendar: CalendarConfig
283
+ colors: ColorsConfig
284
+ geo: GeoConfig
285
+ size: SizeConfig
286
+ layout: LayoutConfig
287
+
288
+ def __init__(self, config_file: str | None = None, **kwargs):
289
+ categories = (
290
+ AppConfig(),
291
+ GeneralConfig(),
292
+ CalendarConfig(),
293
+ ColorsConfig(),
294
+ GeoConfig(),
295
+ SizeConfig(),
296
+ LayoutConfig(),
297
+ )
298
+ super().__init__(categories, config_file, **kwargs)
299
+
300
+
301
+ def main():
302
+ """Main function to generate config file and documentation."""
303
+ default_config: str = "config.yaml"
304
+ default_cli_doc: str = "docs/usage/cli.md"
305
+ default_config_doc: str = "docs/usage/config.md"
306
+
307
+ config_manager = ConfigParameterManager()
308
+ docGen = DocumentationGenerator(config_manager)
309
+
310
+ docGen.generate_default_config_file(output_file=default_config)
311
+ print(f"Generated: {default_config}")
312
+
313
+ docGen.generate_config_markdown_doc(output_file=default_config_doc)
314
+ print(f"Generated: {default_config_doc}")
315
+
316
+ docGen.generate_cli_markdown_doc(output_file=default_cli_doc)
317
+ print(f"Generated: {default_cli_doc}")
318
+
319
+
320
+ if __name__ == "__main__":
321
+ main()
File without changes