├── HELP.md ├── LICENSE ├── README.md ├── SIM_Windows_App.zip ├── StarBuddy.1.0Beta.zip ├── StartApp.bat ├── app.ico ├── banner.png ├── data ├── designs │ ├── blue.json │ ├── contrast.json │ ├── dark.json │ ├── dark_amber.json │ ├── dark_cyan.json │ ├── dark_forest.json │ ├── dark_purple.json │ ├── dark_rose.json │ ├── icons │ │ └── checkmark.png │ ├── light.json │ ├── midnight_blue.json │ ├── purple.json │ └── red.json └── lang │ ├── cs.json │ ├── da.json │ ├── de.json │ ├── el.json │ ├── en_GB.json │ ├── en_US.json │ ├── es.json │ ├── fa.json │ ├── fi.json │ ├── fr.json │ ├── he.json │ ├── hi.json │ ├── hu.json │ ├── id.json │ ├── it.json │ ├── ja.json │ ├── ko.json │ ├── ms.json │ ├── nl.json │ ├── no.json │ ├── pl.json │ ├── pt_BR.json │ ├── pt_PT.json │ ├── ro.json │ ├── ru.json │ ├── sv.json │ ├── th.json │ ├── tr.json │ ├── uk.json │ ├── vi.json │ ├── zh_CN.json │ └── zh_TW.json ├── designs ├── blue.json ├── contrast.json ├── dark.json ├── dark_amber.json ├── dark_cyan.json ├── dark_forest.json ├── dark_purple.json ├── dark_rose.json ├── icons │ └── checkmark.png ├── light.json ├── midnight_blue.json ├── purple.json └── red.json ├── main.py ├── requirements.txt ├── scripts └── update_image_dimensions.py ├── setup_env.py └── src ├── ai ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ ├── batch_processor.cpython-312.pyc │ ├── image_processor.cpython-312.pyc │ └── ollama_batch_processor.cpython-312.pyc ├── batch_processor.py ├── image_processor.py └── ollama_batch_processor.py ├── cache ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ ├── cache_config.cpython-312.pyc │ ├── cache_manager.cpython-312.pyc │ └── image_cache.cpython-312.pyc ├── cache_config.py ├── cache_manager.py └── image_cache.py ├── config ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ ├── config_manager.cpython-312.pyc │ ├── language_manager.cpython-312.pyc │ └── theme_manager.cpython-312.pyc ├── config_manager.py ├── language_manager.py └── theme_manager.py ├── database ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ ├── db_connection.cpython-312.pyc │ ├── db_core.cpython-312.pyc │ ├── db_indexing.cpython-312.pyc │ ├── db_manager.cpython-312.pyc │ ├── db_operations.cpython-312.pyc │ ├── db_operations_extension.cpython-312.pyc │ ├── db_optimization_utils.cpython-312.pyc │ ├── db_optimizer.cpython-312.pyc │ ├── db_repair.cpython-312.pyc │ ├── db_safe_operations.cpython-312.pyc │ ├── db_sharding.cpython-312.pyc │ ├── db_startup_repair.cpython-312.pyc │ ├── db_statement_cache.cpython-312.pyc │ ├── db_upgrade.cpython-312.pyc │ ├── enhanced_search.cpython-312.pyc │ └── performance_optimizer.cpython-312.pyc ├── db_connection.py ├── db_core.py ├── db_indexing.py ├── db_manager.py ├── db_manager.py.bak ├── db_manager.py.new ├── db_operations.py ├── db_operations_extension.py ├── db_optimization_utils.py ├── db_optimizer.py ├── db_optimizer_fix.py ├── db_repair.py ├── db_safe_operations.py ├── db_sharding.py ├── db_startup_repair.py ├── db_statement_cache.py ├── db_upgrade.py ├── enhanced_search.py ├── performance_optimizer.py └── repair_thumbnail_paths.py ├── image_processing ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ ├── format_optimizer.cpython-312.pyc │ ├── image_scanner.cpython-312.pyc │ ├── optimized_thumbnail_generator.cpython-312.pyc │ └── thumbnail_generator.cpython-312.pyc ├── format_optimizer.py ├── image_scanner.py ├── optimized_thumbnail_generator.py └── thumbnail_generator.py ├── memory ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ ├── image_processor_integration.cpython-312.pyc │ ├── image_processor_pool.cpython-312.pyc │ ├── memory_pool.cpython-312.pyc │ ├── memory_utils.cpython-312.pyc │ └── resource_manager.cpython-312.pyc ├── image_processor_integration.py ├── image_processor_pool.py ├── memory_pool.py ├── memory_utils.py └── resource_manager.py ├── processing ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ ├── batch_operations.cpython-312.pyc │ ├── parallel_pipeline.cpython-312.pyc │ └── task_manager.cpython-312.pyc ├── background_scanner.py ├── batch_operations.py ├── parallel_pipeline.py └── task_manager.py ├── scanner ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ └── background_scanner.cpython-312.pyc └── background_scanner.py ├── ui ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ ├── catalog_panel.cpython-312.pyc │ ├── database_dimensions_update_dialog.cpython-312.pyc │ ├── database_maintenance_dialog.cpython-312.pyc │ ├── database_optimization_dialog.cpython-312.pyc │ ├── date_search_worker.cpython-312.pyc │ ├── enhanced_search_panel.cpython-312.pyc │ ├── export_dialog.cpython-312.pyc │ ├── folder_panel.cpython-312.pyc │ ├── hover_preview_widget.cpython-312.pyc │ ├── lazy_thumbnail_loader.cpython-312.pyc │ ├── main_window.cpython-312.pyc │ ├── main_window_language.cpython-312.pyc │ ├── main_window_search_integration.cpython-312.pyc │ ├── metadata_panel.cpython-312.pyc │ ├── notification_manager.cpython-312.pyc │ ├── pagination_integration.cpython-312.pyc │ ├── progress_dialog.cpython-312.pyc │ ├── search_panel.cpython-312.pyc │ ├── settings_dialog.cpython-312.pyc │ ├── thumbnail_browser.cpython-312.pyc │ ├── thumbnail_browser_factory.cpython-312.pyc │ ├── thumbnail_browser_pagination.cpython-312.pyc │ ├── thumbnail_widget.cpython-312.pyc │ ├── view_all_images.cpython-312.pyc │ └── worker.cpython-312.pyc ├── advanced_optimization_dialog.py ├── catalog_panel.py ├── database_dimensions_update_dialog.py ├── database_maintenance_dialog.py ├── database_optimization_dialog.py ├── date_search_worker.py ├── enhanced_lazy_loading.py ├── enhanced_search_panel.py ├── error_handler.py ├── export_dialog.py ├── folder_panel.py ├── hover_preview_widget.py ├── launch_controller.py ├── lazy_thumbnail_loader.py ├── main_window.py ├── main_window_all_images.py ├── main_window_language.py ├── main_window_search_integration.py ├── metadata_panel.py ├── notification_manager.py ├── pagination_implementation.py ├── pagination_integration.py ├── progress_dialog.py ├── search_panel.py ├── settings_dialog.py ├── splash_screen.py ├── thumbnail_browser.py ├── thumbnail_browser_factory.py ├── thumbnail_browser_pagination.py ├── thumbnail_widget.py ├── view_all_images.py ├── virtualized_grid_widget.py ├── virtualized_thumbnail_grid.py └── worker.py ├── utilities ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-312.pyc │ └── convert_thumbnail_paths.cpython-312.pyc ├── convert_thumbnail_paths.py └── path_fixer.py └── utils ├── __init__.py ├── __pycache__ ├── __init__.cpython-312.pyc ├── image_dimensions_updater.cpython-312.pyc ├── image_utils.cpython-312.pyc └── update_metadata.cpython-312.pyc ├── image_dimensions_updater.py ├── image_utils.py └── update_metadata.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Starnodes2024 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # STARNODES Image Manager v1.1.0 2 | 3 | ## Quick Start 4 | 5 | **Ready-to-use portable version**: Extract `SIM_Windows_App.zip` to any location and run `STARNODES Image Manager.exe` 6 | 7 | ## What is STARNODES Image Manager? 8 | 9 | STARNODES Image Manager is a Windows application that helps you organize, search, and manage your image collections. It works with both local images and those generated by AI tools. 10 | 11 | ### Key Features 12 | 13 | - **AI-Powered Search**: Find images using natural language descriptions 14 | - **Smart Organization**: Create catalogs to group images by content, regardless of folder location 15 | - **Batch Operations**: Select multiple images for renaming, copying, or exporting 16 | - **Multi-Criteria Search**: Find images by text description, date, or dimensions 17 | - **Dimension Presets**: Quickly find images matching standard resolutions (HD, 4K, etc.) 18 | - **Multiple Themes**: Choose from 12 different visual styles 19 | 20 | ## Known Bugs in 1.1.0 21 | -when importing a database from a backup merge isn´t working at the moment. please use option: remove 22 | 23 | ## Getting Started 24 | 25 | ### Portable Version (Recommended) 26 | 27 | 1. **Download**: Get the `SIM_Windows_App.zip` file 28 | 2. **Extract**: Unzip to any location (even a USB drive) 29 | 3. **Run**: Launch `STARNODES Image Manager.exe` 30 | 31 | ### Developer Version 32 | 33 | If you prefer to run from source code: 34 | 35 | 1. **Setup**: Run `python setup_env.py` to create the environment and install dependencies 36 | 2. **Launch**: Start with `python main.py` or use `StartApp.bat` 37 | 38 | ### First Steps 39 | 40 | 1. Add folders containing your images using the + button in the Folders panel 41 | 2. Set up Ollama for AI descriptions (optional but recommended) 42 | 3. Browse, search, and organize your images 43 | 44 | ## Need Help? 45 | 46 | See the full documentation in HELP.md for detailed instructions. 47 | 48 | ```powershell 49 | # Clone the repository (or download and extract the ZIP) 50 | git clone https://github.com/Starnodes2024/STARNODES-Image-Manager.git 51 | cd STARNODES-Image-Manager 52 | 53 | # Run the setup script to create virtual environment and install dependencies 54 | python setup_env.py 55 | 56 | # Start the application 57 | python main.py 58 | ``` 59 | 60 | ## Requirements 61 | 62 | - Windows operating system 63 | - Python 3.8 or higher 64 | - Ollama server running locally or on a network (for AI-powered descriptions) 65 | watch https://ollama.com/ for more Info. 66 | Recommended model (every ollama vision model works): ollama run llava-phi3 67 | - At least one vision model installed on your Ollama server (llava recommended) 68 | - Basic image analysis available as fallback when Ollama is unavailable 69 | 70 | ## Organizing with Catalogs 71 | 72 | 1. Create a new catalog: 73 | - Right-click on any image and select "Add to Catalog" → "New Catalog..." 74 | - Enter a name and optional description for the catalog 75 | 76 | 2. Add images to catalogs: 77 | - Select one or more images 78 | - Right-click and choose "Add to Catalog" → [catalog name] 79 | 80 | 3. View catalog contents: 81 | - Click on a catalog name in the left panel (displays number of images in parentheses) 82 | - Browse all images assigned to that catalog 83 | 84 | 4. Remove images from catalogs: 85 | - While viewing a catalog, right-click on an image 86 | - Select "Remove from Catalog" 87 | 88 | 89 | ## Usage 90 | 91 | ### Adding Folders 92 | 93 | 1. Click the "Add Folder" button in the toolbar 94 | 2. Select a folder containing images 95 | 3. The application will scan the folder and generate thumbnails 96 | 97 | ### Browsing Images 98 | 99 | - Navigate folders in the left panel 100 | - View thumbnails in the main grid layout 101 | - Click a thumbnail to select it 102 | - Double-click to open the image 103 | - View > All Images shows your complete collection with pagination 104 | - Configure thumbnails per page (20, 50, 100, 200, or 500) to match your system capabilities 105 | 106 | ### Searching 107 | 108 | 1. Use the enhanced search panel to set up your search criteria: 109 | - **Text Search**: Enable the checkbox and enter search terms (e.g., "sunset beach", "dog playing") 110 | - **Date Range**: Enable the checkbox and select start/end dates 111 | - **Image Dimensions**: Enable the checkbox and specify width/height ranges or select a preset 112 | 2. Select your search scope: 113 | - **Current Folder**: Search only in the currently selected folder 114 | - **Current Catalog**: Search only in the currently selected catalog 115 | - **All Images**: Search across your entire collection 116 | 3. Click "Search" button to execute the search with all selected criteria 117 | 4. Browse the matching images in the thumbnail grid 118 | 5. The status bar will show how many images matched your search criteria 119 | 120 | ### Batch Operations 121 | 122 | 1. Select multiple images by holding Ctrl while clicking thumbnails 123 | 2. Right-click to access batch operations menu 124 | 3. Choose an operation (Generate AI Descriptions, Copy, Delete, Rename) 125 | 126 | ### Context Menu 127 | 128 | Right-click on any thumbnail to access these options: 129 | 130 | - **Copy to folder**: Copy the selected image(s) to another location 131 | - **Export with options**: Export images with format, description, and workflow options 132 | - **Add to Catalog**: Add the image to an existing catalog or create a new one 133 | - **Remove from Catalog**: Remove the image from the current catalog (when viewing a catalog) 134 | - **Open with...**: Open the image with your chosen application 135 | - **Locate on disk**: Open file explorer at the image's location 136 | - **Copy image to clipboard**: Copy the image to the system clipboard 137 | - **Generate AI description**: Generate or regenerate an AI description 138 | - **Edit description**: View or edit the AI-generated description 139 | - **Delete description**: Remove the AI-generated description 140 | - **Delete image**: Remove the image from the database (option to delete the file too) 141 | - **Batch operations**: Access batch operations for multiple selected images 142 | 143 | ## Customization 144 | 145 | You can customize application settings by editing the `config/settings.json` file: 146 | 147 | - Thumbnail size and quality 148 | - AI model settings 149 | - UI preferences 150 | - Monitoring options 151 | 152 | ## Development 153 | 154 | STARNODES Image Manager is built with: 155 | 156 | - **Python**: Core programming language 157 | - **PyQt6**: Modern UI framework 158 | - **SQLite**: Lightweight database 159 | - **Pillow**: Image processing 160 | - **Ollama API**: Integration with local LLM vision models for image descriptions 161 | - **NumPy**: For fallback image analysis 162 | 163 | The application architecture separates concerns into these key modules: 164 | 165 | - **Database**: Stores image information and descriptions 166 | - **Image Processing**: Handles scanning and thumbnail generation 167 | - **AI**: Manages the vision model and description generation 168 | - **UI**: Provides the user interface components 169 | 170 | ## License 171 | 172 | [MIT License](LICENSE) 173 | -------------------------------------------------------------------------------- /SIM_Windows_App.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/SIM_Windows_App.zip -------------------------------------------------------------------------------- /StarBuddy.1.0Beta.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/StarBuddy.1.0Beta.zip -------------------------------------------------------------------------------- /StartApp.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo Starting StarImageBrowse Application... 3 | cd /d "%~dp0" 4 | venv\Scripts\python.exe main.py 5 | if %ERRORLEVEL% NEQ 0 ( 6 | echo An error occurred while running the application. 7 | echo Press any key to close this window... 8 | pause > nul 9 | ) 10 | -------------------------------------------------------------------------------- /app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/app.ico -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/banner.png -------------------------------------------------------------------------------- /data/designs/blue.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blue Theme", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#ebf5ff", 8 | "text": "#333333", 9 | "border": "#c5d9f1" 10 | }, 11 | "menuBar": { 12 | "background": "#daeaff", 13 | "text": "#333333", 14 | "hover": "#c5d9f1", 15 | "border": "#b0c8e6" 16 | }, 17 | "toolbar": { 18 | "background": "#daeaff", 19 | "text": "#333333", 20 | "hover": "#c5d9f1", 21 | "border": "#b0c8e6" 22 | }, 23 | "panel": { 24 | "background": "#f0f5ff", 25 | "text": "#333333", 26 | "border": "#c5d9f1", 27 | "header": "#daeaff" 28 | }, 29 | "thumbnail": { 30 | "background": "#ffffff", 31 | "text": "#333333", 32 | "textSecondary": "#555555", 33 | "selected": "#c5d9f1", 34 | "selectedText": "#1a1a1a", 35 | "border": "#c5d9f1", 36 | "shadow": "#d0d0d0" 37 | }, 38 | "button": { 39 | "background": "#4b6eaf", 40 | "text": "#ffffff", 41 | "hover": "#5d80c1", 42 | "pressed": "#3a5c9d", 43 | "border": "#304b82" 44 | }, 45 | "input": { 46 | "background": "#ffffff", 47 | "text": "#333333", 48 | "placeholder": "#888888", 49 | "border": "#b0c8e6", 50 | "focus": "#4b6eaf" 51 | }, 52 | "scrollBar": { 53 | "background": "#ebf5ff", 54 | "handle": "#b0c8e6", 55 | "handleHover": "#4b6eaf" 56 | }, 57 | "statusBar": { 58 | "background": "#daeaff", 59 | "text": "#333333", 60 | "border": "#b0c8e6" 61 | }, 62 | "dialog": { 63 | "background": "#f0f5ff", 64 | "text": "#333333", 65 | "border": "#c5d9f1", 66 | "header": "#daeaff" 67 | }, 68 | "progressBar": { 69 | "background": "#ebf5ff", 70 | "progress": "#4b6eaf", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#0055aa", 75 | "hover": "#003d7a", 76 | "visited": "#551a8b" 77 | }, 78 | "error": { 79 | "text": "#cc0000", 80 | "background": "#ffeeee" 81 | }, 82 | "warning": { 83 | "text": "#cc6600", 84 | "background": "#fff5e6" 85 | }, 86 | "success": { 87 | "text": "#007700", 88 | "background": "#eeffee" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /data/designs/contrast.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Contrast", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#000000", 8 | "text": "#ffffff", 9 | "border": "#ffffff" 10 | }, 11 | "menuBar": { 12 | "background": "#000000", 13 | "text": "#ffffff", 14 | "hover": "#ffffff", 15 | "border": "#ffffff" 16 | }, 17 | "toolbar": { 18 | "background": "#000000", 19 | "text": "#ffffff", 20 | "hover": "#ffffff", 21 | "border": "#ffffff" 22 | }, 23 | "panel": { 24 | "background": "#000000", 25 | "text": "#ffffff", 26 | "border": "#ffffff", 27 | "header": "#000000" 28 | }, 29 | "thumbnail": { 30 | "background": "#000000", 31 | "text": "#ffffff", 32 | "textSecondary": "#ffffff", 33 | "selected": "#ffffff", 34 | "selectedText": "#000000", 35 | "border": "#ffffff", 36 | "shadow": "#000000" 37 | }, 38 | "button": { 39 | "background": "#000000", 40 | "text": "#ffffff", 41 | "hover": "#ffffff", 42 | "pressed": "#ffffff", 43 | "border": "#ffffff" 44 | }, 45 | "input": { 46 | "background": "#000000", 47 | "text": "#ffffff", 48 | "placeholder": "#ffffff", 49 | "border": "#ffffff", 50 | "focus": "#ffffff" 51 | }, 52 | "scrollBar": { 53 | "background": "#000000", 54 | "handle": "#ffffff", 55 | "handleHover": "#ffffff" 56 | }, 57 | "statusBar": { 58 | "background": "#000000", 59 | "text": "#ffffff", 60 | "border": "#ffffff" 61 | }, 62 | "dialog": { 63 | "background": "#000000", 64 | "text": "#ffffff", 65 | "border": "#ffffff", 66 | "header": "#000000" 67 | }, 68 | "progressBar": { 69 | "background": "#000000", 70 | "progress": "#ffffff", 71 | "text": "#000000" 72 | }, 73 | "link": { 74 | "text": "#ffffff", 75 | "hover": "#ffffff", 76 | "visited": "#ffffff" 77 | }, 78 | "error": { 79 | "text": "#ffffff", 80 | "background": "#000000" 81 | }, 82 | "warning": { 83 | "text": "#ffffff", 84 | "background": "#000000" 85 | }, 86 | "success": { 87 | "text": "#ffffff", 88 | "background": "#000000" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 11, 101 | "weight": "bold", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 13, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 15, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 11, 119 | "weight": "bold", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 11, 125 | "weight": "bold", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 11, 131 | "weight": "bold", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 10, 137 | "weight": "bold", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /data/designs/dark.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Theme", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#222222", 8 | "text": "#ffffff", 9 | "border": "#333333" 10 | }, 11 | "menuBar": { 12 | "background": "#333333", 13 | "text": "#ffffff", 14 | "hover": "#444444", 15 | "border": "#444444" 16 | }, 17 | "toolbar": { 18 | "background": "#333333", 19 | "text": "#ffffff", 20 | "hover": "#444444", 21 | "border": "#444444" 22 | }, 23 | "panel": { 24 | "background": "#2a2a2a", 25 | "text": "#ffffff", 26 | "border": "#444444", 27 | "header": "#333333" 28 | }, 29 | "thumbnail": { 30 | "background": "#333333", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#3a5894", 34 | "selectedText": "#ffffff", 35 | "border": "#444444", 36 | "shadow": "#111111" 37 | }, 38 | "button": { 39 | "background": "#444444", 40 | "text": "#ffffff", 41 | "hover": "#555555", 42 | "pressed": "#222222", 43 | "border": "#666666" 44 | }, 45 | "input": { 46 | "background": "#333333", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#555555", 50 | "focus": "#4477aa" 51 | }, 52 | "scrollBar": { 53 | "background": "#222222", 54 | "handle": "#444444", 55 | "handleHover": "#555555" 56 | }, 57 | "statusBar": { 58 | "background": "#222222", 59 | "text": "#dddddd", 60 | "border": "#333333" 61 | }, 62 | "dialog": { 63 | "background": "#2a2a2a", 64 | "text": "#ffffff", 65 | "border": "#444444", 66 | "header": "#333333" 67 | }, 68 | "progressBar": { 69 | "background": "#333333", 70 | "progress": "#5b7ec0", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#78aeff", 75 | "hover": "#99ccff", 76 | "visited": "#cc99ff" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /data/designs/dark_amber.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Amber", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#1a1410", 8 | "text": "#ffffff", 9 | "border": "#2a211c" 10 | }, 11 | "menuBar": { 12 | "background": "#2a211c", 13 | "text": "#ffffff", 14 | "hover": "#3b2e25", 15 | "border": "#3b2e25" 16 | }, 17 | "toolbar": { 18 | "background": "#2a211c", 19 | "text": "#ffffff", 20 | "hover": "#3b2e25", 21 | "border": "#3b2e25" 22 | }, 23 | "panel": { 24 | "background": "#211c17", 25 | "text": "#ffffff", 26 | "border": "#3b2e25", 27 | "header": "#2a211c" 28 | }, 29 | "thumbnail": { 30 | "background": "#2a211c", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#9c5a20", 34 | "selectedText": "#ffffff", 35 | "border": "#3b2e25", 36 | "shadow": "#110d0a" 37 | }, 38 | "button": { 39 | "background": "#3b2e25", 40 | "text": "#ffffff", 41 | "hover": "#4c3a2f", 42 | "pressed": "#1a1410", 43 | "border": "#5d4639" 44 | }, 45 | "input": { 46 | "background": "#2a211c", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#3b2e25", 50 | "focus": "#9c5a20" 51 | }, 52 | "scrollBar": { 53 | "background": "#1a1410", 54 | "handle": "#3b2e25", 55 | "handleHover": "#4c3a2f" 56 | }, 57 | "statusBar": { 58 | "background": "#1a1410", 59 | "text": "#dddddd", 60 | "border": "#2a211c" 61 | }, 62 | "dialog": { 63 | "background": "#211c17", 64 | "text": "#ffffff", 65 | "border": "#3b2e25", 66 | "header": "#2a211c" 67 | }, 68 | "progressBar": { 69 | "background": "#2a211c", 70 | "progress": "#c7751a", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#e8934d", 75 | "hover": "#f8a35d", 76 | "visited": "#cc9966" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /data/designs/dark_cyan.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Cyan", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#0f1a1c", 8 | "text": "#ffffff", 9 | "border": "#1a2c2f" 10 | }, 11 | "menuBar": { 12 | "background": "#1a2c2f", 13 | "text": "#ffffff", 14 | "hover": "#253d41", 15 | "border": "#253d41" 16 | }, 17 | "toolbar": { 18 | "background": "#1a2c2f", 19 | "text": "#ffffff", 20 | "hover": "#253d41", 21 | "border": "#253d41" 22 | }, 23 | "panel": { 24 | "background": "#152426", 25 | "text": "#ffffff", 26 | "border": "#253d41", 27 | "header": "#1a2c2f" 28 | }, 29 | "thumbnail": { 30 | "background": "#1a2c2f", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#1f7a8c", 34 | "selectedText": "#ffffff", 35 | "border": "#253d41", 36 | "shadow": "#0a1214" 37 | }, 38 | "button": { 39 | "background": "#253d41", 40 | "text": "#ffffff", 41 | "hover": "#2f4c51", 42 | "pressed": "#0f1a1c", 43 | "border": "#3b5c61" 44 | }, 45 | "input": { 46 | "background": "#1a2c2f", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#253d41", 50 | "focus": "#1f7a8c" 51 | }, 52 | "scrollBar": { 53 | "background": "#0f1a1c", 54 | "handle": "#253d41", 55 | "handleHover": "#2f4c51" 56 | }, 57 | "statusBar": { 58 | "background": "#0f1a1c", 59 | "text": "#dddddd", 60 | "border": "#1a2c2f" 61 | }, 62 | "dialog": { 63 | "background": "#152426", 64 | "text": "#ffffff", 65 | "border": "#253d41", 66 | "header": "#1a2c2f" 67 | }, 68 | "progressBar": { 69 | "background": "#1a2c2f", 70 | "progress": "#20b2c9", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#34c6de", 75 | "hover": "#4fd6ee", 76 | "visited": "#90d4de" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /data/designs/dark_forest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Forest", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#121a14", 8 | "text": "#ffffff", 9 | "border": "#1a2a1e" 10 | }, 11 | "menuBar": { 12 | "background": "#1a2a1e", 13 | "text": "#ffffff", 14 | "hover": "#243b2a", 15 | "border": "#243b2a" 16 | }, 17 | "toolbar": { 18 | "background": "#1a2a1e", 19 | "text": "#ffffff", 20 | "hover": "#243b2a", 21 | "border": "#243b2a" 22 | }, 23 | "panel": { 24 | "background": "#172219", 25 | "text": "#ffffff", 26 | "border": "#243b2a", 27 | "header": "#1a2a1e" 28 | }, 29 | "thumbnail": { 30 | "background": "#1a2a1e", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#2a5a37", 34 | "selectedText": "#ffffff", 35 | "border": "#243b2a", 36 | "shadow": "#0a110c" 37 | }, 38 | "button": { 39 | "background": "#243b2a", 40 | "text": "#ffffff", 41 | "hover": "#2e4c34", 42 | "pressed": "#121a14", 43 | "border": "#365d42" 44 | }, 45 | "input": { 46 | "background": "#1a2a1e", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#243b2a", 50 | "focus": "#2a5a37" 51 | }, 52 | "scrollBar": { 53 | "background": "#121a14", 54 | "handle": "#243b2a", 55 | "handleHover": "#2e4c34" 56 | }, 57 | "statusBar": { 58 | "background": "#121a14", 59 | "text": "#dddddd", 60 | "border": "#1a2a1e" 61 | }, 62 | "dialog": { 63 | "background": "#172219", 64 | "text": "#ffffff", 65 | "border": "#243b2a", 66 | "header": "#1a2a1e" 67 | }, 68 | "progressBar": { 69 | "background": "#1a2a1e", 70 | "progress": "#2a5a37", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#4dba7a", 75 | "hover": "#5eca8a", 76 | "visited": "#99cc99" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /data/designs/dark_purple.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Purple", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#17141f", 8 | "text": "#ffffff", 9 | "border": "#251f33" 10 | }, 11 | "menuBar": { 12 | "background": "#251f33", 13 | "text": "#ffffff", 14 | "hover": "#352a47", 15 | "border": "#352a47" 16 | }, 17 | "toolbar": { 18 | "background": "#251f33", 19 | "text": "#ffffff", 20 | "hover": "#352a47", 21 | "border": "#352a47" 22 | }, 23 | "panel": { 24 | "background": "#1e1a29", 25 | "text": "#ffffff", 26 | "border": "#352a47", 27 | "header": "#251f33" 28 | }, 29 | "thumbnail": { 30 | "background": "#251f33", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#6c3c99", 34 | "selectedText": "#ffffff", 35 | "border": "#352a47", 36 | "shadow": "#0e0b14" 37 | }, 38 | "button": { 39 | "background": "#352a47", 40 | "text": "#ffffff", 41 | "hover": "#463759", 42 | "pressed": "#17141f", 43 | "border": "#57446c" 44 | }, 45 | "input": { 46 | "background": "#251f33", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#352a47", 50 | "focus": "#6c3c99" 51 | }, 52 | "scrollBar": { 53 | "background": "#17141f", 54 | "handle": "#352a47", 55 | "handleHover": "#463759" 56 | }, 57 | "statusBar": { 58 | "background": "#17141f", 59 | "text": "#dddddd", 60 | "border": "#251f33" 61 | }, 62 | "dialog": { 63 | "background": "#1e1a29", 64 | "text": "#ffffff", 65 | "border": "#352a47", 66 | "header": "#251f33" 67 | }, 68 | "progressBar": { 69 | "background": "#251f33", 70 | "progress": "#8a4dcc", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#a56df0", 75 | "hover": "#b57dff", 76 | "visited": "#d4b0ff" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /data/designs/dark_rose.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Rose", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#1a1216", 8 | "text": "#ffffff", 9 | "border": "#2a1e24" 10 | }, 11 | "menuBar": { 12 | "background": "#2a1e24", 13 | "text": "#ffffff", 14 | "hover": "#3d2a35", 15 | "border": "#3d2a35" 16 | }, 17 | "toolbar": { 18 | "background": "#2a1e24", 19 | "text": "#ffffff", 20 | "hover": "#3d2a35", 21 | "border": "#3d2a35" 22 | }, 23 | "panel": { 24 | "background": "#221a1f", 25 | "text": "#ffffff", 26 | "border": "#3d2a35", 27 | "header": "#2a1e24" 28 | }, 29 | "thumbnail": { 30 | "background": "#2a1e24", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#9c3662", 34 | "selectedText": "#ffffff", 35 | "border": "#3d2a35", 36 | "shadow": "#110c0f" 37 | }, 38 | "button": { 39 | "background": "#3d2a35", 40 | "text": "#ffffff", 41 | "hover": "#4e3644", 42 | "pressed": "#1a1216", 43 | "border": "#5f4253" 44 | }, 45 | "input": { 46 | "background": "#2a1e24", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#3d2a35", 50 | "focus": "#9c3662" 51 | }, 52 | "scrollBar": { 53 | "background": "#1a1216", 54 | "handle": "#3d2a35", 55 | "handleHover": "#4e3644" 56 | }, 57 | "statusBar": { 58 | "background": "#1a1216", 59 | "text": "#dddddd", 60 | "border": "#2a1e24" 61 | }, 62 | "dialog": { 63 | "background": "#221a1f", 64 | "text": "#ffffff", 65 | "border": "#3d2a35", 66 | "header": "#2a1e24" 67 | }, 68 | "progressBar": { 69 | "background": "#2a1e24", 70 | "progress": "#c74b81", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#e86c9d", 75 | "hover": "#f87cad", 76 | "visited": "#cc99bb" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /data/designs/icons/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/data/designs/icons/checkmark.png -------------------------------------------------------------------------------- /data/designs/light.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Light Theme", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#f5f5f5", 8 | "text": "#333333", 9 | "border": "#d0d0d0" 10 | }, 11 | "menuBar": { 12 | "background": "#f0f0f0", 13 | "text": "#333333", 14 | "hover": "#e0e0e0", 15 | "border": "#d0d0d0" 16 | }, 17 | "toolbar": { 18 | "background": "#f0f0f0", 19 | "text": "#333333", 20 | "hover": "#e0e0e0", 21 | "border": "#d0d0d0" 22 | }, 23 | "panel": { 24 | "background": "#ffffff", 25 | "text": "#333333", 26 | "border": "#d0d0d0", 27 | "header": "#e5e5e5" 28 | }, 29 | "thumbnail": { 30 | "background": "#ffffff", 31 | "text": "#333333", 32 | "textSecondary": "#666666", 33 | "selected": "#e6f2ff", 34 | "selectedText": "#333333", 35 | "border": "#d0d0d0", 36 | "shadow": "#c0c0c0" 37 | }, 38 | "button": { 39 | "background": "#f0f0f0", 40 | "text": "#333333", 41 | "hover": "#e0e0e0", 42 | "pressed": "#d0d0d0", 43 | "border": "#c0c0c0" 44 | }, 45 | "input": { 46 | "background": "#ffffff", 47 | "text": "#333333", 48 | "placeholder": "#888888", 49 | "border": "#c0c0c0", 50 | "focus": "#a0c8f0" 51 | }, 52 | "scrollBar": { 53 | "background": "#f0f0f0", 54 | "handle": "#c0c0c0", 55 | "handleHover": "#a0a0a0" 56 | }, 57 | "statusBar": { 58 | "background": "#f0f0f0", 59 | "text": "#333333", 60 | "border": "#d0d0d0" 61 | }, 62 | "dialog": { 63 | "background": "#ffffff", 64 | "text": "#333333", 65 | "border": "#d0d0d0", 66 | "header": "#f0f0f0" 67 | }, 68 | "progressBar": { 69 | "background": "#f0f0f0", 70 | "progress": "#4b6eaf", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#0066cc", 75 | "hover": "#0055aa", 76 | "visited": "#551a8b" 77 | }, 78 | "error": { 79 | "text": "#cc0000", 80 | "background": "#ffeeee" 81 | }, 82 | "warning": { 83 | "text": "#cc6600", 84 | "background": "#fff5e6" 85 | }, 86 | "success": { 87 | "text": "#007700", 88 | "background": "#eeffee" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /data/designs/midnight_blue.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Midnight Blue", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#141a24", 8 | "text": "#ffffff", 9 | "border": "#1c2433" 10 | }, 11 | "menuBar": { 12 | "background": "#1c2433", 13 | "text": "#ffffff", 14 | "hover": "#253349", 15 | "border": "#253349" 16 | }, 17 | "toolbar": { 18 | "background": "#1c2433", 19 | "text": "#ffffff", 20 | "hover": "#253349", 21 | "border": "#253349" 22 | }, 23 | "panel": { 24 | "background": "#192231", 25 | "text": "#ffffff", 26 | "border": "#253349", 27 | "header": "#1c2433" 28 | }, 29 | "thumbnail": { 30 | "background": "#1c2433", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#2a4887", 34 | "selectedText": "#ffffff", 35 | "border": "#253349", 36 | "shadow": "#0d1117" 37 | }, 38 | "button": { 39 | "background": "#253349", 40 | "text": "#ffffff", 41 | "hover": "#30425e", 42 | "pressed": "#111111", 43 | "border": "#3a536f" 44 | }, 45 | "input": { 46 | "background": "#1c2433", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#253349", 50 | "focus": "#2a4887" 51 | }, 52 | "scrollBar": { 53 | "background": "#141a24", 54 | "handle": "#253349", 55 | "handleHover": "#30425e" 56 | }, 57 | "statusBar": { 58 | "background": "#141a24", 59 | "text": "#dddddd", 60 | "border": "#1c2433" 61 | }, 62 | "dialog": { 63 | "background": "#192231", 64 | "text": "#ffffff", 65 | "border": "#253349", 66 | "header": "#1c2433" 67 | }, 68 | "progressBar": { 69 | "background": "#1c2433", 70 | "progress": "#2a4887", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#4d87da", 75 | "hover": "#5d97ea", 76 | "visited": "#9988dd" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /data/designs/purple.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Purple Theme", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#f5f0ff", 8 | "text": "#333333", 9 | "border": "#e0d5ea" 10 | }, 11 | "menuBar": { 12 | "background": "#ebe0ff", 13 | "text": "#333333", 14 | "hover": "#d8c5ea", 15 | "border": "#c8b0e6" 16 | }, 17 | "toolbar": { 18 | "background": "#ebe0ff", 19 | "text": "#333333", 20 | "hover": "#d8c5ea", 21 | "border": "#c8b0e6" 22 | }, 23 | "panel": { 24 | "background": "#f8f2ff", 25 | "text": "#333333", 26 | "border": "#e0d5ea", 27 | "header": "#ebe0ff" 28 | }, 29 | "thumbnail": { 30 | "background": "#ffffff", 31 | "text": "#333333", 32 | "textSecondary": "#555555", 33 | "selected": "#d8c5ea", 34 | "selectedText": "#1a1a1a", 35 | "border": "#e0d5ea", 36 | "shadow": "#d0d0d0" 37 | }, 38 | "button": { 39 | "background": "#8a65af", 40 | "text": "#ffffff", 41 | "hover": "#9c77c1", 42 | "pressed": "#7954a1", 43 | "border": "#674382" 44 | }, 45 | "input": { 46 | "background": "#ffffff", 47 | "text": "#333333", 48 | "placeholder": "#888888", 49 | "border": "#c8b0e6", 50 | "focus": "#8a65af" 51 | }, 52 | "scrollBar": { 53 | "background": "#f5f0ff", 54 | "handle": "#c8b0e6", 55 | "handleHover": "#8a65af" 56 | }, 57 | "statusBar": { 58 | "background": "#ebe0ff", 59 | "text": "#333333", 60 | "border": "#c8b0e6" 61 | }, 62 | "dialog": { 63 | "background": "#f8f2ff", 64 | "text": "#333333", 65 | "border": "#e0d5ea", 66 | "header": "#ebe0ff" 67 | }, 68 | "progressBar": { 69 | "background": "#f5f0ff", 70 | "progress": "#8a65af", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#6e3baa", 75 | "hover": "#5a2e8a", 76 | "visited": "#4d257a" 77 | }, 78 | "error": { 79 | "text": "#cc0000", 80 | "background": "#ffeeee" 81 | }, 82 | "warning": { 83 | "text": "#cc6600", 84 | "background": "#fff5e6" 85 | }, 86 | "success": { 87 | "text": "#007700", 88 | "background": "#eeffee" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /data/designs/red.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Red Theme", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#fff0f0", 8 | "text": "#333333", 9 | "border": "#e6d0d0" 10 | }, 11 | "menuBar": { 12 | "background": "#ffe0e0", 13 | "text": "#333333", 14 | "hover": "#eac5c5", 15 | "border": "#e6b0b0" 16 | }, 17 | "toolbar": { 18 | "background": "#ffe0e0", 19 | "text": "#333333", 20 | "hover": "#eac5c5", 21 | "border": "#e6b0b0" 22 | }, 23 | "panel": { 24 | "background": "#fff5f5", 25 | "text": "#333333", 26 | "border": "#e6d0d0", 27 | "header": "#ffe0e0" 28 | }, 29 | "thumbnail": { 30 | "background": "#ffffff", 31 | "text": "#333333", 32 | "textSecondary": "#555555", 33 | "selected": "#eac5c5", 34 | "selectedText": "#1a1a1a", 35 | "border": "#e6d0d0", 36 | "shadow": "#d0d0d0" 37 | }, 38 | "button": { 39 | "background": "#af4b4b", 40 | "text": "#ffffff", 41 | "hover": "#c15d5d", 42 | "pressed": "#9d3a3a", 43 | "border": "#823030" 44 | }, 45 | "input": { 46 | "background": "#ffffff", 47 | "text": "#333333", 48 | "placeholder": "#888888", 49 | "border": "#e6b0b0", 50 | "focus": "#af4b4b" 51 | }, 52 | "scrollBar": { 53 | "background": "#fff0f0", 54 | "handle": "#e6b0b0", 55 | "handleHover": "#af4b4b" 56 | }, 57 | "statusBar": { 58 | "background": "#ffe0e0", 59 | "text": "#333333", 60 | "border": "#e6b0b0" 61 | }, 62 | "dialog": { 63 | "background": "#fff5f5", 64 | "text": "#333333", 65 | "border": "#e6d0d0", 66 | "header": "#ffe0e0" 67 | }, 68 | "progressBar": { 69 | "background": "#fff0f0", 70 | "progress": "#af4b4b", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#aa0000", 75 | "hover": "#880000", 76 | "visited": "#660000" 77 | }, 78 | "error": { 79 | "text": "#cc0000", 80 | "background": "#ffeeee" 81 | }, 82 | "warning": { 83 | "text": "#cc6600", 84 | "background": "#fff5e6" 85 | }, 86 | "success": { 87 | "text": "#007700", 88 | "background": "#eeffee" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/blue.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blue Theme", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#ebf5ff", 8 | "text": "#333333", 9 | "border": "#c5d9f1" 10 | }, 11 | "menuBar": { 12 | "background": "#daeaff", 13 | "text": "#333333", 14 | "hover": "#c5d9f1", 15 | "border": "#b0c8e6" 16 | }, 17 | "toolbar": { 18 | "background": "#daeaff", 19 | "text": "#333333", 20 | "hover": "#c5d9f1", 21 | "border": "#b0c8e6" 22 | }, 23 | "panel": { 24 | "background": "#f0f5ff", 25 | "text": "#333333", 26 | "border": "#c5d9f1", 27 | "header": "#daeaff" 28 | }, 29 | "thumbnail": { 30 | "background": "#ffffff", 31 | "text": "#333333", 32 | "textSecondary": "#555555", 33 | "selected": "#c5d9f1", 34 | "selectedText": "#1a1a1a", 35 | "border": "#c5d9f1", 36 | "shadow": "#d0d0d0" 37 | }, 38 | "button": { 39 | "background": "#4b6eaf", 40 | "text": "#ffffff", 41 | "hover": "#5d80c1", 42 | "pressed": "#3a5c9d", 43 | "border": "#304b82" 44 | }, 45 | "input": { 46 | "background": "#ffffff", 47 | "text": "#333333", 48 | "placeholder": "#888888", 49 | "border": "#b0c8e6", 50 | "focus": "#4b6eaf" 51 | }, 52 | "scrollBar": { 53 | "background": "#ebf5ff", 54 | "handle": "#b0c8e6", 55 | "handleHover": "#4b6eaf" 56 | }, 57 | "statusBar": { 58 | "background": "#daeaff", 59 | "text": "#333333", 60 | "border": "#b0c8e6" 61 | }, 62 | "dialog": { 63 | "background": "#f0f5ff", 64 | "text": "#333333", 65 | "border": "#c5d9f1", 66 | "header": "#daeaff" 67 | }, 68 | "progressBar": { 69 | "background": "#ebf5ff", 70 | "progress": "#4b6eaf", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#0055aa", 75 | "hover": "#003d7a", 76 | "visited": "#551a8b" 77 | }, 78 | "error": { 79 | "text": "#cc0000", 80 | "background": "#ffeeee" 81 | }, 82 | "warning": { 83 | "text": "#cc6600", 84 | "background": "#fff5e6" 85 | }, 86 | "success": { 87 | "text": "#007700", 88 | "background": "#eeffee" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/contrast.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Contrast", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#000000", 8 | "text": "#ffffff", 9 | "border": "#ffffff" 10 | }, 11 | "menuBar": { 12 | "background": "#000000", 13 | "text": "#ffffff", 14 | "hover": "#ffffff", 15 | "border": "#ffffff" 16 | }, 17 | "toolbar": { 18 | "background": "#000000", 19 | "text": "#ffffff", 20 | "hover": "#ffffff", 21 | "border": "#ffffff" 22 | }, 23 | "panel": { 24 | "background": "#000000", 25 | "text": "#ffffff", 26 | "border": "#ffffff", 27 | "header": "#000000" 28 | }, 29 | "thumbnail": { 30 | "background": "#000000", 31 | "text": "#ffffff", 32 | "textSecondary": "#ffffff", 33 | "selected": "#ffffff", 34 | "selectedText": "#000000", 35 | "border": "#ffffff", 36 | "shadow": "#000000" 37 | }, 38 | "button": { 39 | "background": "#000000", 40 | "text": "#ffffff", 41 | "hover": "#ffffff", 42 | "pressed": "#ffffff", 43 | "border": "#ffffff" 44 | }, 45 | "input": { 46 | "background": "#000000", 47 | "text": "#ffffff", 48 | "placeholder": "#ffffff", 49 | "border": "#ffffff", 50 | "focus": "#ffffff" 51 | }, 52 | "scrollBar": { 53 | "background": "#000000", 54 | "handle": "#ffffff", 55 | "handleHover": "#ffffff" 56 | }, 57 | "statusBar": { 58 | "background": "#000000", 59 | "text": "#ffffff", 60 | "border": "#ffffff" 61 | }, 62 | "dialog": { 63 | "background": "#000000", 64 | "text": "#ffffff", 65 | "border": "#ffffff", 66 | "header": "#000000" 67 | }, 68 | "progressBar": { 69 | "background": "#000000", 70 | "progress": "#ffffff", 71 | "text": "#000000" 72 | }, 73 | "link": { 74 | "text": "#ffffff", 75 | "hover": "#ffffff", 76 | "visited": "#ffffff" 77 | }, 78 | "error": { 79 | "text": "#ffffff", 80 | "background": "#000000" 81 | }, 82 | "warning": { 83 | "text": "#ffffff", 84 | "background": "#000000" 85 | }, 86 | "success": { 87 | "text": "#ffffff", 88 | "background": "#000000" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 11, 101 | "weight": "bold", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 13, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 15, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 11, 119 | "weight": "bold", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 11, 125 | "weight": "bold", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 11, 131 | "weight": "bold", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 10, 137 | "weight": "bold", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/dark.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Theme", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#222222", 8 | "text": "#ffffff", 9 | "border": "#333333" 10 | }, 11 | "menuBar": { 12 | "background": "#333333", 13 | "text": "#ffffff", 14 | "hover": "#444444", 15 | "border": "#444444" 16 | }, 17 | "toolbar": { 18 | "background": "#333333", 19 | "text": "#ffffff", 20 | "hover": "#444444", 21 | "border": "#444444" 22 | }, 23 | "panel": { 24 | "background": "#2a2a2a", 25 | "text": "#ffffff", 26 | "border": "#444444", 27 | "header": "#333333" 28 | }, 29 | "thumbnail": { 30 | "background": "#333333", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#3a5894", 34 | "selectedText": "#ffffff", 35 | "border": "#444444", 36 | "shadow": "#111111" 37 | }, 38 | "button": { 39 | "background": "#444444", 40 | "text": "#ffffff", 41 | "hover": "#555555", 42 | "pressed": "#222222", 43 | "border": "#666666" 44 | }, 45 | "input": { 46 | "background": "#333333", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#555555", 50 | "focus": "#4477aa" 51 | }, 52 | "scrollBar": { 53 | "background": "#222222", 54 | "handle": "#444444", 55 | "handleHover": "#555555" 56 | }, 57 | "statusBar": { 58 | "background": "#222222", 59 | "text": "#dddddd", 60 | "border": "#333333" 61 | }, 62 | "dialog": { 63 | "background": "#2a2a2a", 64 | "text": "#ffffff", 65 | "border": "#444444", 66 | "header": "#333333" 67 | }, 68 | "progressBar": { 69 | "background": "#333333", 70 | "progress": "#5b7ec0", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#78aeff", 75 | "hover": "#99ccff", 76 | "visited": "#cc99ff" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/dark_amber.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Amber", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#1a1410", 8 | "text": "#ffffff", 9 | "border": "#2a211c" 10 | }, 11 | "menuBar": { 12 | "background": "#2a211c", 13 | "text": "#ffffff", 14 | "hover": "#3b2e25", 15 | "border": "#3b2e25" 16 | }, 17 | "toolbar": { 18 | "background": "#2a211c", 19 | "text": "#ffffff", 20 | "hover": "#3b2e25", 21 | "border": "#3b2e25" 22 | }, 23 | "panel": { 24 | "background": "#211c17", 25 | "text": "#ffffff", 26 | "border": "#3b2e25", 27 | "header": "#2a211c" 28 | }, 29 | "thumbnail": { 30 | "background": "#2a211c", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#9c5a20", 34 | "selectedText": "#ffffff", 35 | "border": "#3b2e25", 36 | "shadow": "#110d0a" 37 | }, 38 | "button": { 39 | "background": "#3b2e25", 40 | "text": "#ffffff", 41 | "hover": "#4c3a2f", 42 | "pressed": "#1a1410", 43 | "border": "#5d4639" 44 | }, 45 | "input": { 46 | "background": "#2a211c", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#3b2e25", 50 | "focus": "#9c5a20" 51 | }, 52 | "scrollBar": { 53 | "background": "#1a1410", 54 | "handle": "#3b2e25", 55 | "handleHover": "#4c3a2f" 56 | }, 57 | "statusBar": { 58 | "background": "#1a1410", 59 | "text": "#dddddd", 60 | "border": "#2a211c" 61 | }, 62 | "dialog": { 63 | "background": "#211c17", 64 | "text": "#ffffff", 65 | "border": "#3b2e25", 66 | "header": "#2a211c" 67 | }, 68 | "progressBar": { 69 | "background": "#2a211c", 70 | "progress": "#c7751a", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#e8934d", 75 | "hover": "#f8a35d", 76 | "visited": "#cc9966" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/dark_cyan.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Cyan", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#0f1a1c", 8 | "text": "#ffffff", 9 | "border": "#1a2c2f" 10 | }, 11 | "menuBar": { 12 | "background": "#1a2c2f", 13 | "text": "#ffffff", 14 | "hover": "#253d41", 15 | "border": "#253d41" 16 | }, 17 | "toolbar": { 18 | "background": "#1a2c2f", 19 | "text": "#ffffff", 20 | "hover": "#253d41", 21 | "border": "#253d41" 22 | }, 23 | "panel": { 24 | "background": "#152426", 25 | "text": "#ffffff", 26 | "border": "#253d41", 27 | "header": "#1a2c2f" 28 | }, 29 | "thumbnail": { 30 | "background": "#1a2c2f", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#1f7a8c", 34 | "selectedText": "#ffffff", 35 | "border": "#253d41", 36 | "shadow": "#0a1214" 37 | }, 38 | "button": { 39 | "background": "#253d41", 40 | "text": "#ffffff", 41 | "hover": "#2f4c51", 42 | "pressed": "#0f1a1c", 43 | "border": "#3b5c61" 44 | }, 45 | "input": { 46 | "background": "#1a2c2f", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#253d41", 50 | "focus": "#1f7a8c" 51 | }, 52 | "scrollBar": { 53 | "background": "#0f1a1c", 54 | "handle": "#253d41", 55 | "handleHover": "#2f4c51" 56 | }, 57 | "statusBar": { 58 | "background": "#0f1a1c", 59 | "text": "#dddddd", 60 | "border": "#1a2c2f" 61 | }, 62 | "dialog": { 63 | "background": "#152426", 64 | "text": "#ffffff", 65 | "border": "#253d41", 66 | "header": "#1a2c2f" 67 | }, 68 | "progressBar": { 69 | "background": "#1a2c2f", 70 | "progress": "#20b2c9", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#34c6de", 75 | "hover": "#4fd6ee", 76 | "visited": "#90d4de" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/dark_forest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Forest", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#121a14", 8 | "text": "#ffffff", 9 | "border": "#1a2a1e" 10 | }, 11 | "menuBar": { 12 | "background": "#1a2a1e", 13 | "text": "#ffffff", 14 | "hover": "#243b2a", 15 | "border": "#243b2a" 16 | }, 17 | "toolbar": { 18 | "background": "#1a2a1e", 19 | "text": "#ffffff", 20 | "hover": "#243b2a", 21 | "border": "#243b2a" 22 | }, 23 | "panel": { 24 | "background": "#172219", 25 | "text": "#ffffff", 26 | "border": "#243b2a", 27 | "header": "#1a2a1e" 28 | }, 29 | "thumbnail": { 30 | "background": "#1a2a1e", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#2a5a37", 34 | "selectedText": "#ffffff", 35 | "border": "#243b2a", 36 | "shadow": "#0a110c" 37 | }, 38 | "button": { 39 | "background": "#243b2a", 40 | "text": "#ffffff", 41 | "hover": "#2e4c34", 42 | "pressed": "#121a14", 43 | "border": "#365d42" 44 | }, 45 | "input": { 46 | "background": "#1a2a1e", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#243b2a", 50 | "focus": "#2a5a37" 51 | }, 52 | "scrollBar": { 53 | "background": "#121a14", 54 | "handle": "#243b2a", 55 | "handleHover": "#2e4c34" 56 | }, 57 | "statusBar": { 58 | "background": "#121a14", 59 | "text": "#dddddd", 60 | "border": "#1a2a1e" 61 | }, 62 | "dialog": { 63 | "background": "#172219", 64 | "text": "#ffffff", 65 | "border": "#243b2a", 66 | "header": "#1a2a1e" 67 | }, 68 | "progressBar": { 69 | "background": "#1a2a1e", 70 | "progress": "#2a5a37", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#4dba7a", 75 | "hover": "#5eca8a", 76 | "visited": "#99cc99" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/dark_purple.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Purple", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#17141f", 8 | "text": "#ffffff", 9 | "border": "#251f33" 10 | }, 11 | "menuBar": { 12 | "background": "#251f33", 13 | "text": "#ffffff", 14 | "hover": "#352a47", 15 | "border": "#352a47" 16 | }, 17 | "toolbar": { 18 | "background": "#251f33", 19 | "text": "#ffffff", 20 | "hover": "#352a47", 21 | "border": "#352a47" 22 | }, 23 | "panel": { 24 | "background": "#1e1a29", 25 | "text": "#ffffff", 26 | "border": "#352a47", 27 | "header": "#251f33" 28 | }, 29 | "thumbnail": { 30 | "background": "#251f33", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#6c3c99", 34 | "selectedText": "#ffffff", 35 | "border": "#352a47", 36 | "shadow": "#0e0b14" 37 | }, 38 | "button": { 39 | "background": "#352a47", 40 | "text": "#ffffff", 41 | "hover": "#463759", 42 | "pressed": "#17141f", 43 | "border": "#57446c" 44 | }, 45 | "input": { 46 | "background": "#251f33", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#352a47", 50 | "focus": "#6c3c99" 51 | }, 52 | "scrollBar": { 53 | "background": "#17141f", 54 | "handle": "#352a47", 55 | "handleHover": "#463759" 56 | }, 57 | "statusBar": { 58 | "background": "#17141f", 59 | "text": "#dddddd", 60 | "border": "#251f33" 61 | }, 62 | "dialog": { 63 | "background": "#1e1a29", 64 | "text": "#ffffff", 65 | "border": "#352a47", 66 | "header": "#251f33" 67 | }, 68 | "progressBar": { 69 | "background": "#251f33", 70 | "progress": "#8a4dcc", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#a56df0", 75 | "hover": "#b57dff", 76 | "visited": "#d4b0ff" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/dark_rose.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dark Rose", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#1a1216", 8 | "text": "#ffffff", 9 | "border": "#2a1e24" 10 | }, 11 | "menuBar": { 12 | "background": "#2a1e24", 13 | "text": "#ffffff", 14 | "hover": "#3d2a35", 15 | "border": "#3d2a35" 16 | }, 17 | "toolbar": { 18 | "background": "#2a1e24", 19 | "text": "#ffffff", 20 | "hover": "#3d2a35", 21 | "border": "#3d2a35" 22 | }, 23 | "panel": { 24 | "background": "#221a1f", 25 | "text": "#ffffff", 26 | "border": "#3d2a35", 27 | "header": "#2a1e24" 28 | }, 29 | "thumbnail": { 30 | "background": "#2a1e24", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#9c3662", 34 | "selectedText": "#ffffff", 35 | "border": "#3d2a35", 36 | "shadow": "#110c0f" 37 | }, 38 | "button": { 39 | "background": "#3d2a35", 40 | "text": "#ffffff", 41 | "hover": "#4e3644", 42 | "pressed": "#1a1216", 43 | "border": "#5f4253" 44 | }, 45 | "input": { 46 | "background": "#2a1e24", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#3d2a35", 50 | "focus": "#9c3662" 51 | }, 52 | "scrollBar": { 53 | "background": "#1a1216", 54 | "handle": "#3d2a35", 55 | "handleHover": "#4e3644" 56 | }, 57 | "statusBar": { 58 | "background": "#1a1216", 59 | "text": "#dddddd", 60 | "border": "#2a1e24" 61 | }, 62 | "dialog": { 63 | "background": "#221a1f", 64 | "text": "#ffffff", 65 | "border": "#3d2a35", 66 | "header": "#2a1e24" 67 | }, 68 | "progressBar": { 69 | "background": "#2a1e24", 70 | "progress": "#c74b81", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#e86c9d", 75 | "hover": "#f87cad", 76 | "visited": "#cc99bb" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/icons/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/designs/icons/checkmark.png -------------------------------------------------------------------------------- /designs/light.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Light Theme", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#f5f5f5", 8 | "text": "#333333", 9 | "border": "#d0d0d0" 10 | }, 11 | "menuBar": { 12 | "background": "#f0f0f0", 13 | "text": "#333333", 14 | "hover": "#e0e0e0", 15 | "border": "#d0d0d0" 16 | }, 17 | "toolbar": { 18 | "background": "#f0f0f0", 19 | "text": "#333333", 20 | "hover": "#e0e0e0", 21 | "border": "#d0d0d0" 22 | }, 23 | "panel": { 24 | "background": "#ffffff", 25 | "text": "#333333", 26 | "border": "#d0d0d0", 27 | "header": "#e5e5e5" 28 | }, 29 | "thumbnail": { 30 | "background": "#ffffff", 31 | "text": "#333333", 32 | "textSecondary": "#666666", 33 | "selected": "#e6f2ff", 34 | "selectedText": "#333333", 35 | "border": "#d0d0d0", 36 | "shadow": "#c0c0c0" 37 | }, 38 | "button": { 39 | "background": "#f0f0f0", 40 | "text": "#333333", 41 | "hover": "#e0e0e0", 42 | "pressed": "#d0d0d0", 43 | "border": "#c0c0c0" 44 | }, 45 | "input": { 46 | "background": "#ffffff", 47 | "text": "#333333", 48 | "placeholder": "#888888", 49 | "border": "#c0c0c0", 50 | "focus": "#a0c8f0" 51 | }, 52 | "scrollBar": { 53 | "background": "#f0f0f0", 54 | "handle": "#c0c0c0", 55 | "handleHover": "#a0a0a0" 56 | }, 57 | "statusBar": { 58 | "background": "#f0f0f0", 59 | "text": "#333333", 60 | "border": "#d0d0d0" 61 | }, 62 | "dialog": { 63 | "background": "#ffffff", 64 | "text": "#333333", 65 | "border": "#d0d0d0", 66 | "header": "#f0f0f0" 67 | }, 68 | "progressBar": { 69 | "background": "#f0f0f0", 70 | "progress": "#4b6eaf", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#0066cc", 75 | "hover": "#0055aa", 76 | "visited": "#551a8b" 77 | }, 78 | "error": { 79 | "text": "#cc0000", 80 | "background": "#ffeeee" 81 | }, 82 | "warning": { 83 | "text": "#cc6600", 84 | "background": "#fff5e6" 85 | }, 86 | "success": { 87 | "text": "#007700", 88 | "background": "#eeffee" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/midnight_blue.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Midnight Blue", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#141a24", 8 | "text": "#ffffff", 9 | "border": "#1c2433" 10 | }, 11 | "menuBar": { 12 | "background": "#1c2433", 13 | "text": "#ffffff", 14 | "hover": "#253349", 15 | "border": "#253349" 16 | }, 17 | "toolbar": { 18 | "background": "#1c2433", 19 | "text": "#ffffff", 20 | "hover": "#253349", 21 | "border": "#253349" 22 | }, 23 | "panel": { 24 | "background": "#192231", 25 | "text": "#ffffff", 26 | "border": "#253349", 27 | "header": "#1c2433" 28 | }, 29 | "thumbnail": { 30 | "background": "#1c2433", 31 | "text": "#ffffff", 32 | "textSecondary": "#cccccc", 33 | "selected": "#2a4887", 34 | "selectedText": "#ffffff", 35 | "border": "#253349", 36 | "shadow": "#0d1117" 37 | }, 38 | "button": { 39 | "background": "#253349", 40 | "text": "#ffffff", 41 | "hover": "#30425e", 42 | "pressed": "#111111", 43 | "border": "#3a536f" 44 | }, 45 | "input": { 46 | "background": "#1c2433", 47 | "text": "#ffffff", 48 | "placeholder": "#aaaaaa", 49 | "border": "#253349", 50 | "focus": "#2a4887" 51 | }, 52 | "scrollBar": { 53 | "background": "#141a24", 54 | "handle": "#253349", 55 | "handleHover": "#30425e" 56 | }, 57 | "statusBar": { 58 | "background": "#141a24", 59 | "text": "#dddddd", 60 | "border": "#1c2433" 61 | }, 62 | "dialog": { 63 | "background": "#192231", 64 | "text": "#ffffff", 65 | "border": "#253349", 66 | "header": "#1c2433" 67 | }, 68 | "progressBar": { 69 | "background": "#1c2433", 70 | "progress": "#2a4887", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#4d87da", 75 | "hover": "#5d97ea", 76 | "visited": "#9988dd" 77 | }, 78 | "error": { 79 | "text": "#ff6666", 80 | "background": "#552222" 81 | }, 82 | "warning": { 83 | "text": "#ffaa66", 84 | "background": "#553322" 85 | }, 86 | "success": { 87 | "text": "#66dd66", 88 | "background": "#225522" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/purple.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Purple Theme", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#f5f0ff", 8 | "text": "#333333", 9 | "border": "#e0d5ea" 10 | }, 11 | "menuBar": { 12 | "background": "#ebe0ff", 13 | "text": "#333333", 14 | "hover": "#d8c5ea", 15 | "border": "#c8b0e6" 16 | }, 17 | "toolbar": { 18 | "background": "#ebe0ff", 19 | "text": "#333333", 20 | "hover": "#d8c5ea", 21 | "border": "#c8b0e6" 22 | }, 23 | "panel": { 24 | "background": "#f8f2ff", 25 | "text": "#333333", 26 | "border": "#e0d5ea", 27 | "header": "#ebe0ff" 28 | }, 29 | "thumbnail": { 30 | "background": "#ffffff", 31 | "text": "#333333", 32 | "textSecondary": "#555555", 33 | "selected": "#d8c5ea", 34 | "selectedText": "#1a1a1a", 35 | "border": "#e0d5ea", 36 | "shadow": "#d0d0d0" 37 | }, 38 | "button": { 39 | "background": "#8a65af", 40 | "text": "#ffffff", 41 | "hover": "#9c77c1", 42 | "pressed": "#7954a1", 43 | "border": "#674382" 44 | }, 45 | "input": { 46 | "background": "#ffffff", 47 | "text": "#333333", 48 | "placeholder": "#888888", 49 | "border": "#c8b0e6", 50 | "focus": "#8a65af" 51 | }, 52 | "scrollBar": { 53 | "background": "#f5f0ff", 54 | "handle": "#c8b0e6", 55 | "handleHover": "#8a65af" 56 | }, 57 | "statusBar": { 58 | "background": "#ebe0ff", 59 | "text": "#333333", 60 | "border": "#c8b0e6" 61 | }, 62 | "dialog": { 63 | "background": "#f8f2ff", 64 | "text": "#333333", 65 | "border": "#e0d5ea", 66 | "header": "#ebe0ff" 67 | }, 68 | "progressBar": { 69 | "background": "#f5f0ff", 70 | "progress": "#8a65af", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#6e3baa", 75 | "hover": "#5a2e8a", 76 | "visited": "#4d257a" 77 | }, 78 | "error": { 79 | "text": "#cc0000", 80 | "background": "#ffeeee" 81 | }, 82 | "warning": { 83 | "text": "#cc6600", 84 | "background": "#fff5e6" 85 | }, 86 | "success": { 87 | "text": "#007700", 88 | "background": "#eeffee" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /designs/red.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Red Theme", 3 | "version": "1.0", 4 | "author": "STARNODES", 5 | "colors": { 6 | "window": { 7 | "background": "#fff0f0", 8 | "text": "#333333", 9 | "border": "#e6d0d0" 10 | }, 11 | "menuBar": { 12 | "background": "#ffe0e0", 13 | "text": "#333333", 14 | "hover": "#eac5c5", 15 | "border": "#e6b0b0" 16 | }, 17 | "toolbar": { 18 | "background": "#ffe0e0", 19 | "text": "#333333", 20 | "hover": "#eac5c5", 21 | "border": "#e6b0b0" 22 | }, 23 | "panel": { 24 | "background": "#fff5f5", 25 | "text": "#333333", 26 | "border": "#e6d0d0", 27 | "header": "#ffe0e0" 28 | }, 29 | "thumbnail": { 30 | "background": "#ffffff", 31 | "text": "#333333", 32 | "textSecondary": "#555555", 33 | "selected": "#eac5c5", 34 | "selectedText": "#1a1a1a", 35 | "border": "#e6d0d0", 36 | "shadow": "#d0d0d0" 37 | }, 38 | "button": { 39 | "background": "#af4b4b", 40 | "text": "#ffffff", 41 | "hover": "#c15d5d", 42 | "pressed": "#9d3a3a", 43 | "border": "#823030" 44 | }, 45 | "input": { 46 | "background": "#ffffff", 47 | "text": "#333333", 48 | "placeholder": "#888888", 49 | "border": "#e6b0b0", 50 | "focus": "#af4b4b" 51 | }, 52 | "scrollBar": { 53 | "background": "#fff0f0", 54 | "handle": "#e6b0b0", 55 | "handleHover": "#af4b4b" 56 | }, 57 | "statusBar": { 58 | "background": "#ffe0e0", 59 | "text": "#333333", 60 | "border": "#e6b0b0" 61 | }, 62 | "dialog": { 63 | "background": "#fff5f5", 64 | "text": "#333333", 65 | "border": "#e6d0d0", 66 | "header": "#ffe0e0" 67 | }, 68 | "progressBar": { 69 | "background": "#fff0f0", 70 | "progress": "#af4b4b", 71 | "text": "#ffffff" 72 | }, 73 | "link": { 74 | "text": "#aa0000", 75 | "hover": "#880000", 76 | "visited": "#660000" 77 | }, 78 | "error": { 79 | "text": "#cc0000", 80 | "background": "#ffeeee" 81 | }, 82 | "warning": { 83 | "text": "#cc6600", 84 | "background": "#fff5e6" 85 | }, 86 | "success": { 87 | "text": "#007700", 88 | "background": "#eeffee" 89 | }, 90 | "banner": { 91 | "background": "#141417", 92 | "text": "#ffffff", 93 | "linkText": "#78aeff", 94 | "linkHover": "#99ccff" 95 | } 96 | }, 97 | "fonts": { 98 | "main": { 99 | "family": "Segoe UI", 100 | "size": 10, 101 | "weight": "normal", 102 | "style": "normal" 103 | }, 104 | "header": { 105 | "family": "Segoe UI", 106 | "size": 12, 107 | "weight": "bold", 108 | "style": "normal" 109 | }, 110 | "title": { 111 | "family": "Segoe UI", 112 | "size": 14, 113 | "weight": "bold", 114 | "style": "normal" 115 | }, 116 | "description": { 117 | "family": "Segoe UI", 118 | "size": 9, 119 | "weight": "normal", 120 | "style": "normal" 121 | }, 122 | "button": { 123 | "family": "Segoe UI", 124 | "size": 10, 125 | "weight": "normal", 126 | "style": "normal" 127 | }, 128 | "input": { 129 | "family": "Segoe UI", 130 | "size": 10, 131 | "weight": "normal", 132 | "style": "normal" 133 | }, 134 | "statusBar": { 135 | "family": "Segoe UI", 136 | "size": 9, 137 | "weight": "normal", 138 | "style": "normal" 139 | } 140 | }, 141 | "paths": { 142 | "banner": "banner.png" 143 | } 144 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pillow 2 | pyqt6 3 | watchdog 4 | numpy 5 | sqlalchemy 6 | psutil 7 | requests 8 | -------------------------------------------------------------------------------- /setup_env.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Virtual environment setup script for StarImageBrowse 5 | Creates and configures a Python virtual environment with all required dependencies. 6 | """ 7 | 8 | import os 9 | import subprocess 10 | import sys 11 | import platform 12 | 13 | def setup_virtual_environment(): 14 | """Set up a virtual environment for the application.""" 15 | print("Starnodes Image Manager Environment Setup") 16 | print("=================================") 17 | 18 | # Create virtual environment if it doesn't exist 19 | venv_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "venv") 20 | if not os.path.exists(venv_path): 21 | print("Creating virtual environment...") 22 | try: 23 | subprocess.check_call([sys.executable, "-m", "venv", venv_path]) 24 | print("Virtual environment created successfully.") 25 | except subprocess.CalledProcessError as e: 26 | print(f"Error creating virtual environment: {e}") 27 | return False 28 | else: 29 | print("Virtual environment already exists.") 30 | 31 | # Determine the pip executable path 32 | if platform.system() == "Windows": # Windows 33 | pip_path = os.path.join(venv_path, "Scripts", "pip.exe") 34 | python_path = os.path.join(venv_path, "Scripts", "python.exe") 35 | else: # Unix/Linux/Mac 36 | pip_path = os.path.join(venv_path, "bin", "pip") 37 | python_path = os.path.join(venv_path, "bin", "python") 38 | 39 | # Upgrade pip 40 | print("Upgrading pip...") 41 | try: 42 | subprocess.check_call([pip_path, "install", "--upgrade", "pip"]) 43 | except subprocess.CalledProcessError as e: 44 | print(f"Error upgrading pip: {e}") 45 | 46 | # Install required packages 47 | print("Installing dependencies...") 48 | requirements = [ 49 | "pillow", # Image processing 50 | "pyqt6", # GUI framework 51 | "watchdog", # File system monitoring 52 | "numpy", # Numerical processing 53 | "sqlalchemy", # Database ORM 54 | "psutil", # System utilization monitoring 55 | "requests", # HTTP requests for Ollama API 56 | ] 57 | 58 | for package in requirements: 59 | print(f"Installing {package}...") 60 | try: 61 | subprocess.check_call([pip_path, "install", package]) 62 | except subprocess.CalledProcessError as e: 63 | print(f"Error installing {package}: {e}") 64 | 65 | # Create requirements.txt file 66 | print("Creating requirements.txt file...") 67 | with open("requirements.txt", "w") as f: 68 | for package in requirements: 69 | f.write(f"{package}\n") 70 | 71 | print("\nEnvironment setup complete!") 72 | print("\nTo activate the virtual environment:") 73 | if platform.system() == "Windows": 74 | print(f" {venv_path}\\Scripts\\activate") 75 | else: 76 | print(f" source {venv_path}/bin/activate") 77 | 78 | return True 79 | 80 | if __name__ == "__main__": 81 | setup_virtual_environment() 82 | -------------------------------------------------------------------------------- /src/ai/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/ai/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ai/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/ai/__pycache__/batch_processor.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ai/__pycache__/batch_processor.cpython-312.pyc -------------------------------------------------------------------------------- /src/ai/__pycache__/image_processor.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ai/__pycache__/image_processor.cpython-312.pyc -------------------------------------------------------------------------------- /src/ai/__pycache__/ollama_batch_processor.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ai/__pycache__/ollama_batch_processor.cpython-312.pyc -------------------------------------------------------------------------------- /src/cache/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Multi-level caching system for StarImageBrowse. 5 | Provides coordinated caching across different parts of the application. 6 | """ 7 | 8 | import logging 9 | 10 | logger = logging.getLogger("StarImageBrowse.cache") 11 | -------------------------------------------------------------------------------- /src/cache/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/cache/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/cache/__pycache__/cache_config.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/cache/__pycache__/cache_config.cpython-312.pyc -------------------------------------------------------------------------------- /src/cache/__pycache__/cache_manager.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/cache/__pycache__/cache_manager.cpython-312.pyc -------------------------------------------------------------------------------- /src/cache/__pycache__/image_cache.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/cache/__pycache__/image_cache.cpython-312.pyc -------------------------------------------------------------------------------- /src/cache/cache_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Cache configuration for StarImageBrowse. 5 | Defines default settings and provides utility functions for cache configuration. 6 | """ 7 | 8 | import os 9 | import logging 10 | import psutil 11 | 12 | logger = logging.getLogger("StarImageBrowse.cache.cache_config") 13 | 14 | # Default cache settings 15 | DEFAULT_CACHE_CONFIG = { 16 | # Memory caches 17 | "l1_size": 1000, # Number of items in L1 (fast) cache 18 | "l1_ttl": 300, # L1 cache TTL in seconds (5 minutes) 19 | "l2_size": 5000, # Number of items in L2 (medium) cache 20 | "l2_ttl": 1800, # L2 cache TTL in seconds (30 minutes) 21 | 22 | # Disk cache 23 | "disk_size": 10000, # Number of items in disk cache 24 | "disk_ttl": 86400, # Disk cache TTL in seconds (1 day) 25 | 26 | # Thumbnail-specific settings 27 | "thumbnail_memory_limit": 200, # Number of thumbnails to keep in memory 28 | 29 | # Image settings 30 | "image_memory_limit": 20, # Number of full images to keep in memory 31 | 32 | # Advanced settings 33 | "prefetch_enabled": True, # Whether to prefetch items 34 | "prefetch_distance": 10, # Number of items to prefetch 35 | "memory_pressure_limit": 75 # % of memory usage to trigger cache reduction 36 | } 37 | 38 | def get_optimal_cache_sizes(): 39 | """Calculate optimal cache sizes based on system resources. 40 | 41 | Returns: 42 | dict: Optimized cache settings 43 | """ 44 | try: 45 | # Get system memory information 46 | mem = psutil.virtual_memory() 47 | total_mem_gb = mem.total / (1024 ** 3) # Convert to GB 48 | 49 | # Scale cache sizes based on available memory 50 | config = DEFAULT_CACHE_CONFIG.copy() 51 | 52 | # For systems with less than 4GB RAM 53 | if total_mem_gb < 4: 54 | config["l1_size"] = 500 55 | config["l2_size"] = 2000 56 | config["thumbnail_memory_limit"] = 100 57 | config["image_memory_limit"] = 10 58 | 59 | # For systems with 4-8GB RAM (use defaults) 60 | 61 | # For systems with more than 8GB RAM 62 | elif total_mem_gb > 8: 63 | config["l1_size"] = 2000 64 | config["l2_size"] = 10000 65 | config["thumbnail_memory_limit"] = 500 66 | config["image_memory_limit"] = 50 67 | 68 | # For systems with more than 16GB RAM 69 | if total_mem_gb > 16: 70 | config["l1_size"] = 4000 71 | config["l2_size"] = 20000 72 | config["thumbnail_memory_limit"] = 1000 73 | config["image_memory_limit"] = 100 74 | 75 | logger.info(f"Optimized cache settings for system with {total_mem_gb:.1f}GB RAM") 76 | return config 77 | 78 | except Exception as e: 79 | logger.error(f"Error calculating optimal cache sizes: {e}") 80 | return DEFAULT_CACHE_CONFIG 81 | 82 | def apply_cache_config(config_manager): 83 | """Apply cache configuration to the config manager. 84 | 85 | Args: 86 | config_manager: Configuration manager instance 87 | 88 | Returns: 89 | bool: True if successful 90 | """ 91 | try: 92 | # Get optimal settings 93 | optimal_settings = get_optimal_cache_sizes() 94 | 95 | # Apply settings if not already set 96 | for key, value in optimal_settings.items(): 97 | if not config_manager.has("cache", key): 98 | config_manager.set("cache", key, value) 99 | 100 | logger.info("Applied cache configuration") 101 | return True 102 | 103 | except Exception as e: 104 | logger.error(f"Error applying cache configuration: {e}") 105 | return False 106 | 107 | def memory_pressure_check(): 108 | """Check if the system is under memory pressure. 109 | 110 | Returns: 111 | bool: True if under pressure 112 | """ 113 | try: 114 | mem = psutil.virtual_memory() 115 | percent_used = mem.percent 116 | 117 | # Consider memory pressure if usage is above the threshold 118 | threshold = DEFAULT_CACHE_CONFIG["memory_pressure_limit"] 119 | if percent_used > threshold: 120 | logger.warning(f"System under memory pressure: {percent_used}% used (threshold: {threshold}%)") 121 | return True 122 | 123 | return False 124 | 125 | except Exception as e: 126 | logger.error(f"Error checking memory pressure: {e}") 127 | return False 128 | -------------------------------------------------------------------------------- /src/config/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/config/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/config/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/config/__pycache__/config_manager.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/config/__pycache__/config_manager.cpython-312.pyc -------------------------------------------------------------------------------- /src/config/__pycache__/language_manager.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/config/__pycache__/language_manager.cpython-312.pyc -------------------------------------------------------------------------------- /src/config/__pycache__/theme_manager.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/config/__pycache__/theme_manager.cpython-312.pyc -------------------------------------------------------------------------------- /src/config/language_manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Language manager for StarImageBrowse 5 | Handles loading and applying translations. 6 | """ 7 | 8 | import os 9 | import json 10 | import logging 11 | from pathlib import Path 12 | 13 | logger = logging.getLogger("StarImageBrowse.config.language_manager") 14 | 15 | class LanguageManager: 16 | """Manages language translations for the application.""" 17 | 18 | def __init__(self, config_manager): 19 | """Initialize the language manager. 20 | 21 | Args: 22 | config_manager: Configuration manager instance 23 | """ 24 | self.config_manager = config_manager 25 | self.translations = {} 26 | self.current_language = None 27 | self.default_language = "en_GB" 28 | 29 | # Load the selected language 30 | self.load_language() 31 | 32 | def get_language_dir(self): 33 | """Get the path to the language directory.""" 34 | import sys 35 | 36 | # Check if we're running in portable mode (PyInstaller executable) 37 | is_portable = getattr(sys, 'frozen', False) 38 | 39 | if is_portable: 40 | # Use the executable directory for portable mode 41 | exe_dir = os.path.dirname(sys.executable) 42 | return os.path.join(exe_dir, "data", "lang") 43 | else: 44 | # For development mode, use the path relative to this file 45 | app_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 46 | return os.path.join(app_dir, "data", "lang") 47 | 48 | def get_available_languages(self): 49 | """Get a list of available languages. 50 | 51 | Returns: 52 | list: List of dictionaries with language information 53 | """ 54 | languages = [] 55 | lang_dir = self.get_language_dir() 56 | 57 | # Check if the directory exists 58 | if not os.path.exists(lang_dir): 59 | logger.warning(f"Language directory not found: {lang_dir}") 60 | # Add English as fallback 61 | languages.append({"name": "English (UK)", "code": "en_GB"}) 62 | return languages 63 | 64 | # Get all JSON files in the directory 65 | language_files = [f for f in os.listdir(lang_dir) if f.endswith(".json")] 66 | 67 | # If no language files found, add English as fallback 68 | if not language_files: 69 | logger.warning("No language files found in the language directory") 70 | languages.append({"name": "English (UK)", "code": "en_GB"}) 71 | return languages 72 | 73 | # Add each language to the list 74 | for lang_file in language_files: 75 | try: 76 | with open(os.path.join(lang_dir, lang_file), "r", encoding="utf-8") as f: 77 | lang_data = json.load(f) 78 | languages.append({ 79 | "name": lang_data.get("language", os.path.splitext(lang_file)[0]), 80 | "code": lang_data.get("code", os.path.splitext(lang_file)[0]), 81 | "file": lang_file 82 | }) 83 | except Exception as e: 84 | logger.error(f"Error loading language file {lang_file}: {e}") 85 | 86 | return languages 87 | 88 | def load_language(self, language_code=None): 89 | """Load a language file. 90 | 91 | Args: 92 | language_code (str, optional): Language code to load. If None, load from config. 93 | 94 | Returns: 95 | bool: True if language was loaded successfully, False otherwise 96 | """ 97 | # If no language code provided, get from config 98 | if language_code is None: 99 | language_code = self.config_manager.get("ui", "language", self.default_language) 100 | 101 | # If same as current language, do nothing 102 | if language_code == self.current_language and self.translations: 103 | return True 104 | 105 | # Get the language file path 106 | lang_dir = self.get_language_dir() 107 | lang_file = os.path.join(lang_dir, f"{language_code}.json") 108 | 109 | # Check if the file exists 110 | if not os.path.exists(lang_file): 111 | logger.warning(f"Language file not found: {lang_file}") 112 | # If not the default language, try to load the default 113 | if language_code != self.default_language: 114 | logger.info(f"Falling back to default language: {self.default_language}") 115 | return self.load_language(self.default_language) 116 | else: 117 | # If default language file not found, create empty translations 118 | self.translations = {} 119 | self.current_language = self.default_language 120 | return False 121 | 122 | # Load the language file 123 | try: 124 | with open(lang_file, "r", encoding="utf-8") as f: 125 | lang_data = json.load(f) 126 | self.translations = lang_data.get("translations", {}) 127 | self.current_language = language_code 128 | logger.info(f"Loaded language: {language_code}") 129 | return True 130 | except Exception as e: 131 | logger.error(f"Error loading language file {lang_file}: {e}") 132 | # If not the default language, try to load the default 133 | if language_code != self.default_language: 134 | logger.info(f"Falling back to default language: {self.default_language}") 135 | return self.load_language(self.default_language) 136 | else: 137 | # If default language file not found, create empty translations 138 | self.translations = {} 139 | self.current_language = self.default_language 140 | return False 141 | 142 | def get_translation(self, section, key, default=None): 143 | """Get a translation for a key. 144 | 145 | Args: 146 | section (str): Section in the translations 147 | key (str): Key in the section 148 | default (str, optional): Default value if translation not found 149 | 150 | Returns: 151 | str: Translated string or default value 152 | """ 153 | # If no translations loaded, return default 154 | if not self.translations: 155 | return default if default is not None else key 156 | 157 | # Get the section 158 | section_data = self.translations.get(section, {}) 159 | 160 | # Get the translation 161 | translation = section_data.get(key) 162 | 163 | # If translation not found, return default or key 164 | if translation is None: 165 | return default if default is not None else key 166 | 167 | return translation 168 | 169 | def translate(self, section, key, default=None): 170 | """Alias for get_translation.""" 171 | return self.get_translation(section, key, default) 172 | -------------------------------------------------------------------------------- /src/database/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/database/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_connection.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_connection.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_core.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_core.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_indexing.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_indexing.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_manager.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_manager.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_operations.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_operations.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_operations_extension.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_operations_extension.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_optimization_utils.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_optimization_utils.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_optimizer.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_optimizer.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_repair.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_repair.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_safe_operations.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_safe_operations.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_sharding.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_sharding.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_startup_repair.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_startup_repair.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_statement_cache.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_statement_cache.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/db_upgrade.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/db_upgrade.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/enhanced_search.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/enhanced_search.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/__pycache__/performance_optimizer.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/database/__pycache__/performance_optimizer.cpython-312.pyc -------------------------------------------------------------------------------- /src/database/db_indexing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Database indexing optimization for StarImageBrowse 5 | Enhances query performance through strategic index creation. 6 | """ 7 | 8 | import logging 9 | from .db_core import DatabaseConnection 10 | 11 | logger = logging.getLogger("StarImageBrowse.database.db_indexing") 12 | 13 | class DatabaseIndexOptimizer: 14 | """Optimizes database indexes for better query performance.""" 15 | 16 | def __init__(self, db_path): 17 | """Initialize the database index optimizer. 18 | 19 | Args: 20 | db_path (str): Path to the SQLite database file 21 | """ 22 | self.db_path = db_path 23 | 24 | def create_optimized_indexes(self): 25 | """Create optimized indexes for the database. 26 | 27 | Returns: 28 | bool: True if successful, False otherwise 29 | """ 30 | logger.info("Creating optimized indexes...") 31 | conn = DatabaseConnection(self.db_path) 32 | 33 | try: 34 | if not conn.connect(): 35 | raise Exception("Failed to connect to database") 36 | 37 | # Begin transaction 38 | if not conn.begin_transaction(): 39 | raise Exception("Failed to begin transaction") 40 | 41 | # Create composite indexes for common query patterns 42 | logger.info("Creating composite indexes for common query patterns...") 43 | operations = [ 44 | # Folder-based queries with date filtering 45 | "CREATE INDEX IF NOT EXISTS idx_images_folder_date ON images(folder_id, last_modified_date)", 46 | 47 | # Optimize search and sort operations 48 | "CREATE INDEX IF NOT EXISTS idx_images_search ON images(ai_description, user_description)", 49 | 50 | # Optimize filtering for images without descriptions 51 | "CREATE INDEX IF NOT EXISTS idx_images_no_desc ON images(folder_id) WHERE ai_description IS NULL OR ai_description = ''", 52 | 53 | # Optimize path-based lookups 54 | "CREATE INDEX IF NOT EXISTS idx_images_path ON images(full_path)", 55 | 56 | # Optimize thumbnail fetching 57 | "CREATE INDEX IF NOT EXISTS idx_images_thumbnail ON images(thumbnail_path)", 58 | 59 | # Optimize for the virtual FTS table if it exists 60 | "CREATE INDEX IF NOT EXISTS idx_images_image_id ON images(image_id)" 61 | ] 62 | 63 | # Execute each index creation statement 64 | for operation in operations: 65 | try: 66 | conn.execute(operation) 67 | logger.debug(f"Executed: {operation}") 68 | except Exception as e: 69 | logger.warning(f"Error executing {operation}: {e}") 70 | 71 | # Commit the transaction 72 | if not conn.commit(): 73 | raise Exception("Failed to commit transaction") 74 | 75 | logger.info("Optimized indexes created successfully") 76 | 77 | # Analyze the database to update statistics 78 | conn.execute("ANALYZE") 79 | 80 | return True 81 | 82 | except Exception as e: 83 | logger.error(f"Error creating optimized indexes: {e}") 84 | conn.rollback() 85 | return False 86 | 87 | finally: 88 | conn.disconnect() 89 | 90 | def check_index_usage(self): 91 | """Check the usage of indexes in the database. 92 | 93 | Returns: 94 | dict: Index usage statistics 95 | """ 96 | logger.info("Checking index usage...") 97 | conn = DatabaseConnection(self.db_path) 98 | 99 | try: 100 | if not conn.connect(): 101 | raise Exception("Failed to connect to database") 102 | 103 | # Get list of indexes 104 | cursor = conn.execute("SELECT name FROM sqlite_master WHERE type='index'") 105 | if not cursor: 106 | raise Exception("Failed to get index list") 107 | 108 | indexes = [row['name'] for row in cursor.fetchall()] 109 | 110 | # Get index usage statistics 111 | stats = {"indexes": []} 112 | for index in indexes: 113 | stats["indexes"].append({ 114 | "name": index, 115 | "table": index.split("_")[1] if "_" in index else "unknown" 116 | }) 117 | 118 | return stats 119 | 120 | except Exception as e: 121 | logger.error(f"Error checking index usage: {e}") 122 | return {"error": str(e)} 123 | 124 | finally: 125 | conn.disconnect() 126 | -------------------------------------------------------------------------------- /src/database/db_operations_extension.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Database operations extensions for StarImageBrowse 5 | Adds additional database capabilities for pagination and all images view 6 | """ 7 | 8 | import logging 9 | 10 | logger = logging.getLogger("StarImageBrowse.database.db_operations_extension") 11 | 12 | def add_get_all_images_method(db_ops): 13 | """Add get_all_images method to database operations 14 | 15 | Args: 16 | db_ops: The database operations instance to extend 17 | """ 18 | def get_all_images(self, limit=None, offset=0): 19 | """Get all images from the database with pagination 20 | 21 | Args: 22 | limit (int, optional): Maximum number of images to return 23 | offset (int, optional): Offset for pagination 24 | 25 | Returns: 26 | list: List of image dictionaries 27 | """ 28 | conn = self.db.get_connection() 29 | if not conn: 30 | logger.error("Failed to get database connection") 31 | return [] 32 | 33 | try: 34 | # Build query with pagination 35 | query = "SELECT * FROM images ORDER BY last_modified_date DESC" 36 | params = [] 37 | 38 | if limit is not None: 39 | query += " LIMIT ?" 40 | params.append(limit) 41 | 42 | if offset > 0: 43 | query += " OFFSET ?" 44 | params.append(offset) 45 | 46 | # Execute query 47 | cursor = conn.execute(query, params) 48 | images = [] 49 | 50 | for row in cursor: 51 | image = { 52 | 'image_id': row['image_id'], 53 | 'folder_id': row['folder_id'], 54 | 'filename': row['filename'], 55 | 'full_path': row['full_path'], 56 | 'thumbnail_path': row['thumbnail_path'], 57 | 'last_modified_date': row['last_modified_date'], 58 | 'user_description': row['user_description'], 59 | 'ai_description': row['ai_description'], 60 | 'width': row['width'], 61 | 'height': row['height'] 62 | } 63 | images.append(image) 64 | 65 | return images 66 | 67 | except Exception as e: 68 | logger.error(f"Error getting all images: {e}") 69 | return [] 70 | 71 | # Add method to the object 72 | import types 73 | db_ops.get_all_images = types.MethodType(get_all_images, db_ops) 74 | 75 | def add_get_all_images_count_method(db_ops): 76 | """Add get_all_images_count method to database operations 77 | 78 | Args: 79 | db_ops: The database operations instance to extend 80 | """ 81 | def get_all_images_count(self): 82 | """Get total count of all images in the database 83 | 84 | Returns: 85 | int: Total number of images 86 | """ 87 | conn = self.db.get_connection() 88 | if not conn: 89 | logger.error("Failed to get database connection") 90 | return 0 91 | 92 | try: 93 | cursor = conn.execute("SELECT COUNT(*) FROM images") 94 | count = cursor.fetchone()[0] 95 | return count 96 | 97 | except Exception as e: 98 | logger.error(f"Error counting all images: {e}") 99 | return 0 100 | 101 | # Add method to the object 102 | import types 103 | db_ops.get_all_images_count = types.MethodType(get_all_images_count, db_ops) 104 | 105 | def extend_db_operations(db_manager): 106 | """Extend database operations with additional methods 107 | 108 | Args: 109 | db_manager: The database manager instance to extend 110 | """ 111 | # Add methods to db_ops 112 | add_get_all_images_method(db_manager.db_ops) 113 | add_get_all_images_count_method(db_manager.db_ops) 114 | 115 | # Also make them available on the db_manager directly 116 | db_manager.get_all_images = db_manager.db_ops.get_all_images 117 | db_manager.get_all_images_count = db_manager.db_ops.get_all_images_count 118 | -------------------------------------------------------------------------------- /src/database/db_statement_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Prepared statement caching for StarImageBrowse 5 | Improves query performance by caching and reusing prepared statements. 6 | """ 7 | 8 | import logging 9 | import time 10 | import sqlite3 11 | from collections import OrderedDict 12 | 13 | logger = logging.getLogger("StarImageBrowse.database.db_statement_cache") 14 | 15 | class PreparedStatementCache: 16 | """Cache for SQLite prepared statements to improve query performance.""" 17 | 18 | def __init__(self, max_size=100, expiration_seconds=300): 19 | """Initialize the prepared statement cache. 20 | 21 | Args: 22 | max_size (int): Maximum number of statements to cache 23 | expiration_seconds (int): Number of seconds before a statement expires 24 | """ 25 | self.max_size = max_size 26 | self.expiration_seconds = expiration_seconds 27 | self.cache = OrderedDict() # {query_hash: (statement, last_used_time)} 28 | 29 | def _get_hash(self, query, params=None): 30 | """Get a hash for a query and its parameters. 31 | 32 | Args: 33 | query (str): SQL query 34 | params (tuple, optional): Query parameters 35 | 36 | Returns: 37 | int: Hash of the query and parameters 38 | """ 39 | # Include parameter types in the hash to ensure type safety 40 | param_types = None 41 | if params: 42 | if isinstance(params, (list, tuple)): 43 | param_types = tuple(type(p).__name__ for p in params) 44 | elif isinstance(params, dict): 45 | param_types = tuple((k, type(v).__name__) for k, v in params.items()) 46 | 47 | return hash((query, param_types)) 48 | 49 | def get(self, conn, query, params=None): 50 | """Get a prepared statement from the cache or prepare a new one. 51 | 52 | Args: 53 | conn (sqlite3.Connection): Database connection 54 | query (str): SQL query 55 | params (tuple, optional): Query parameters 56 | 57 | Returns: 58 | sqlite3.Cursor: Prepared statement 59 | """ 60 | query_hash = self._get_hash(query, params) 61 | 62 | # Check if the statement is in the cache 63 | if query_hash in self.cache: 64 | statement, last_used_time = self.cache[query_hash] 65 | 66 | # Check if the statement has expired 67 | if time.time() - last_used_time > self.expiration_seconds: 68 | # Remove from cache and prepare a new statement 69 | del self.cache[query_hash] 70 | logger.debug(f"Statement expired: {query}") 71 | else: 72 | # Move to the end of the OrderedDict (most recently used) 73 | self.cache.move_to_end(query_hash) 74 | self.cache[query_hash] = (statement, time.time()) 75 | logger.debug(f"Using cached statement: {query}") 76 | return statement 77 | 78 | # Prepare a new statement 79 | try: 80 | statement = conn.cursor() 81 | if params is None: 82 | statement.execute(query) 83 | else: 84 | statement.execute(query, params) 85 | 86 | # Add to cache 87 | self.cache[query_hash] = (statement, time.time()) 88 | 89 | # If the cache is too large, remove the least recently used statement 90 | if len(self.cache) > self.max_size: 91 | self.cache.popitem(last=False) 92 | 93 | logger.debug(f"Prepared new statement: {query}") 94 | return statement 95 | 96 | except sqlite3.Error as e: 97 | logger.error(f"Error preparing statement: {e}") 98 | raise 99 | 100 | def clear(self): 101 | """Clear the statement cache.""" 102 | self.cache.clear() 103 | logger.debug("Statement cache cleared") 104 | 105 | def remove(self, query, params=None): 106 | """Remove a specific statement from the cache. 107 | 108 | Args: 109 | query (str): SQL query 110 | params (tuple, optional): Query parameters 111 | """ 112 | query_hash = self._get_hash(query, params) 113 | if query_hash in self.cache: 114 | del self.cache[query_hash] 115 | logger.debug(f"Removed statement from cache: {query}") 116 | 117 | def get_stats(self): 118 | """Get statistics about the cache. 119 | 120 | Returns: 121 | dict: Cache statistics 122 | """ 123 | return { 124 | "size": len(self.cache), 125 | "max_size": self.max_size, 126 | "hit_ratio": 0, # Would need to track hits and misses over time 127 | "oldest_statement_age": max(time.time() - self.cache[k][1] for k in self.cache) if self.cache else 0 128 | } 129 | 130 | 131 | # Enhancement to DatabaseConnection for statement caching 132 | class CachedDatabaseConnection: 133 | """Enhances DatabaseConnection with prepared statement caching.""" 134 | 135 | def __init__(self, db_path, max_cache_size=100, expiration_seconds=300): 136 | """Initialize a cached database connection. 137 | 138 | Args: 139 | db_path (str): Path to the SQLite database file 140 | max_cache_size (int): Maximum number of statements to cache 141 | expiration_seconds (int): Number of seconds before a statement expires 142 | """ 143 | from .db_core import DatabaseConnection 144 | self.db_connection = DatabaseConnection(db_path) 145 | self.statement_cache = PreparedStatementCache(max_cache_size, expiration_seconds) 146 | 147 | def __getattr__(self, name): 148 | """Delegate attribute access to the underlying DatabaseConnection.""" 149 | return getattr(self.db_connection, name) 150 | 151 | def execute_cached(self, query, params=None): 152 | """Execute a SQL query using a cached prepared statement. 153 | 154 | Args: 155 | query (str): SQL query to execute 156 | params (tuple, optional): Parameters for the query 157 | 158 | Returns: 159 | cursor: Database cursor for fetching results 160 | """ 161 | if self.db_connection.conn is None: 162 | if not self.db_connection.connect(): 163 | return None 164 | 165 | try: 166 | return self.statement_cache.get(self.db_connection.conn, query, params) 167 | except sqlite3.Error as e: 168 | logger.error(f"Error executing cached statement: {e}") 169 | return None 170 | -------------------------------------------------------------------------------- /src/database/repair_thumbnail_paths.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sqlite3 3 | 4 | DB_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../data/database.sqlite3')) 5 | THUMBNAIL_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../data/thumbnails')) 6 | 7 | def repair_thumbnail_paths(db_path=DB_PATH, thumbnail_dir=THUMBNAIL_DIR): 8 | conn = sqlite3.connect(db_path) 9 | cursor = conn.cursor() 10 | # Get all images with NULL or empty thumbnail_path 11 | cursor.execute("SELECT image_id, file_hash FROM images WHERE thumbnail_path IS NULL OR thumbnail_path = ''") 12 | images = cursor.fetchall() 13 | print(f"Found {len(images)} images with missing thumbnail_path.") 14 | updated = 0 15 | for image_id, file_hash in images: 16 | # Try to find a matching thumbnail file by hash (or by image_id if that's the convention) 17 | thumb_filename = None 18 | # Try by hash 19 | if file_hash: 20 | for ext in ('.jpg', '.jpeg', '.png', '.webp'): 21 | candidate = f"{file_hash}{ext}" 22 | if os.path.isfile(os.path.join(thumbnail_dir, candidate)): 23 | thumb_filename = candidate 24 | break 25 | # Fallback: try by image_id 26 | if not thumb_filename: 27 | for ext in ('.jpg', '.jpeg', '.png', '.webp'): 28 | candidate = f"{image_id}{ext}" 29 | if os.path.isfile(os.path.join(thumbnail_dir, candidate)): 30 | thumb_filename = candidate 31 | break 32 | if thumb_filename: 33 | cursor.execute("UPDATE images SET thumbnail_path = ? WHERE image_id = ?", (thumb_filename, image_id)) 34 | updated += 1 35 | print(f"Updated image_id {image_id} with thumbnail {thumb_filename}") 36 | conn.commit() 37 | print(f"Updated {updated} images with thumbnail filenames.") 38 | conn.close() 39 | 40 | if __name__ == "__main__": 41 | repair_thumbnail_paths() 42 | -------------------------------------------------------------------------------- /src/image_processing/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/image_processing/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/image_processing/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/image_processing/__pycache__/format_optimizer.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/image_processing/__pycache__/format_optimizer.cpython-312.pyc -------------------------------------------------------------------------------- /src/image_processing/__pycache__/image_scanner.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/image_processing/__pycache__/image_scanner.cpython-312.pyc -------------------------------------------------------------------------------- /src/image_processing/__pycache__/optimized_thumbnail_generator.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/image_processing/__pycache__/optimized_thumbnail_generator.cpython-312.pyc -------------------------------------------------------------------------------- /src/image_processing/__pycache__/thumbnail_generator.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/image_processing/__pycache__/thumbnail_generator.cpython-312.pyc -------------------------------------------------------------------------------- /src/memory/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Memory management for StarImageBrowse 5 | Provides optimized memory management for image processing operations. 6 | """ 7 | 8 | import logging 9 | 10 | logger = logging.getLogger("StarImageBrowse.memory") 11 | -------------------------------------------------------------------------------- /src/memory/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/memory/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/memory/__pycache__/image_processor_integration.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/memory/__pycache__/image_processor_integration.cpython-312.pyc -------------------------------------------------------------------------------- /src/memory/__pycache__/image_processor_pool.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/memory/__pycache__/image_processor_pool.cpython-312.pyc -------------------------------------------------------------------------------- /src/memory/__pycache__/memory_pool.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/memory/__pycache__/memory_pool.cpython-312.pyc -------------------------------------------------------------------------------- /src/memory/__pycache__/memory_utils.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/memory/__pycache__/memory_utils.cpython-312.pyc -------------------------------------------------------------------------------- /src/memory/__pycache__/resource_manager.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/memory/__pycache__/resource_manager.cpython-312.pyc -------------------------------------------------------------------------------- /src/memory/image_processor_integration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Integration utilities for the memory pool image processor. 5 | Allows seamless integration with existing image processing code. 6 | """ 7 | 8 | import logging 9 | import os 10 | from typing import Dict, List, Tuple, Optional, Union, Any 11 | 12 | from src.config.config_manager import ConfigManager 13 | from src.memory.memory_utils import get_image_processor, is_memory_pool_enabled 14 | from src.image_processing.optimized_thumbnail_generator import OptimizedThumbnailGenerator 15 | 16 | logger = logging.getLogger("StarImageBrowse.memory.image_processor_integration") 17 | 18 | _thumbnail_generator = None 19 | 20 | def get_thumbnail_generator(thumbnail_dir: str, size: Tuple[int, int] = (200, 200), config_manager=None) -> OptimizedThumbnailGenerator: 21 | """Get an optimized thumbnail generator instance. 22 | 23 | Args: 24 | thumbnail_dir (str): Directory to store thumbnails 25 | size (tuple): Thumbnail size (width, height) 26 | config_manager: Configuration manager instance 27 | 28 | Returns: 29 | OptimizedThumbnailGenerator: Thumbnail generator instance 30 | """ 31 | global _thumbnail_generator 32 | 33 | if _thumbnail_generator is None: 34 | # Create a new thumbnail generator 35 | _thumbnail_generator = OptimizedThumbnailGenerator(thumbnail_dir, size, config_manager) 36 | logger.debug("Created optimized thumbnail generator") 37 | 38 | return _thumbnail_generator 39 | 40 | def process_image_for_ai(image_path: str) -> Optional[str]: 41 | """Process an image for AI analysis with memory pooling if enabled. 42 | 43 | This function loads and potentially resizes an image for AI analysis, 44 | using memory pooling for optimal performance if it's enabled. 45 | 46 | Args: 47 | image_path (str): Path to the image file 48 | 49 | Returns: 50 | str or None: Path to the processed image 51 | """ 52 | if not os.path.exists(image_path): 53 | logger.error(f"Image file not found: {image_path}") 54 | return None 55 | 56 | try: 57 | # Get image processor 58 | if is_memory_pool_enabled(): 59 | # Use memory pooled image processor 60 | image_processor = get_image_processor() 61 | 62 | # Load the image with memory pooling 63 | img, _ = image_processor.load_image(image_path) 64 | 65 | # Process for AI analysis (resize if needed, convert formats, etc.) 66 | max_dimension = 1024 # Maximum dimension for AI processing 67 | 68 | if img.width > max_dimension or img.height > max_dimension: 69 | # Resize while maintaining aspect ratio 70 | img = image_processor.process_image(img, [ 71 | { 72 | 'type': 'resize', 73 | 'width': max_dimension if img.width > img.height else int(max_dimension * img.width / img.height), 74 | 'height': max_dimension if img.height > img.width else int(max_dimension * img.height / img.width), 75 | 'method': 'lanczos' 76 | } 77 | ]) 78 | 79 | # Create a temporary file for the processed image 80 | temp_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "temp") 81 | os.makedirs(temp_dir, exist_ok=True) 82 | 83 | basename = os.path.basename(image_path) 84 | processed_path = os.path.join(temp_dir, f"ai_ready_{basename}") 85 | 86 | # Save the processed image 87 | image_processor.save_image(img, processed_path) 88 | 89 | return processed_path 90 | 91 | else: 92 | # Use standard PIL processing 93 | from PIL import Image 94 | 95 | with Image.open(image_path) as img: 96 | # Convert to RGB if needed 97 | if img.mode not in ('RGB', 'RGBA'): 98 | img = img.convert('RGB') 99 | 100 | # Resize if needed 101 | max_dimension = 1024 # Maximum dimension for AI processing 102 | 103 | if img.width > max_dimension or img.height > max_dimension: 104 | # Calculate new dimensions 105 | if img.width > img.height: 106 | new_width = max_dimension 107 | new_height = int(max_dimension * img.height / img.width) 108 | else: 109 | new_height = max_dimension 110 | new_width = int(max_dimension * img.width / img.height) 111 | 112 | # Resize the image 113 | img = img.resize((new_width, new_height), Image.Resampling.LANCZOS) 114 | 115 | # Create a temporary file for the processed image 116 | temp_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "temp") 117 | os.makedirs(temp_dir, exist_ok=True) 118 | 119 | basename = os.path.basename(image_path) 120 | processed_path = os.path.join(temp_dir, f"ai_ready_{basename}") 121 | 122 | # Save the processed image 123 | img.save(processed_path) 124 | 125 | return processed_path 126 | 127 | except Exception as e: 128 | logger.error(f"Error processing image for AI: {e}") 129 | return None 130 | 131 | def batch_process_images_for_ai(image_paths: List[str]) -> Dict[str, str]: 132 | """Process multiple images for AI analysis with memory pooling. 133 | 134 | Args: 135 | image_paths (list): List of paths to image files 136 | 137 | Returns: 138 | dict: Dictionary mapping original paths to processed paths 139 | """ 140 | results = {} 141 | 142 | if is_memory_pool_enabled(): 143 | # Use memory pooled image processor for batch processing 144 | image_processor = get_image_processor() 145 | 146 | # Prepare temporary directory 147 | temp_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "temp") 148 | os.makedirs(temp_dir, exist_ok=True) 149 | 150 | # Define the operations 151 | operations = [ 152 | { 153 | 'type': 'resize', 154 | 'width': 1024, 155 | 'height': 1024, 156 | 'method': 'lanczos' 157 | } 158 | ] 159 | 160 | # Process images in batch 161 | processed_images = image_processor.batch_process_images(image_paths, operations) 162 | 163 | # Save processed images 164 | for original_path, processed_img in processed_images.items(): 165 | if processed_img: 166 | basename = os.path.basename(original_path) 167 | processed_path = os.path.join(temp_dir, f"ai_ready_{basename}") 168 | 169 | # Save the processed image 170 | if image_processor.save_image(processed_img, processed_path): 171 | results[original_path] = processed_path 172 | else: 173 | results[original_path] = None 174 | else: 175 | results[original_path] = None 176 | else: 177 | # Process individually with standard PIL 178 | for path in image_paths: 179 | processed_path = process_image_for_ai(path) 180 | results[path] = processed_path 181 | 182 | return results 183 | -------------------------------------------------------------------------------- /src/processing/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Processing modules for StarImageBrowse 5 | Provides functionality for processing images and tasks in parallel. 6 | """ 7 | 8 | import logging 9 | 10 | logger = logging.getLogger("StarImageBrowse.processing") 11 | -------------------------------------------------------------------------------- /src/processing/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/processing/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/processing/__pycache__/batch_operations.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/processing/__pycache__/batch_operations.cpython-312.pyc -------------------------------------------------------------------------------- /src/processing/__pycache__/parallel_pipeline.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/processing/__pycache__/parallel_pipeline.cpython-312.pyc -------------------------------------------------------------------------------- /src/processing/__pycache__/task_manager.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/processing/__pycache__/task_manager.cpython-312.pyc -------------------------------------------------------------------------------- /src/scanner/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Scanner package for StarImageBrowse 5 | Contains modules for scanning and monitoring folders for images 6 | """ 7 | 8 | from .background_scanner import BackgroundScanner 9 | 10 | __all__ = ['BackgroundScanner'] 11 | -------------------------------------------------------------------------------- /src/scanner/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/scanner/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/scanner/__pycache__/background_scanner.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/scanner/__pycache__/background_scanner.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/ui/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/catalog_panel.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/catalog_panel.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/database_dimensions_update_dialog.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/database_dimensions_update_dialog.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/database_maintenance_dialog.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/database_maintenance_dialog.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/database_optimization_dialog.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/database_optimization_dialog.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/date_search_worker.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/date_search_worker.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/enhanced_search_panel.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/enhanced_search_panel.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/export_dialog.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/export_dialog.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/folder_panel.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/folder_panel.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/hover_preview_widget.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/hover_preview_widget.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/lazy_thumbnail_loader.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/lazy_thumbnail_loader.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/main_window.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/main_window.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/main_window_language.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/main_window_language.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/main_window_search_integration.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/main_window_search_integration.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/metadata_panel.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/metadata_panel.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/notification_manager.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/notification_manager.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/pagination_integration.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/pagination_integration.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/progress_dialog.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/progress_dialog.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/search_panel.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/search_panel.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/settings_dialog.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/settings_dialog.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/thumbnail_browser.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/thumbnail_browser.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/thumbnail_browser_factory.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/thumbnail_browser_factory.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/thumbnail_browser_pagination.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/thumbnail_browser_pagination.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/thumbnail_widget.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/thumbnail_widget.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/view_all_images.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/view_all_images.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/__pycache__/worker.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/ui/__pycache__/worker.cpython-312.pyc -------------------------------------------------------------------------------- /src/ui/date_search_worker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Date search worker for StarImageBrowse 5 | Provides background processing for searching images by date range. 6 | """ 7 | 8 | import os 9 | import logging 10 | from datetime import datetime 11 | from PIL import Image, ExifTags 12 | from PyQt6.QtCore import QObject, pyqtSignal, QRunnable 13 | 14 | logger = logging.getLogger("StarImageBrowse.ui.date_search_worker") 15 | 16 | class DateSearchSignals(QObject): 17 | """Signals for date search tasks.""" 18 | finished = pyqtSignal(list) # Results list 19 | error = pyqtSignal(str) # Error message 20 | progress = pyqtSignal(int, int, str) # current, total, message 21 | 22 | class DateSearchWorker(QRunnable): 23 | """Worker for searching images by date range in a background thread.""" 24 | 25 | def __init__(self, db_manager, from_date, to_date): 26 | """Initialize the date search worker. 27 | 28 | Args: 29 | db_manager: Database manager instance 30 | from_date (datetime): Start date for the search 31 | to_date (datetime): End date for the search 32 | """ 33 | super().__init__() 34 | self.db_manager = db_manager 35 | self.from_date = from_date 36 | self.to_date = to_date 37 | self.signals = DateSearchSignals() 38 | # Initialize cancelled as False - this will only be set to True by explicit cancellation 39 | self.cancelled = False 40 | 41 | # Log worker initialization 42 | logger.debug(f"Date search worker initialized: {from_date.date()} to {to_date.date()}") 43 | 44 | def cancel(self): 45 | """Cancel the search operation.""" 46 | logger.debug("User explicitly canceled the date search operation") 47 | self.cancelled = True 48 | 49 | def run(self): 50 | """Run the date search operation using database-stored date information.""" 51 | try: 52 | # Don't check for cancellation at the beginning since we just started 53 | # Start with no cancellation assumption 54 | 55 | # Initial progress update 56 | self.signals.progress.emit(0, 100, "Starting date range search...") 57 | 58 | logger.info(f"Searching for images between {self.from_date} and {self.to_date}") 59 | self.signals.progress.emit(10, 100, "Querying database for images in date range...") 60 | 61 | # Convert date objects to strings for SQLite 62 | from_date_str = self.from_date.strftime('%Y-%m-%d 00:00:00') 63 | to_date_str = self.to_date.strftime('%Y-%m-%d 23:59:59') 64 | 65 | # Only check for cancellation if the user has explicitly clicked the cancel button 66 | if self.cancelled: 67 | logger.info("Date search cancelled by user before database query") 68 | self.signals.error.emit("Search cancelled by user") 69 | return 70 | 71 | # Use the database to find images directly - much faster than file processing 72 | results = self.db_manager.get_images_by_date_range(from_date_str, to_date_str, limit=1000000) 73 | 74 | # Only check for cancellation if the user has explicitly clicked the cancel button 75 | if self.cancelled: 76 | logger.info("Date search cancelled by user after database query") 77 | self.signals.error.emit("Search cancelled by user") 78 | return 79 | 80 | # Completely avoid any progress update between initial and completion 81 | # Skip directly to completion to avoid any false cancellation triggers 82 | msg = f"Found {len(results)} images in date range" 83 | logger.debug(f"Date search completed: {msg}") 84 | 85 | # Only check for explicit user cancellation 86 | if self.cancelled: 87 | logger.info("Date search explicitly cancelled by user") 88 | self.signals.error.emit("Search cancelled by user") 89 | return 90 | 91 | # We found results, emit them directly - no further progress updates 92 | # This avoids any potential signal issues that might be triggering false cancellations 93 | self.signals.finished.emit(results) 94 | 95 | except Exception as e: 96 | logger.error(f"Error in date search: {e}") 97 | # Only emit error if we didn't explicitly cancel 98 | if not self.cancelled: 99 | self.signals.error.emit(str(e)) 100 | -------------------------------------------------------------------------------- /src/ui/hover_preview_widget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Hover preview widget for StarImageBrowse 5 | Shows a large preview of images when hovering over thumbnails 6 | """ 7 | 8 | import os 9 | import logging 10 | import re 11 | from PyQt6.QtWidgets import QLabel, QApplication, QFrame 12 | from PyQt6.QtGui import QPixmap, QImage 13 | from PyQt6.QtCore import Qt 14 | 15 | logger = logging.getLogger("StarImageBrowse.ui.hover_preview_widget") 16 | 17 | class HoverPreviewWidget(QFrame): 18 | """Widget for displaying a larger preview on hover.""" 19 | 20 | def __init__(self, parent=None, language_manager=None): 21 | """Initialize the hover preview widget. 22 | 23 | Args: 24 | parent (QWidget, optional): Parent widget 25 | language_manager: Language manager instance for translations 26 | """ 27 | super().__init__(parent) 28 | 29 | # Store language manager 30 | self.language_manager = language_manager 31 | 32 | # Set up UI 33 | self.setWindowFlags(Qt.WindowType.ToolTip | Qt.WindowType.FramelessWindowHint) 34 | self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) 35 | # Use stylesheet for border, not QFrame border 36 | self.setObjectName("hoverPreviewWidget") 37 | 38 | # Create label for preview 39 | self.preview_label = QLabel(self) 40 | self.preview_label.setAlignment(Qt.AlignmentFlag.AlignCenter) 41 | self.preview_label.setContentsMargins(0, 0, 0, 0) 42 | 43 | # Set maximum preview size (actual size will be set dynamically) 44 | self.max_preview_size = 700 45 | self.border_width = 4 # Border thickness in px 46 | # Initial minimal size; will be updated on image load 47 | self.setFixedSize(2 * self.border_width + 1, 2 * self.border_width + 1) 48 | self.preview_label.setFixedSize(1, 1) 49 | self.preview_label.move(self.border_width, self.border_width) 50 | 51 | # Initial invisible state 52 | self.hide() 53 | 54 | # Update theme colors 55 | self.update_theme() 56 | 57 | def update_theme(self): 58 | """Update widget with current theme colors.""" 59 | app = QApplication.instance() 60 | main_window = None 61 | 62 | # Default border color - use the selected background color of thumbnails 63 | border_color = "#6c06a7" # Purple default 64 | 65 | # Try to get theme colors from the main window's theme manager 66 | if app: 67 | for widget in app.topLevelWidgets(): 68 | if hasattr(widget, 'theme_manager'): 69 | main_window = widget 70 | theme = widget.theme_manager.get_current_theme() 71 | if theme and 'colors' in theme: 72 | if 'thumbnail' in theme['colors']: 73 | theme_colors = theme['colors']['thumbnail'] 74 | # Use the selected background color for the border 75 | border_color = theme_colors.get('selected', border_color) 76 | break 77 | 78 | # Validate color format 79 | if not re.match(r"^#[0-9a-fA-F]{6}$", border_color): 80 | border_color = "#6c06a7" # Default fallback if invalid 81 | 82 | # Set frame style directly on the widget (no selector) for maximum reliability 83 | self.setStyleSheet(f"background-color: white; border: 4px solid {border_color}; border-radius: 0px;") 84 | 85 | def set_language_manager(self, language_manager): 86 | """Set the language manager for translations. 87 | 88 | Args: 89 | language_manager: Language manager instance 90 | """ 91 | self.language_manager = language_manager 92 | 93 | def get_translation(self, key, default=None): 94 | """Get a translation for a key. 95 | 96 | Args: 97 | key (str): Key in the hover_preview section 98 | default (str, optional): Default value if translation not found 99 | 100 | Returns: 101 | str: Translated string or default value 102 | """ 103 | if hasattr(self, 'language_manager') and self.language_manager: 104 | return self.language_manager.translate('hover_preview', key, default) 105 | return default 106 | 107 | def load_preview(self, image_path, max_size=None): 108 | """Load an image preview at the specified size. 109 | 110 | Args: 111 | image_path (str): Path to the original image file 112 | max_size (int, optional): Maximum size for the preview 113 | """ 114 | if max_size is not None: 115 | self.max_preview_size = max_size 116 | # No fixed widget/label size here; will set after loading image 117 | 118 | try: 119 | if not os.path.exists(image_path): 120 | logger.warning(f"Image not found for preview: {image_path}") 121 | self.preview_label.setText(self.get_translation('image_not_found', 'Image not found')) 122 | return False 123 | 124 | # Load image 125 | pixmap = QPixmap(image_path) 126 | if pixmap.isNull(): 127 | logger.warning(f"Failed to load image for preview: {image_path}") 128 | self.preview_label.setText(self.get_translation('failed_to_load', 'Failed to load image')) 129 | return False 130 | 131 | # Scale maintaining aspect ratio 132 | scaled_pixmap = pixmap.scaled( 133 | self.max_preview_size, 134 | self.max_preview_size, 135 | Qt.AspectRatioMode.KeepAspectRatio, 136 | Qt.TransformationMode.SmoothTransformation 137 | ) 138 | # Get scaled image size 139 | img_width = scaled_pixmap.width() 140 | img_height = scaled_pixmap.height() 141 | # Set label and widget size to fit image plus border 142 | self.preview_label.setFixedSize(img_width, img_height) 143 | self.setFixedSize(img_width + 2 * self.border_width, img_height + 2 * self.border_width) 144 | self.preview_label.move(self.border_width, self.border_width) 145 | # Set the preview 146 | self.preview_label.setPixmap(scaled_pixmap) 147 | return True 148 | 149 | except Exception as e: 150 | logger.error(f"Error loading preview for {image_path}: {str(e)}") 151 | self.preview_label.setText(self.get_translation('error_loading', 'Error loading preview')) 152 | return False 153 | 154 | def show_at(self, global_pos, desktop_rect=None): 155 | """Show the preview at the specified position, adjusted to fit on screen. 156 | 157 | Args: 158 | global_pos (QPoint): Global position to show the preview at 159 | desktop_rect (QRect, optional): Available desktop area 160 | """ 161 | # Get desktop rect if not provided 162 | if not desktop_rect: 163 | desktop_rect = QApplication.primaryScreen().availableGeometry() 164 | 165 | # Calculate position to ensure preview stays on screen 166 | preview_width = self.width() 167 | preview_height = self.height() 168 | 169 | # Try to show preview to the right of the cursor 170 | pos_x = global_pos.x() + 20 171 | pos_y = global_pos.y() - preview_height // 2 172 | 173 | # Adjust if would go off right edge 174 | if pos_x + preview_width > desktop_rect.right(): 175 | # Show to the left of the cursor instead 176 | pos_x = global_pos.x() - 20 - preview_width 177 | 178 | # Ensure top edge is on screen 179 | if pos_y < desktop_rect.top(): 180 | pos_y = desktop_rect.top() + 10 181 | 182 | # Ensure bottom edge is on screen 183 | if pos_y + preview_height > desktop_rect.bottom(): 184 | pos_y = desktop_rect.bottom() - preview_height - 10 185 | 186 | # Move to calculated position and show 187 | self.move(pos_x, pos_y) 188 | self.show() 189 | -------------------------------------------------------------------------------- /src/ui/launch_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Launch controller for StarImageBrowse 5 | Handles the startup sequence and main window creation. 6 | """ 7 | 8 | import os 9 | import logging 10 | import sys 11 | from PyQt6.QtWidgets import QApplication, QMessageBox 12 | from PyQt6.QtCore import QTimer 13 | 14 | from src.config.config_manager import ConfigManager 15 | from src.database.db_manager import DatabaseManager 16 | from src.ui.main_window import MainWindow 17 | 18 | logger = logging.getLogger("StarImageBrowse.ui.launch_controller") 19 | 20 | class LaunchController: 21 | """Controls the startup sequence for StarImageBrowse.""" 22 | 23 | def __init__(self): 24 | """Initialize the launch controller.""" 25 | self.config_manager = ConfigManager() 26 | self.db_manager = None 27 | self.main_window = None 28 | self.app = QApplication.instance() 29 | 30 | # Get database path 31 | db_path = self.config_manager.get("database", "path") 32 | if not os.path.isabs(db_path): 33 | app_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 34 | db_path = os.path.join(app_dir, db_path) 35 | 36 | # Ensure directory exists 37 | os.makedirs(os.path.dirname(db_path), exist_ok=True) 38 | 39 | # Initialize database manager 40 | self.db_manager = DatabaseManager(db_path) 41 | self.db_manager.initialize_database() 42 | 43 | def start(self): 44 | """Start the application.""" 45 | logger.info("Starting application launch sequence") 46 | 47 | # Check if this is the first run 48 | is_first_run = self.config_manager.get("app", "first_run") 49 | 50 | # If this is the first run, initialize with default settings 51 | if is_first_run: 52 | logger.info("First run - initializing with default settings") 53 | self.initialize_default_settings() 54 | 55 | # Show main window directly 56 | logger.info("Showing main window") 57 | QTimer.singleShot(0, self.show_main_window) 58 | 59 | def initialize_default_settings(self): 60 | """Initialize the application with default settings.""" 61 | try: 62 | logger.info("Initializing default settings") 63 | 64 | # Default Ollama settings 65 | self.config_manager.set("ollama", "server_url", "http://localhost:11434") 66 | self.config_manager.set("ollama", "model", "llava") # Default to llava if available 67 | 68 | # AI settings 69 | self.config_manager.set("ai", "process_all_images", False) 70 | 71 | # UI settings 72 | self.config_manager.set("ui", "theme", "system") 73 | self.config_manager.set("thumbnails", "size", 200) # Medium size 74 | self.config_manager.set("ui", "show_descriptions", True) 75 | self.config_manager.set("monitor", "watch_folders", True) 76 | 77 | # Set first run flag to false 78 | self.config_manager.set("app", "first_run", False) 79 | 80 | # Save to file 81 | self.config_manager.save() 82 | logger.info("Default settings saved successfully") 83 | 84 | except Exception as e: 85 | logger.error(f"Error initializing default settings: {e}") 86 | 87 | def show_main_window(self): 88 | """Show the main window.""" 89 | try: 90 | if not self.main_window: 91 | logger.info("Creating main window") 92 | self.main_window = MainWindow(self.db_manager) 93 | 94 | # Store reference in app to prevent garbage collection 95 | self.app._main_window = self.main_window 96 | 97 | logger.info("Showing main window") 98 | self.main_window.show() 99 | 100 | # Process events to ensure window is shown 101 | QApplication.processEvents() 102 | 103 | # Ensure window is visible and active 104 | self.main_window.setWindowState( 105 | (self.main_window.windowState() & ~Qt.WindowState.WindowMinimized) | 106 | Qt.WindowState.WindowActive 107 | ) 108 | self.main_window.activateWindow() 109 | self.main_window.raise_() 110 | 111 | # Process events again to ensure changes are applied 112 | QApplication.processEvents() 113 | 114 | logger.info("Main window shown successfully") 115 | 116 | except Exception as e: 117 | logger.error(f"Error creating/showing main window: {e}") 118 | QMessageBox.critical( 119 | None, 120 | "Error", 121 | f"Failed to create main window: {e}\n\nThe application will now exit." 122 | ) 123 | QApplication.instance().quit() 124 | 125 | 126 | def launch_application(): 127 | """Launch the application. 128 | 129 | Returns: 130 | int: Exit code 131 | """ 132 | try: 133 | # Create application 134 | app = QApplication.instance() 135 | if app is None: 136 | app = QApplication(sys.argv) 137 | 138 | app.setApplicationName("StarImageBrowse") 139 | app.setOrganizationName("StarKeeper") 140 | app.setOrganizationDomain("starkeeper.example.com") 141 | 142 | # Keep the application running 143 | app.setQuitOnLastWindowClosed(False) 144 | 145 | # Create launch controller 146 | controller = LaunchController() 147 | 148 | # Store reference to prevent garbage collection 149 | app._controller = controller 150 | 151 | # Start launch sequence 152 | controller.start() 153 | 154 | logger.info("Starting application event loop") 155 | # Start event loop 156 | return app.exec() 157 | 158 | except Exception as e: 159 | logger.error(f"Error in launch_application: {e}") 160 | return 1 161 | 162 | 163 | if __name__ == "__main__": 164 | sys.exit(launch_application()) 165 | -------------------------------------------------------------------------------- /src/ui/main_window_all_images.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | All Images View functionality for StarImageBrowse 5 | Direct implementation of view_all_images method for the MainWindow class. 6 | This is patched directly into the MainWindow class during initialization. 7 | """ 8 | 9 | import logging 10 | from PyQt6.QtWidgets import QApplication, QMessageBox 11 | 12 | logger = logging.getLogger("StarImageBrowse.ui.main_window_all_images") 13 | 14 | def view_all_images(self): 15 | """Display all images in the database with pagination""" 16 | try: 17 | # Update the thumbnail browser UI to show we're loading 18 | # This ensures UI responsiveness during loading 19 | self.status_bar.showMessage("Loading all images...") 20 | QApplication.processEvents() # Process events to update the UI 21 | 22 | # Initialize database extensions if needed 23 | if not hasattr(self.db_manager, 'get_all_images_count'): 24 | logger.info("Initializing database extensions for view_all_images") 25 | from src.database.db_operations_extension import extend_db_operations 26 | extend_db_operations(self.db_manager) 27 | 28 | # Get total count of images for pagination 29 | total_count = self.db_manager.get_all_images_count() 30 | 31 | # Clear any previous search or folder selection 32 | if hasattr(self.thumbnail_browser, 'current_folder_id'): 33 | self.thumbnail_browser.current_folder_id = None 34 | if hasattr(self.thumbnail_browser, 'current_catalog_id'): 35 | self.thumbnail_browser.current_catalog_id = None 36 | if hasattr(self.thumbnail_browser, 'current_search_query'): 37 | self.thumbnail_browser.current_search_query = None 38 | if hasattr(self.thumbnail_browser, 'last_search_params'): 39 | self.thumbnail_browser.last_search_params = None 40 | 41 | # Set flag to indicate this is the "All Images" view 42 | self.thumbnail_browser.all_images_view = True 43 | 44 | # Reset pagination to first page 45 | self.thumbnail_browser.current_page = 0 46 | 47 | # Get the page size from pagination if available 48 | page_size = 200 # Default 49 | if hasattr(self.thumbnail_browser, 'page_size'): 50 | page_size = self.thumbnail_browser.page_size 51 | 52 | # Enable pagination 53 | self.thumbnail_browser.is_paginated = True 54 | self.thumbnail_browser.total_items = total_count 55 | self.thumbnail_browser.total_pages = (total_count + page_size - 1) // page_size 56 | 57 | # Get first page of images 58 | images = self.db_manager.get_all_images(limit=page_size, offset=0) 59 | 60 | # Clear thumbnails and add the first page 61 | self.thumbnail_browser.clear_thumbnails() 62 | self.thumbnail_browser.add_thumbnails(images) 63 | 64 | # Update header with count 65 | if hasattr(self.thumbnail_browser, 'header_label'): 66 | self.thumbnail_browser.header_label.setText(f"All Images ({total_count} total)") 67 | 68 | # Update status 69 | page_count = (total_count + page_size - 1) // page_size 70 | if page_count > 1: 71 | self.status_bar.showMessage( 72 | f"Showing page 1 of {page_count} ({len(images)} of {total_count} images)" 73 | ) 74 | else: 75 | self.status_bar.showMessage(f"Showing all {len(images)} images") 76 | 77 | # Update pagination controls if available 78 | if hasattr(self.thumbnail_browser, 'thumbnail_pagination') and \ 79 | hasattr(self.thumbnail_browser.thumbnail_pagination, 'update_pagination_controls'): 80 | self.thumbnail_browser.thumbnail_pagination.update_pagination_controls() 81 | 82 | # Update window title 83 | if hasattr(self, 'setWindowTitle'): 84 | self.setWindowTitle("STARNODES Image Manager - All Images") 85 | 86 | except Exception as e: 87 | logger.error(f"Error viewing all images: {e}") 88 | QMessageBox.critical( 89 | self, 90 | "Error", 91 | f"An error occurred while loading all images:\n{str(e)}", 92 | QMessageBox.StandardButton.Ok 93 | ) 94 | -------------------------------------------------------------------------------- /src/ui/pagination_integration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Pagination integration for StarImageBrowse 5 | Integrates pagination into the main application 6 | """ 7 | 8 | import logging 9 | from .thumbnail_browser_pagination import enable_pagination_for_browser 10 | 11 | logger = logging.getLogger("StarImageBrowse.ui.pagination_integration") 12 | 13 | def integrate_pagination(main_window): 14 | """ 15 | Integrate pagination features into the main window 16 | 17 | Args: 18 | main_window: The main application window 19 | """ 20 | # Check if we have a thumbnail browser 21 | if not hasattr(main_window, 'thumbnail_browser'): 22 | logger.warning("No thumbnail browser found to add pagination to") 23 | return False 24 | 25 | try: 26 | # Enable pagination for the thumbnail browser 27 | pagination = enable_pagination_for_browser(main_window.thumbnail_browser) 28 | 29 | # Store the pagination controller in the main window for future reference 30 | main_window.thumbnail_pagination = pagination 31 | 32 | # Check if enhanced_search attribute exists before attempting to use it 33 | has_enhanced_search = hasattr(main_window.db_manager, 'enhanced_search') and main_window.db_manager.enhanced_search is not None 34 | 35 | # Add a DB manager method to count search results if needed and enhanced_search exists 36 | if has_enhanced_search and not hasattr(main_window.db_manager.enhanced_search, 'count_results'): 37 | def count_results(self, params, folder_id=None, catalog_id=None): 38 | """Count results without fetching all records""" 39 | # Build a similar query as search but with COUNT() 40 | conn = self.db_ops.db.get_connection() 41 | if not conn: 42 | return 0 43 | 44 | try: 45 | # Build query parts similar to search method 46 | query_parts = [] 47 | query_params = [] 48 | 49 | # Base query depends on scope 50 | if folder_id is not None: 51 | base_query = "SELECT COUNT(*) FROM images WHERE folder_id = ?" 52 | query_params.append(folder_id) 53 | elif catalog_id is not None: 54 | base_query = """ 55 | SELECT COUNT(i.image_id) FROM images i 56 | JOIN image_catalog_mapping m ON i.image_id = m.image_id 57 | WHERE m.catalog_id = ? 58 | """ 59 | query_params.append(catalog_id) 60 | else: 61 | base_query = "SELECT COUNT(*) FROM images WHERE 1=1" 62 | 63 | # Add text search 64 | if params.get('text_enabled', False) and params.get('text_query'): 65 | query_text = params['text_query'].strip() 66 | if query_text: 67 | like_pattern = f"%{query_text}%" 68 | query_parts.append("(ai_description LIKE ? OR user_description LIKE ? OR filename LIKE ?)") 69 | query_params.extend([like_pattern, like_pattern, like_pattern]) 70 | 71 | # Add date range 72 | if params.get('date_enabled', False): 73 | date_from = params.get('date_from') 74 | date_to = params.get('date_to') 75 | 76 | if date_from and date_to: 77 | query_parts.append("last_modified_date BETWEEN ? AND ?") 78 | query_params.append(date_from) 79 | query_params.append(date_to) 80 | 81 | # Add dimensions 82 | if params.get('dimensions_enabled', False): 83 | # Add a condition to filter only images that have dimensions stored 84 | query_parts.append("(width IS NOT NULL AND height IS NOT NULL)") 85 | 86 | # Apply dimension filters 87 | min_width = params.get('min_width') 88 | max_width = params.get('max_width') 89 | min_height = params.get('min_height') 90 | max_height = params.get('max_height') 91 | 92 | if min_width is not None and min_width > 0: 93 | query_parts.append("width >= ?") 94 | query_params.append(min_width) 95 | 96 | if max_width is not None and max_width < 10000: 97 | query_parts.append("width <= ?") 98 | query_params.append(max_width) 99 | 100 | if min_height is not None and min_height > 0: 101 | query_parts.append("height >= ?") 102 | query_params.append(min_height) 103 | 104 | if max_height is not None and max_height < 10000: 105 | query_parts.append("height <= ?") 106 | query_params.append(max_height) 107 | 108 | # Combine all parts 109 | final_query = base_query 110 | if query_parts: 111 | final_query += " AND " + " AND ".join(query_parts) 112 | 113 | # Execute count query 114 | cursor = conn.execute(final_query, tuple(query_params)) 115 | count = cursor.fetchone()[0] 116 | 117 | return count 118 | 119 | except Exception as e: 120 | logger.error(f"Error counting search results: {e}") 121 | return 0 122 | 123 | # Add the count_results method to EnhancedSearch 124 | import types 125 | # Only add the method if enhanced_search exists 126 | if has_enhanced_search: 127 | main_window.db_manager.enhanced_search.count_results = types.MethodType( 128 | count_results, main_window.db_manager.enhanced_search 129 | ) 130 | 131 | # Add method to get folder image count if it doesn't exist 132 | if not hasattr(main_window.db_manager, 'get_image_count_for_folder'): 133 | def get_image_count_for_folder(self, folder_id): 134 | """Get the number of images in a folder""" 135 | conn = self.db.get_connection() 136 | if not conn: 137 | return 0 138 | 139 | try: 140 | cursor = conn.execute( 141 | "SELECT COUNT(*) FROM images WHERE folder_id = ?", 142 | (folder_id,) 143 | ) 144 | count = cursor.fetchone()[0] 145 | return count 146 | except Exception as e: 147 | logger.error(f"Error counting folder images: {e}") 148 | return 0 149 | 150 | # Add the get_image_count_for_folder method to DB manager 151 | import types 152 | main_window.db_manager.get_image_count_for_folder = types.MethodType( 153 | get_image_count_for_folder, main_window.db_manager 154 | ) 155 | 156 | logger.info("Pagination successfully integrated") 157 | return True 158 | 159 | except Exception as e: 160 | logger.error(f"Error integrating pagination: {e}") 161 | return False 162 | -------------------------------------------------------------------------------- /src/ui/search_panel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Search panel UI component for StarImageBrowse 5 | Provides search functionality for finding images by description and date range. 6 | """ 7 | 8 | import logging 9 | from datetime import datetime 10 | from PyQt6.QtWidgets import ( 11 | QWidget, QVBoxLayout, QHBoxLayout, QLabel, 12 | QLineEdit, QPushButton, QCompleter, QDateEdit, 13 | QGroupBox, QCheckBox, QRadioButton, QButtonGroup 14 | ) 15 | from PyQt6.QtCore import Qt, pyqtSignal, QStringListModel, QDate 16 | 17 | logger = logging.getLogger("StarImageBrowse.ui.search_panel") 18 | 19 | class SearchPanel(QWidget): 20 | """Panel for searching images by description and date range.""" 21 | 22 | search_requested = pyqtSignal(str, bool) # Signal emitted when search is requested (query, search_all_folders) 23 | date_search_requested = pyqtSignal(datetime, datetime) # Signal emitted when date search is requested 24 | 25 | def __init__(self, parent=None): 26 | """Initialize the search panel. 27 | 28 | Args: 29 | parent (QWidget, optional): Parent widget 30 | """ 31 | super().__init__(parent) 32 | 33 | self.setup_ui() 34 | 35 | def setup_ui(self): 36 | """Set up the search panel UI.""" 37 | # Main layout 38 | layout = QVBoxLayout(self) 39 | layout.setContentsMargins(0, 0, 0, 0) 40 | 41 | # Header 42 | header_label = QLabel("Search Images") 43 | header_label.setStyleSheet("font-weight: bold;") 44 | layout.addWidget(header_label) 45 | 46 | # Search input layout 47 | search_layout = QHBoxLayout() 48 | 49 | # Search input field 50 | self.search_input = QLineEdit() 51 | self.search_input.setPlaceholderText("Search by description...") 52 | self.search_input.returnPressed.connect(self.on_search) 53 | search_layout.addWidget(self.search_input) 54 | 55 | # Search button 56 | self.search_button = QPushButton("Search") 57 | self.search_button.clicked.connect(self.on_search) 58 | search_layout.addWidget(self.search_button) 59 | 60 | layout.addLayout(search_layout) 61 | 62 | # Search scope options 63 | scope_layout = QHBoxLayout() 64 | scope_layout.setContentsMargins(5, 5, 5, 5) 65 | 66 | # Create a button group for radio buttons 67 | self.scope_group = QButtonGroup(self) 68 | 69 | # Current folder option (default) 70 | self.current_folder_radio = QRadioButton("Current Folder") 71 | self.current_folder_radio.setChecked(True) 72 | self.scope_group.addButton(self.current_folder_radio) 73 | scope_layout.addWidget(self.current_folder_radio) 74 | 75 | # All images option 76 | self.all_images_radio = QRadioButton("All Images") 77 | self.scope_group.addButton(self.all_images_radio) 78 | scope_layout.addWidget(self.all_images_radio) 79 | 80 | layout.addLayout(scope_layout) 81 | 82 | # Search suggestions (will be populated later) 83 | self.completer = QCompleter() 84 | self.completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) 85 | self.search_input.setCompleter(self.completer) 86 | 87 | # Add some spacing 88 | layout.addSpacing(10) 89 | 90 | # Date range search 91 | self.date_group = QGroupBox("Search by Date Range") 92 | date_layout = QVBoxLayout() 93 | 94 | # Enable checkbox 95 | self.date_enabled = QCheckBox("Enable date filtering") 96 | date_layout.addWidget(self.date_enabled) 97 | 98 | # From date 99 | from_layout = QHBoxLayout() 100 | from_layout.addWidget(QLabel("From:")) 101 | self.from_date = QDateEdit() 102 | self.from_date.setCalendarPopup(True) 103 | self.from_date.setDate(QDate.currentDate().addMonths(-1)) 104 | from_layout.addWidget(self.from_date) 105 | date_layout.addLayout(from_layout) 106 | 107 | # To date 108 | to_layout = QHBoxLayout() 109 | to_layout.addWidget(QLabel("To:")) 110 | self.to_date = QDateEdit() 111 | self.to_date.setCalendarPopup(True) 112 | self.to_date.setDate(QDate.currentDate()) 113 | to_layout.addWidget(self.to_date) 114 | date_layout.addLayout(to_layout) 115 | 116 | # Date search button 117 | self.date_search_button = QPushButton("Search by Date") 118 | self.date_search_button.clicked.connect(self.on_date_search) 119 | date_layout.addWidget(self.date_search_button) 120 | 121 | self.date_group.setLayout(date_layout) 122 | layout.addWidget(self.date_group) 123 | 124 | # Add some spacing 125 | layout.addSpacing(10) 126 | 127 | # Help text 128 | help_label = QLabel( 129 | "Enter keywords to search image descriptions. Examples:\n" 130 | "• \"sunset beach\"\n" 131 | "• \"dog playing\"\n" 132 | "• \"red car\"\n\n" 133 | "Select 'Current Folder' to search only within the selected folder,\n" 134 | "or 'All Images' to search across your entire collection.\n\n" 135 | "Or search for images by modification date range." 136 | ) 137 | help_label.setWordWrap(True) 138 | help_label.setStyleSheet("color: #666;") 139 | layout.addWidget(help_label) 140 | 141 | # Add stretch to push everything to the top 142 | layout.addStretch(1) 143 | 144 | def on_search(self): 145 | """Handle search button click or Enter key press.""" 146 | query = self.search_input.text().strip() 147 | 148 | if query: 149 | # Determine if we're searching all folders 150 | search_all_folders = self.all_images_radio.isChecked() 151 | 152 | # Emit search signal with search scope 153 | self.search_requested.emit(query, search_all_folders) 154 | 155 | # Add to recent searches (could be implemented later) 156 | 157 | def on_date_search(self): 158 | """Handle date search button click.""" 159 | if not self.date_enabled.isChecked(): 160 | return 161 | 162 | # Get date range 163 | from_date = self.from_date.date().toPyDate() 164 | from_datetime = datetime.combine(from_date, datetime.min.time()) 165 | 166 | to_date = self.to_date.date().toPyDate() 167 | to_datetime = datetime.combine(to_date, datetime.max.time()) 168 | 169 | # Emit date search signal 170 | self.date_search_requested.emit(from_datetime, to_datetime) 171 | 172 | def set_search_suggestions(self, suggestions): 173 | """Set the search suggestions for autocomplete. 174 | 175 | Args: 176 | suggestions (list): List of search suggestions 177 | """ 178 | model = QStringListModel() 179 | model.setStringList(suggestions) 180 | self.completer.setModel(model) 181 | 182 | def clear(self): 183 | """Clear the search input.""" 184 | self.search_input.clear() 185 | -------------------------------------------------------------------------------- /src/ui/splash_screen.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from PyQt6.QtWidgets import QSplashScreen 4 | from PyQt6.QtGui import QPixmap 5 | from PyQt6.QtCore import Qt, QTimer 6 | 7 | class SplashScreen(QSplashScreen): 8 | def __init__(self): 9 | # Determine the correct path to splash.png for PyInstaller onefile compatibility 10 | if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): 11 | splash_path = os.path.join(sys._MEIPASS, 'splash.png') 12 | else: 13 | # Assume splash.png is in the main directory (same as main.py) 14 | splash_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'splash.png') 15 | pixmap = QPixmap(splash_path) 16 | super().__init__(pixmap) 17 | self.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint | Qt.WindowType.FramelessWindowHint) 18 | self.setEnabled(False) 19 | 20 | def show_for(self, msecs, on_finish): 21 | self.show() 22 | QTimer.singleShot(msecs, lambda: (self.close(), on_finish())) 23 | -------------------------------------------------------------------------------- /src/ui/thumbnail_browser_factory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Thumbnail browser factory for StarImageBrowse 5 | Creates the appropriate thumbnail browser implementation based on settings 6 | """ 7 | 8 | import logging 9 | from .thumbnail_browser import ThumbnailBrowser 10 | 11 | logger = logging.getLogger("StarImageBrowse.ui.thumbnail_browser_factory") 12 | 13 | def create_thumbnail_browser(db_manager, config_manager, parent=None, language_manager=None): 14 | """Create the appropriate thumbnail browser based on configuration settings. 15 | 16 | Args: 17 | db_manager: Database manager instance 18 | config_manager: Configuration manager instance 19 | parent: Parent widget 20 | language_manager: Language manager instance for translations 21 | 22 | Returns: 23 | A thumbnail browser instance 24 | """ 25 | # Always use standard thumbnail browser for better visual appearance 26 | # This fixes selection highlight issues and provides a more consistent experience 27 | 28 | # Log collection size for informational purposes 29 | collection_size = db_manager.get_image_count() 30 | logger.info(f"Creating standard thumbnail browser for collection ({collection_size} images)") 31 | 32 | # Always return the standard thumbnail browser 33 | logger.info("Using standard thumbnail browser") 34 | # Create the thumbnail browser with language manager if provided 35 | browser = ThumbnailBrowser(db_manager, parent) 36 | 37 | # Set language manager explicitly to ensure translations work 38 | if language_manager: 39 | browser.language_manager = language_manager 40 | logger.info(f"Language manager set for thumbnail browser: {language_manager.current_language}") 41 | 42 | return browser 43 | -------------------------------------------------------------------------------- /src/ui/view_all_images.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | All Images View functionality for StarImageBrowse 5 | Implementation of the direct "All Images" view with pagination 6 | """ 7 | 8 | import logging 9 | from PyQt6.QtWidgets import QApplication, QMessageBox 10 | 11 | logger = logging.getLogger("StarImageBrowse.ui.view_all_images") 12 | 13 | def add_view_all_images_to_main_window(main_window): 14 | """Add view_all_images method to the main window 15 | 16 | Args: 17 | main_window: The main window instance 18 | """ 19 | # Define the view_all_images method 20 | def view_all_images(self): 21 | """Display all images in the database with pagination""" 22 | try: 23 | # Update the thumbnail browser UI to show we're loading 24 | # This ensures UI responsiveness during loading 25 | self.status_bar.showMessage("Loading all images...") 26 | QApplication.processEvents() # Process events to update the UI 27 | 28 | # Make sure we have the database extension methods 29 | if not hasattr(self.db_manager, 'get_all_images_count'): 30 | from src.database.db_operations_extension import extend_db_operations 31 | extend_db_operations(self.db_manager) 32 | 33 | # Get total count of images for pagination 34 | total_count = self.db_manager.get_all_images_count() 35 | 36 | # Clear any previous search or folder selection 37 | if hasattr(self.thumbnail_browser, 'current_folder_id'): 38 | self.thumbnail_browser.current_folder_id = None 39 | if hasattr(self.thumbnail_browser, 'current_catalog_id'): 40 | self.thumbnail_browser.current_catalog_id = None 41 | if hasattr(self.thumbnail_browser, 'current_search_query'): 42 | self.thumbnail_browser.current_search_query = None 43 | if hasattr(self.thumbnail_browser, 'last_search_params'): 44 | self.thumbnail_browser.last_search_params = None 45 | 46 | # Set flag to indicate this is the "All Images" view 47 | self.thumbnail_browser.all_images_view = True 48 | 49 | # Reset pagination to first page 50 | self.thumbnail_browser.current_page = 0 51 | 52 | # Get the page size from pagination if available 53 | page_size = 200 # Default 54 | if hasattr(self.thumbnail_browser, 'page_size'): 55 | page_size = self.thumbnail_browser.page_size 56 | 57 | # Enable pagination 58 | self.thumbnail_browser.is_paginated = True 59 | self.thumbnail_browser.total_items = total_count 60 | self.thumbnail_browser.total_pages = (total_count + page_size - 1) // page_size 61 | 62 | # Get first page of images 63 | images = self.db_manager.get_all_images(limit=page_size, offset=0) 64 | 65 | # Clear thumbnails and add the first page 66 | self.thumbnail_browser.clear_thumbnails() 67 | self.thumbnail_browser.add_thumbnails(images) 68 | 69 | # Update header with count 70 | if hasattr(self.thumbnail_browser, 'header_label'): 71 | self.thumbnail_browser.header_label.setText(f"All Images ({total_count} total)") 72 | 73 | # Update status 74 | page_count = (total_count + page_size - 1) // page_size 75 | if page_count > 1: 76 | self.status_bar.showMessage( 77 | f"Showing page 1 of {page_count} ({len(images)} of {total_count} images)" 78 | ) 79 | else: 80 | self.status_bar.showMessage(f"Showing all {len(images)} images") 81 | 82 | # Update pagination controls if available 83 | if hasattr(self.thumbnail_browser, 'thumbnail_pagination') and \ 84 | hasattr(self.thumbnail_browser.thumbnail_pagination, 'update_pagination_controls'): 85 | self.thumbnail_browser.thumbnail_pagination.update_pagination_controls() 86 | 87 | # Update window title 88 | if hasattr(self, 'setWindowTitle'): 89 | self.setWindowTitle("STARNODES Image Manager - All Images") 90 | 91 | except Exception as e: 92 | logger.error(f"Error viewing all images: {e}") 93 | QMessageBox.critical( 94 | self, 95 | "Error", 96 | f"An error occurred while loading all images:\n{str(e)}", 97 | QMessageBox.StandardButton.Ok 98 | ) 99 | 100 | # Add the method to the main window 101 | import types 102 | main_window.view_all_images = types.MethodType(view_all_images, main_window) 103 | 104 | # Ensure DB extensions are loaded 105 | if not hasattr(main_window.db_manager, 'get_all_images_count'): 106 | from src.database.db_operations_extension import extend_db_operations 107 | extend_db_operations(main_window.db_manager) 108 | -------------------------------------------------------------------------------- /src/utilities/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Utilities package for StarImageBrowse application 5 | """ 6 | 7 | __version__ = "1.1.0" 8 | -------------------------------------------------------------------------------- /src/utilities/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/utilities/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/utilities/__pycache__/convert_thumbnail_paths.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/utilities/__pycache__/convert_thumbnail_paths.cpython-312.pyc -------------------------------------------------------------------------------- /src/utils/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Utils package for StarImageBrowse. 5 | Contains utility functions and helper modules. 6 | """ 7 | 8 | __all__ = ['image_utils'] 9 | -------------------------------------------------------------------------------- /src/utils/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/utils/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/image_dimensions_updater.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/utils/__pycache__/image_dimensions_updater.cpython-312.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/image_utils.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/utils/__pycache__/image_utils.cpython-312.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/update_metadata.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Starnodes2024/StarnodesImageManager/02c6744b8f245d75039a3bbbd93e3c3293f12746/src/utils/__pycache__/update_metadata.cpython-312.pyc -------------------------------------------------------------------------------- /src/utils/image_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Image utility functions for StarImageBrowse. 5 | Provides functions for working with image files. 6 | """ 7 | 8 | import os 9 | import json 10 | import logging 11 | from pathlib import Path 12 | 13 | logger = logging.getLogger("StarImageBrowse.utils.image_utils") 14 | 15 | def is_supported_image(file_path): 16 | """Check if a file is a supported image format. 17 | 18 | Args: 19 | file_path (str): Path to the file to check 20 | 21 | Returns: 22 | bool: True if the file is a supported image format, False otherwise 23 | """ 24 | if not os.path.isfile(file_path): 25 | return False 26 | 27 | # Get file extension in lowercase 28 | ext = Path(file_path).suffix.lower() 29 | 30 | # List of supported image formats 31 | supported_formats = [ 32 | '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.tif', '.webp' 33 | ] 34 | 35 | return ext in supported_formats 36 | 37 | def get_image_dimensions(file_path): 38 | """Get the dimensions of an image. 39 | 40 | Args: 41 | file_path (str): Path to the image file 42 | 43 | Returns: 44 | tuple: (width, height) or None if not an image or error occurs 45 | """ 46 | try: 47 | from PIL import Image 48 | 49 | if not is_supported_image(file_path): 50 | return None 51 | 52 | with Image.open(file_path) as img: 53 | return img.size 54 | except Exception as e: 55 | logger.error(f"Error getting image dimensions for {file_path}: {e}") 56 | return None 57 | 58 | def format_dimension_string(dimensions): 59 | """Format image dimensions as a string. 60 | 61 | Args: 62 | dimensions (tuple): (width, height) tuple 63 | 64 | Returns: 65 | str: Formatted dimensions string (e.g., "1024x768") 66 | """ 67 | if not dimensions or len(dimensions) != 2: 68 | return "Unknown" 69 | 70 | width, height = dimensions 71 | return f"{width}x{height}" 72 | 73 | def extract_comfyui_workflow(image_path, output_json_path=None): 74 | """Extract ComfyUI workflow data from an image and save it as a JSON file. 75 | 76 | Args: 77 | image_path (str): Path to the image file 78 | output_json_path (str, optional): Path to save the JSON file. If None, uses image_path + "_workflow.json" 79 | 80 | Returns: 81 | tuple: (success, message, output_path) where: 82 | success (bool): True if workflow was extracted successfully 83 | message (str): Success or error message 84 | output_path (str): Path to the saved JSON file or None if failed 85 | """ 86 | try: 87 | from PIL import Image 88 | 89 | # Open the image 90 | img = Image.open(image_path) 91 | 92 | # Check if workflow data exists 93 | if "workflow" in img.info: 94 | workflow_data = img.info["workflow"] 95 | 96 | # If no output path specified, use image path as base 97 | if output_json_path is None: 98 | output_json_path = os.path.splitext(image_path)[0] + "_workflow.json" 99 | 100 | # Save workflow as JSON file 101 | with open(output_json_path, "w") as f: 102 | f.write(workflow_data) 103 | 104 | logger.info(f"ComfyUI workflow extracted to {output_json_path}") 105 | return True, "Workflow extracted successfully", output_json_path 106 | else: 107 | logger.info(f"No ComfyUI workflow data found in image: {image_path}") 108 | return False, "No workflow data found in image", None 109 | except Exception as e: 110 | error_msg = f"Error extracting workflow from {image_path}: {str(e)}" 111 | logger.error(error_msg) 112 | return False, error_msg, None 113 | 114 | 115 | 116 | --------------------------------------------------------------------------------