plex-generate-previews 2.0.0__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.
@@ -0,0 +1,479 @@
1
+ """
2
+ Configuration management for Plex Video Preview Generator.
3
+
4
+ Handles environment variable loading, validation, and provides a centralized
5
+ configuration object for the entire application.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import shutil
11
+ import subprocess
12
+ from dataclasses import dataclass
13
+ from typing import List, Optional
14
+ from dotenv import load_dotenv
15
+ from loguru import logger
16
+
17
+ from .utils import is_docker_environment
18
+
19
+ # Load environment variables from .env file
20
+ load_dotenv()
21
+
22
+ # Set default ROCM_PATH if not already set to prevent KeyError in AMD SMI
23
+ if 'ROCM_PATH' not in os.environ:
24
+ os.environ['ROCM_PATH'] = '/opt/rocm'
25
+
26
+
27
+ def get_config_value(cli_args, field_name: str, env_key: str, default, value_type: type = str):
28
+ """
29
+ Get configuration value with proper precedence: CLI args > env vars > defaults.
30
+
31
+ Args:
32
+ cli_args: CLI arguments object or None
33
+ field_name: Name of the CLI argument field
34
+ env_key: Environment variable key
35
+ default: Default value if neither CLI nor env var is set
36
+ value_type: Type to convert the value to (str, int, bool)
37
+
38
+ Returns:
39
+ The configuration value converted to the specified type
40
+ """
41
+ cli_value = getattr(cli_args, field_name, None) if cli_args else None
42
+ if cli_value is not None:
43
+ return cli_value
44
+
45
+ env_value = os.environ.get(env_key, '')
46
+
47
+ # Handle boolean conversion specially
48
+ if value_type == bool:
49
+ if env_value.strip().lower() in ('true', '1', 'yes'):
50
+ return True
51
+ elif env_value.strip().lower() in ('false', '0', 'no'):
52
+ return False
53
+ return default
54
+
55
+ # Handle other types
56
+ if not env_value:
57
+ return default
58
+
59
+ try:
60
+ return value_type(env_value)
61
+ except (ValueError, TypeError):
62
+ return default
63
+
64
+
65
+ def get_config_value_str(cli_args, field_name: str, env_key: str, default: str = '') -> str:
66
+ """Get string configuration value."""
67
+ return get_config_value(cli_args, field_name, env_key, default, str)
68
+
69
+
70
+ def get_config_value_int(cli_args, field_name: str, env_key: str, default: int = 0) -> int:
71
+ """Get integer configuration value."""
72
+ return get_config_value(cli_args, field_name, env_key, default, int)
73
+
74
+
75
+ def get_config_value_bool(cli_args, field_name: str, env_key: str, default: bool = False) -> bool:
76
+ """Get boolean configuration value."""
77
+ return get_config_value(cli_args, field_name, env_key, default, bool)
78
+
79
+
80
+ @dataclass
81
+ class Config:
82
+ """Configuration object containing all application settings."""
83
+
84
+ # Plex server configuration
85
+ plex_url: str
86
+ plex_token: str
87
+ plex_timeout: int
88
+ plex_libraries: List[str]
89
+
90
+ # Media paths
91
+ plex_config_folder: str
92
+ plex_local_videos_path_mapping: str
93
+ plex_videos_path_mapping: str
94
+
95
+ # Processing configuration
96
+ plex_bif_frame_interval: int
97
+ thumbnail_quality: int
98
+ regenerate_thumbnails: bool
99
+
100
+ # Threading configuration
101
+ gpu_threads: int
102
+ cpu_threads: int
103
+ gpu_selection: str
104
+
105
+ # System paths
106
+ tmp_folder: str
107
+ ffmpeg_path: str
108
+
109
+ # Logging
110
+ log_level: str
111
+
112
+ # Internal constants
113
+ worker_pool_timeout: int = 30
114
+
115
+
116
+ def show_docker_help():
117
+ """Show Docker-optimized help message with environment variables prominently displayed."""
118
+ logger.info('🐳 Docker Environment Detected - Configuration via Environment Variables')
119
+ logger.info('=' * 80)
120
+ logger.info('')
121
+ logger.info('📋 Required Environment Variables:')
122
+ logger.info('')
123
+ logger.info(' PLEX_URL Plex server URL (e.g., http://localhost:32400)')
124
+ logger.info(' PLEX_TOKEN Plex authentication token')
125
+ logger.info(' PLEX_CONFIG_FOLDER Path to Plex Media Server configuration folder')
126
+ logger.info('')
127
+ logger.info('📋 Optional Environment Variables:')
128
+ logger.info('')
129
+ logger.info(' PLEX_TIMEOUT Plex API timeout in seconds (default: 60)')
130
+ logger.info(' PLEX_LIBRARIES Comma-separated library names (e.g., "Movies, TV Shows")')
131
+ logger.info(' PLEX_LOCAL_VIDEOS_PATH_MAPPING Local videos path mapping')
132
+ logger.info(' PLEX_VIDEOS_PATH_MAPPING Plex videos path mapping')
133
+ logger.info(' PLEX_BIF_FRAME_INTERVAL Interval between preview images in seconds (default: 5)')
134
+ logger.info(' THUMBNAIL_QUALITY Preview image quality 1-10 (default: 4)')
135
+ logger.info(' REGENERATE_THUMBNAILS Regenerate existing thumbnails (true/false, default: false)')
136
+ logger.info(' GPU_THREADS Number of GPU worker threads (default: 1)')
137
+ logger.info(' CPU_THREADS Number of CPU worker threads (default: 1)')
138
+ logger.info(' GPU_SELECTION GPU selection: "all" or comma-separated indices (default: all)')
139
+ logger.info(' TMP_FOLDER Temporary folder for processing (default: /tmp/plex_generate_previews)')
140
+ logger.info(' LOG_LEVEL Logging level: DEBUG, INFO, WARNING, ERROR (default: INFO)')
141
+ logger.info('')
142
+ logger.info('💡 Example Docker Run Command:')
143
+ logger.info('')
144
+ logger.info(' docker run -it --rm --runtime=nvidia \\')
145
+ logger.info(' -e PLEX_URL="http://localhost:32400" \\')
146
+ logger.info(' -e PLEX_TOKEN="your_token_here" \\')
147
+ logger.info(' -e PLEX_CONFIG_FOLDER="/config/plex/Library/Application Support/Plex Media Server" \\')
148
+ logger.info(' -e GPU_THREADS=1 \\')
149
+ logger.info(' -e CPU_THREADS=1 \\')
150
+ logger.info(' -v /path/to/plex/config:/config \\')
151
+ logger.info(' -v /path/to/videos:/data \\')
152
+ logger.info(' plex_generate_vid_previews:latest')
153
+ logger.info('')
154
+ logger.info('🔧 For CLI arguments (non-Docker), use: plex-generate-previews --help')
155
+
156
+
157
+ def load_config(cli_args=None) -> Config:
158
+ """
159
+ Load and validate configuration from CLI arguments and environment variables.
160
+ CLI arguments take precedence over environment variables.
161
+
162
+ Args:
163
+ cli_args: Parsed CLI arguments or None
164
+
165
+ Returns:
166
+ Config: Validated configuration object
167
+
168
+ Raises:
169
+ SystemExit: If required configuration is missing or invalid
170
+ """
171
+ # Extract CLI values (None if not provided)
172
+ if cli_args is None:
173
+ cli_args = None # Empty namespace
174
+
175
+ # Load configuration with precedence: CLI args > env vars > defaults
176
+ plex_url = get_config_value_str(cli_args, 'plex_url', 'PLEX_URL', '')
177
+ plex_token = get_config_value_str(cli_args, 'plex_token', 'PLEX_TOKEN', '')
178
+ plex_timeout = get_config_value_int(cli_args, 'plex_timeout', 'PLEX_TIMEOUT', 60)
179
+
180
+ # Handle plex_libraries (special case for comma-separated values)
181
+ plex_libraries = get_config_value_str(cli_args, 'plex_libraries', 'PLEX_LIBRARIES', '')
182
+ plex_libraries = [library.strip().lower() for library in plex_libraries.split(',') if library.strip()]
183
+
184
+ plex_config_folder = get_config_value_str(cli_args, 'plex_config_folder', 'PLEX_CONFIG_FOLDER', '/path_to/plex/Library/Application Support/Plex Media Server')
185
+ plex_local_videos_path_mapping = get_config_value_str(cli_args, 'plex_local_videos_path_mapping', 'PLEX_LOCAL_VIDEOS_PATH_MAPPING', '')
186
+ plex_videos_path_mapping = get_config_value_str(cli_args, 'plex_videos_path_mapping', 'PLEX_VIDEOS_PATH_MAPPING', '')
187
+
188
+ plex_bif_frame_interval = get_config_value_int(cli_args, 'plex_bif_frame_interval', 'PLEX_BIF_FRAME_INTERVAL', 5)
189
+ thumbnail_quality = get_config_value_int(cli_args, 'thumbnail_quality', 'THUMBNAIL_QUALITY', 4)
190
+ regenerate_thumbnails = get_config_value_bool(cli_args, 'regenerate_thumbnails', 'REGENERATE_THUMBNAILS', False)
191
+
192
+ gpu_threads = get_config_value_int(cli_args, 'gpu_threads', 'GPU_THREADS', 1)
193
+ cpu_threads = get_config_value_int(cli_args, 'cpu_threads', 'CPU_THREADS', 1)
194
+ gpu_selection = get_config_value_str(cli_args, 'gpu_selection', 'GPU_SELECTION', 'all')
195
+
196
+ tmp_folder = get_config_value_str(cli_args, 'tmp_folder', 'TMP_FOLDER', '/tmp/plex_generate_previews')
197
+
198
+ # Handle log_level (case insensitive)
199
+ log_level = get_config_value_str(cli_args, 'log_level', 'LOG_LEVEL', 'INFO').upper()
200
+
201
+ # Initialize validation lists
202
+ missing_params = []
203
+ validation_errors = []
204
+
205
+ # Validate log level
206
+ valid_log_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
207
+ if log_level not in valid_log_levels:
208
+ validation_errors.append(f'LOG_LEVEL must be one of {valid_log_levels} (got: {log_level})')
209
+
210
+ # Update logging level early so debug statements work
211
+ if log_level in valid_log_levels:
212
+ from .cli import setup_logging
213
+ setup_logging(log_level)
214
+
215
+ # Find FFmpeg path
216
+ ffmpeg_path = shutil.which("ffmpeg")
217
+ if not ffmpeg_path:
218
+ logger.error('FFmpeg not found. FFmpeg must be installed and available in PATH.')
219
+ sys.exit(1)
220
+
221
+ # Test FFmpeg actually works
222
+ try:
223
+ result = subprocess.run([ffmpeg_path, '-version'], capture_output=True, text=True, timeout=5)
224
+ if result.returncode != 0:
225
+ validation_errors.append('FFmpeg found but not working properly')
226
+ except (subprocess.TimeoutExpired, FileNotFoundError):
227
+ validation_errors.append('FFmpeg found but cannot execute properly')
228
+
229
+ # Check basic required parameters first
230
+ if not plex_url:
231
+ if is_docker_environment():
232
+ missing_params.append('PLEX_URL is required (set PLEX_URL environment variable)')
233
+ else:
234
+ missing_params.append('PLEX_URL is required (use --plex-url or set PLEX_URL environment variable)')
235
+ elif not plex_url.startswith(('http://', 'https://')):
236
+ validation_errors.append(f'PLEX_URL must start with http:// or https:// (got: {plex_url})')
237
+
238
+ if not plex_token:
239
+ if is_docker_environment():
240
+ missing_params.append('PLEX_TOKEN is required (set PLEX_TOKEN environment variable)')
241
+ else:
242
+ missing_params.append('PLEX_TOKEN is required (use --plex-token or set PLEX_TOKEN environment variable)')
243
+
244
+ # Check PLEX_CONFIG_FOLDER
245
+ if not plex_config_folder or plex_config_folder == '/path_to/plex/Library/Application Support/Plex Media Server':
246
+ if is_docker_environment():
247
+ missing_params.append('PLEX_CONFIG_FOLDER is required (set PLEX_CONFIG_FOLDER environment variable)')
248
+ else:
249
+ missing_params.append('PLEX_CONFIG_FOLDER is required (use --plex-config-folder or set PLEX_CONFIG_FOLDER environment variable)')
250
+ else:
251
+ # Path is provided, validate it
252
+ if not os.path.exists(plex_config_folder):
253
+ # Enhanced debugging for path issues
254
+ debug_info = []
255
+ debug_info.append(f'PLEX_CONFIG_FOLDER ({plex_config_folder}) does not exist')
256
+
257
+ # Walk back to find existing parent directories
258
+ current_path = plex_config_folder
259
+ found_existing = False
260
+ while current_path and current_path != '/' and current_path != os.path.dirname(current_path):
261
+ parent_path = os.path.dirname(current_path)
262
+ if os.path.exists(parent_path):
263
+ if not found_existing:
264
+ debug_info.append(f'Checked folder path and found that up to this directory exists: {parent_path}')
265
+ found_existing = True
266
+ try:
267
+ contents = os.listdir(parent_path)
268
+ debug_info.append(f'Contents of {parent_path}:')
269
+ if contents:
270
+ for item in sorted(contents)[:10]: # Show first 10 items
271
+ item_path = os.path.join(parent_path, item)
272
+ item_type = "DIR" if os.path.isdir(item_path) else "FILE"
273
+ debug_info.append(f' {item_type}: {item}')
274
+ if len(contents) > 10:
275
+ debug_info.append(f' ... and {len(contents) - 10} more items')
276
+ else:
277
+ debug_info.append(' (empty directory)')
278
+ except PermissionError:
279
+ debug_info.append(f' Permission denied reading {parent_path}')
280
+ break
281
+ current_path = parent_path
282
+
283
+ if not found_existing:
284
+ debug_info.append('Checked folder path but no parent directories exist')
285
+
286
+ # Show current working directory and environment
287
+ debug_info.append(f'Current working directory: {os.getcwd()}')
288
+ debug_info.append(f'User: {os.getenv("USER", "unknown")}')
289
+
290
+ validation_errors.append('\n'.join(debug_info))
291
+ else:
292
+ # Config folder exists, validate it contains Plex server structure
293
+ try:
294
+ config_contents = os.listdir(plex_config_folder)
295
+ found_folders = [item for item in config_contents if os.path.isdir(os.path.join(plex_config_folder, item))]
296
+
297
+ # Check for essential Plex server folders (only require Cache and Media)
298
+ essential_folders = ['Cache', 'Media']
299
+ found_essential = [folder for folder in essential_folders if folder in found_folders]
300
+
301
+ if len(found_essential) < 2: # Need both Cache and Media
302
+ debug_info = []
303
+ debug_info.append(f'PLEX_CONFIG_FOLDER exists but does not appear to be a valid Plex Media Server directory')
304
+ debug_info.append(f'Are you sure you mapped the right Plex folder?')
305
+ debug_info.append(f'Expected: Essential Plex folders (Cache, Media)')
306
+ debug_info.append(f'Found: {sorted(found_folders)}')
307
+ debug_info.append(f'Missing: {sorted([f for f in essential_folders if f not in found_folders])}')
308
+ debug_info.append(f'💡 Tip: Point to the main Plex directory:')
309
+ debug_info.append(f' Linux: /var/lib/plexmediaserver/Library/Application Support/Plex Media Server')
310
+ debug_info.append(f' Docker: /config/plex/Library/Application Support/Plex Media Server')
311
+ debug_info.append(f' Windows: C:\\Users\\[Username]\\AppData\\Local\\Plex Media Server')
312
+ debug_info.append(f' macOS: ~/Library/Application Support/Plex Media Server')
313
+ validation_errors.append('\n'.join(debug_info))
314
+ else:
315
+ # Config folder looks good, now check Media/localhost
316
+ media_path = os.path.join(plex_config_folder, 'Media')
317
+ localhost_path = os.path.join(media_path, 'localhost')
318
+
319
+ if not os.path.exists(media_path):
320
+ validation_errors.append(f'PLEX_CONFIG_FOLDER/Media directory does not exist: {media_path}')
321
+ elif not os.path.exists(localhost_path):
322
+ validation_errors.append(f'PLEX_CONFIG_FOLDER/Media/localhost directory does not exist: {localhost_path}')
323
+ else:
324
+ # localhost folder exists, validate it contains Plex database structure
325
+ try:
326
+ localhost_contents = os.listdir(localhost_path)
327
+ found_localhost_folders = [item for item in localhost_contents if os.path.isdir(os.path.join(localhost_path, item))]
328
+
329
+ # Check for either hex directories (0-f) or standard Plex folders
330
+ hex_folders = [item for item in localhost_contents if len(item) == 1 and item in '0123456789abcdef']
331
+ standard_folders = ['Metadata', 'Cache', 'Plug-ins', 'Logs', 'Plug-in Support']
332
+ found_standard = [folder for folder in standard_folders if folder in found_localhost_folders]
333
+
334
+ # Accept if we have either hex directories OR standard folders
335
+ has_hex_structure = len(hex_folders) >= 10 # Most of 0-f
336
+ has_standard_structure = len(found_standard) >= 3 # At least 3 standard folders
337
+
338
+ if not has_hex_structure and not has_standard_structure:
339
+ debug_info = []
340
+ debug_info.append(f'PLEX_CONFIG_FOLDER/Media/localhost exists but does not appear to be a valid Plex database')
341
+ debug_info.append(f'Expected: Either hex directories (0-f) OR standard Plex folders (Metadata, Cache, etc.)')
342
+ debug_info.append(f'Found: {sorted(found_localhost_folders)}')
343
+ if hex_folders:
344
+ debug_info.append(f'Hex directories found: {len(hex_folders)}/16 (need 10+)')
345
+ if found_standard:
346
+ debug_info.append(f'Standard folders found: {len(found_standard)}/5 (need 3+)')
347
+ debug_info.append(f'This suggests the path may not point to the correct Plex Media Server database location')
348
+ validation_errors.append('\n'.join(debug_info))
349
+ except PermissionError:
350
+ validation_errors.append(f'Permission denied reading localhost folder: {localhost_path}')
351
+ except PermissionError:
352
+ validation_errors.append(f'Permission denied reading PLEX_CONFIG_FOLDER: {plex_config_folder}')
353
+
354
+ # Validate numeric ranges
355
+ if plex_bif_frame_interval < 1 or plex_bif_frame_interval > 60:
356
+ validation_errors.append(f'PLEX_BIF_FRAME_INTERVAL must be between 1-60 seconds (got: {plex_bif_frame_interval})')
357
+
358
+ if thumbnail_quality < 1 or thumbnail_quality > 10:
359
+ validation_errors.append(f'THUMBNAIL_QUALITY must be between 1-10 (got: {thumbnail_quality})')
360
+
361
+ if plex_timeout < 10 or plex_timeout > 3600:
362
+ validation_errors.append(f'PLEX_TIMEOUT must be between 10-3600 seconds (got: {plex_timeout})')
363
+
364
+ # Validate thread counts
365
+ if gpu_threads < 0 or gpu_threads > 32:
366
+ validation_errors.append(f'GPU_THREADS must be between 0-32 (got: {gpu_threads})')
367
+
368
+ if cpu_threads < 0 or cpu_threads > 32:
369
+ validation_errors.append(f'CPU_THREADS must be between 0-32 (got: {cpu_threads})')
370
+
371
+ # Validate gpu_selection format
372
+ if gpu_selection.lower() != 'all':
373
+ try:
374
+ # Parse comma-separated GPU indices
375
+ gpu_indices = [int(x.strip()) for x in gpu_selection.split(',') if x.strip()]
376
+ if not gpu_indices:
377
+ validation_errors.append(f'GPU_SELECTION must be "all" or comma-separated GPU indices (got: {gpu_selection})')
378
+ elif any(idx < 0 for idx in gpu_indices):
379
+ validation_errors.append(f'GPU_SELECTION indices must be non-negative (got: {gpu_selection})')
380
+ except ValueError:
381
+ validation_errors.append(f'GPU_SELECTION must be "all" or comma-separated integers (got: {gpu_selection})')
382
+
383
+ # Validate tmp_folder exists and is writable (user must create it)
384
+ if not os.path.exists(tmp_folder):
385
+ validation_errors.append(f'TMP_FOLDER does not exist: {tmp_folder}')
386
+ validation_errors.append(f'Please create the directory first: mkdir -p {tmp_folder}')
387
+ elif not os.access(tmp_folder, os.W_OK):
388
+ validation_errors.append(f'TMP_FOLDER ({tmp_folder}) is not writable')
389
+ validation_errors.append(f'Please fix permissions: chmod 755 {tmp_folder}')
390
+
391
+ # Additional safety check: warn if tmp_folder is a system directory
392
+ if tmp_folder in ['/tmp', '/var/tmp', '/']:
393
+ validation_errors.append(f'TMP_FOLDER should not be a system directory like {tmp_folder}. Use a subdirectory instead (e.g., {tmp_folder}/plex_previews)')
394
+
395
+ # Check available disk space in tmp_folder
396
+ if os.path.exists(tmp_folder):
397
+ try:
398
+ statvfs = os.statvfs(tmp_folder)
399
+ free_space_gb = (statvfs.f_frsize * statvfs.f_bavail) / (1024**3)
400
+ if free_space_gb < 1: # Less than 1GB
401
+ validation_errors.append(f'TMP_FOLDER has less than 1GB free space ({free_space_gb:.1f}GB available)')
402
+ except OSError:
403
+ validation_errors.append(f'Cannot check disk space for TMP_FOLDER ({tmp_folder})')
404
+
405
+ # Handle missing parameters (show help)
406
+ if missing_params:
407
+ logger.error('❌ Configuration Error: Missing required parameters:')
408
+ for i, error_msg in enumerate(missing_params, 1):
409
+ logger.error(f' {i}. {error_msg}')
410
+ logger.info('')
411
+
412
+ # Show Docker-optimized help if running in Docker, otherwise show CLI help
413
+ if is_docker_environment():
414
+ show_docker_help()
415
+ else:
416
+ logger.info('📋 Showing help for all available options:')
417
+ logger.info('=' * 60)
418
+ # Show help automatically
419
+ sys.argv = [sys.argv[0], '--help']
420
+ try:
421
+ from .cli import parse_arguments
422
+ parse_arguments()
423
+ except SystemExit:
424
+ pass
425
+
426
+ return None # Return None to indicate validation failure
427
+
428
+ # Handle validation errors (standard error messages)
429
+ if validation_errors:
430
+ logger.error('❌ Configuration Error:')
431
+ for i, error_msg in enumerate(validation_errors, 1):
432
+ logger.error(f' {i}. {error_msg}')
433
+ return None # Return None to indicate validation failure
434
+
435
+ # Validate thread configuration
436
+ if cpu_threads == 0 and gpu_threads == 0:
437
+ logger.error('❌ Configuration Error: Both CPU_THREADS and GPU_THREADS are set to 0.')
438
+ logger.error('📋 At least one processing method must be enabled.')
439
+ logger.info('💡 Use --help to see all available options.')
440
+ logger.info('💡 Example: plex-generate-previews --cpu-threads 4 --gpu-threads 2')
441
+ sys.exit(1)
442
+
443
+ config = Config(
444
+ plex_url=plex_url,
445
+ plex_token=plex_token,
446
+ plex_timeout=plex_timeout,
447
+ plex_libraries=plex_libraries,
448
+ plex_config_folder=plex_config_folder,
449
+ plex_local_videos_path_mapping=plex_local_videos_path_mapping,
450
+ plex_videos_path_mapping=plex_videos_path_mapping,
451
+ plex_bif_frame_interval=plex_bif_frame_interval,
452
+ thumbnail_quality=thumbnail_quality,
453
+ regenerate_thumbnails=regenerate_thumbnails,
454
+ gpu_threads=gpu_threads,
455
+ cpu_threads=cpu_threads,
456
+ gpu_selection=gpu_selection,
457
+ tmp_folder=tmp_folder,
458
+ ffmpeg_path=ffmpeg_path,
459
+ log_level=log_level
460
+ )
461
+
462
+ # Set the timeout envvar for https://github.com/pkkid/python-plexapi
463
+ os.environ["PLEXAPI_PLEXAPI_TIMEOUT"] = str(config.plex_timeout)
464
+
465
+ # Output debug information
466
+ logger.debug(f'PLEX_URL = {config.plex_url}')
467
+ logger.debug(f'PLEX_BIF_FRAME_INTERVAL = {config.plex_bif_frame_interval}')
468
+ logger.debug(f'THUMBNAIL_QUALITY = {config.thumbnail_quality}')
469
+ logger.debug(f'PLEX_CONFIG_FOLDER = {config.plex_config_folder}')
470
+ logger.debug(f'TMP_FOLDER = {config.tmp_folder}')
471
+ logger.debug(f'PLEX_TIMEOUT = {config.plex_timeout}')
472
+ logger.debug(f'PLEX_LOCAL_VIDEOS_PATH_MAPPING = {config.plex_local_videos_path_mapping}')
473
+ logger.debug(f'PLEX_VIDEOS_PATH_MAPPING = {config.plex_videos_path_mapping}')
474
+ logger.debug(f'GPU_THREADS = {config.gpu_threads}')
475
+ logger.debug(f'CPU_THREADS = {config.cpu_threads}')
476
+ logger.debug(f'GPU_SELECTION = {config.gpu_selection}')
477
+ logger.debug(f'REGENERATE_THUMBNAILS = {config.regenerate_thumbnails}')
478
+
479
+ return config