Compare commits
No commits in common. "master" and "session-header" have entirely different histories.
master
...
session-he
48 changed files with 577 additions and 903 deletions
14
.github/FUNDING.yml
vendored
14
.github/FUNDING.yml
vendored
|
@ -1,14 +0,0 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: # dnutiu
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: nuculabs
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -40,5 +40,3 @@ bin/
|
|||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
/.idea/workspace.xml
|
||||
/.idea/copilot
|
1
.idea/.name
Normal file
1
.idea/.name
Normal file
|
@ -0,0 +1 @@
|
|||
ImageTagger-Solution
|
|
@ -9,7 +9,6 @@
|
|||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/img-ai" />
|
||||
<option value="$PROJECT_DIR$/img-core" />
|
||||
<option value="$PROJECT_DIR$/img-ui" />
|
||||
</set>
|
||||
</option>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.9.25" />
|
||||
<option name="version" value="1.8.22" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,9 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EntryPointsManager">
|
||||
<list size="1">
|
||||
<item index="0" class="java.lang.String" itemvalue="javafx.fxml.FXML" />
|
||||
</list>
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
|
|
334
.idea/workspace.xml
Normal file
334
.idea/workspace.xml
Normal file
|
@ -0,0 +1,334 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="dac8f85c-5bd6-4e05-9520-9e6a91c7f78e" name="Changes" comment="add module-info.java">
|
||||
<change beforePath="$PROJECT_DIR$/readme.md" beforeDir="false" afterPath="$PROJECT_DIR$/readme.md" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ExternalProjectsData">
|
||||
<projectState path="$PROJECT_DIR$">
|
||||
<ProjectState />
|
||||
</projectState>
|
||||
<projectState path="$PROJECT_DIR$/img-ui">
|
||||
<ProjectState />
|
||||
</projectState>
|
||||
</component>
|
||||
<component name="ExternalProjectsManager">
|
||||
<system id="GRADLE">
|
||||
<state>
|
||||
<task path="$PROJECT_DIR$/img-ui">
|
||||
<activation />
|
||||
</task>
|
||||
<task path="$PROJECT_DIR$">
|
||||
<activation />
|
||||
</task>
|
||||
<task path="$PROJECT_DIR$/ImageTagger">
|
||||
<activation />
|
||||
</task>
|
||||
<task path="$PROJECT_DIR$/img-ai">
|
||||
<activation />
|
||||
</task>
|
||||
<projects_view>
|
||||
<tree_state>
|
||||
<expand>
|
||||
<path>
|
||||
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
|
||||
<item name="ImageTagger-Solution" type="f1a62948:ProjectNode" />
|
||||
</path>
|
||||
</expand>
|
||||
<select />
|
||||
</tree_state>
|
||||
</projects_view>
|
||||
</state>
|
||||
</system>
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Class" />
|
||||
<option value="module-info" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"customColor": "",
|
||||
"associatedIndex": 5
|
||||
}</component>
|
||||
<component name="ProjectId" id="2eOxGB0t00dWfHWsgfDybOaUUBb" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"Gradle.Build img-ui.executor": "Run",
|
||||
"Gradle.ImageTagger [build].executor": "Run",
|
||||
"Gradle.ImageTagger:img-ai [test].executor": "Run",
|
||||
"Gradle.ImageTagger:img-ui [clean].executor": "Run",
|
||||
"Gradle.ImageTagger:img-ui [jlinkZip].executor": "Run",
|
||||
"Gradle.ImageTagsPredictionTests.executor": "Run",
|
||||
"Gradle.img-ui [build].executor": "Run",
|
||||
"Gradle.img-ui [run].executor": "Run",
|
||||
"Kotlin.MainPageKt.executor": "Debug",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"dart.analysis.tool.window.visible": "false",
|
||||
"git-widget-placeholder": "multi-project",
|
||||
"jdk.selected.JAVA_MODULE": "17",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "/Users/dnutiu/Projects/ImageTagger/img-ai/src/test/resources/dev/nuculabs/imagetagger/ai",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"project.structure.last.edited": "Modules",
|
||||
"project.structure.proportion": "0.15",
|
||||
"project.structure.side.proportion": "0.29070836",
|
||||
"settings.editor.selected.configurable": "reference.settingsdialog.project.gradle",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/img-ai/src/test/resources/dev/nuculabs/imagetagger/ai" />
|
||||
<recent name="$PROJECT_DIR$/img-ai/src/main/resources/dev.nuculabs.imagetagger.ai" />
|
||||
<recent name="$PROJECT_DIR$/img-ui" />
|
||||
<recent name="$PROJECT_DIR$" />
|
||||
</key>
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/img-ai/src/main/resources/dev/nuculabs/imagetagger/ai" />
|
||||
<recent name="$PROJECT_DIR$" />
|
||||
</key>
|
||||
<key name="MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY">
|
||||
<recent name="dev.nuculabs.imagetagger.ai" />
|
||||
</key>
|
||||
<key name="CopyKotlinDeclarationDialog.RECENTS_KEY">
|
||||
<recent name="" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager" selected="Gradle.ImageTagger:img-ui [jlinkZip]">
|
||||
<configuration name="ImageTagger:img-ui [clean]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$/img-ui" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="clean" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="ImageTagger:img-ui [jlinkZip]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$/img-ui" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="jlinkZip" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="ImageTagsPredictionTests" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":img-ai:test" />
|
||||
<option value="--tests" />
|
||||
<option value=""dev.nuculabs.imagetagger.ai.ImageTagsPredictionTests"" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="img-ui [build]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$/img-ui" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="build" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="img-ui [run]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$/img-ui" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="run" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Gradle.ImageTagger:img-ui [jlinkZip]" />
|
||||
<item itemvalue="Gradle.ImageTagger:img-ui [clean]" />
|
||||
<item itemvalue="Gradle.img-ui [run]" />
|
||||
<item itemvalue="Gradle.ImageTagsPredictionTests" />
|
||||
<item itemvalue="Gradle.img-ui [build]" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="jdk-17.0.10-corretto-17.0.10-4caba194b151-53826d6a" />
|
||||
<option value="jdk-17.0.9-corretto-17.0.9-4caba194b151-92e50af3" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="dac8f85c-5bd6-4e05-9520-9e6a91c7f78e" name="Changes" comment="" />
|
||||
<created>1711789328334</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1711789328334</updated>
|
||||
<workItem from="1711789330084" duration="2109000" />
|
||||
<workItem from="1711791444105" duration="4705000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="split ImageTagger into subproject img-ui">
|
||||
<option name="closed" value="true" />
|
||||
<created>1711790450344</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1711790450344</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="update readme.md">
|
||||
<option name="closed" value="true" />
|
||||
<created>1711790617285</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1711790617285</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="update gradle config">
|
||||
<option name="closed" value="true" />
|
||||
<created>1711791812195</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1711791812195</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00004" summary="extract common ai-related functionality into img-ai">
|
||||
<option name="closed" value="true" />
|
||||
<created>1711793501074</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1711793501074</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00005" summary="add module-info.java">
|
||||
<option name="closed" value="true" />
|
||||
<created>1711796896478</created>
|
||||
<option name="number" value="00005" />
|
||||
<option name="presentableId" value="LOCAL-00005" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1711796896478</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="6" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="split ImageTagger into subproject img-ui" />
|
||||
<MESSAGE value="update readme.md" />
|
||||
<MESSAGE value="update gradle config" />
|
||||
<MESSAGE value="extract common ai-related functionality into img-ai" />
|
||||
<MESSAGE value="add module-info.java" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="add module-info.java" />
|
||||
</component>
|
||||
<component name="XDebuggerManager">
|
||||
<breakpoint-manager>
|
||||
<breakpoints>
|
||||
<line-breakpoint enabled="true" type="kotlin-line">
|
||||
<url>file://$PROJECT_DIR$/img-ui/src/main/kotlin/dev/nuculabs/imagetagger/ui/MainPage.kt</url>
|
||||
<line>70</line>
|
||||
<option name="timeStamp" value="1" />
|
||||
</line-breakpoint>
|
||||
</breakpoints>
|
||||
</breakpoint-manager>
|
||||
</component>
|
||||
<component name="XSLT-Support.FileAssociations.UIState">
|
||||
<expand />
|
||||
<select />
|
||||
</component>
|
||||
</project>
|
Binary file not shown.
Before Width: | Height: | Size: 327 KiB After Width: | Height: | Size: 316 KiB |
3
flatpak/.gitignore
vendored
3
flatpak/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
build-dir
|
||||
.idea
|
||||
.flatpak-builder
|
|
@ -1,3 +0,0 @@
|
|||
# Script to debug the Flatpak application.
|
||||
flatpak-builder --user --install --force-clean build-dir dev.nuculabs.ImageTagger.yaml
|
||||
flatpak run dev.nuculabs.ImageTagger
|
|
@ -1,9 +0,0 @@
|
|||
[Desktop Entry]
|
||||
Name=ImageTagger
|
||||
Exec=/app/bin/ImageTagger/bin/ImageTagger
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Icon=dev.nuculabs.ImageTagger
|
||||
StartupWMClass=ImageTagger
|
||||
Comment=Image Tagger
|
||||
Categories=Utility;
|
Binary file not shown.
Before Width: | Height: | Size: 42 KiB |
|
@ -1,49 +0,0 @@
|
|||
id: dev.nuculabs.ImageTagger
|
||||
runtime: org.freedesktop.Platform
|
||||
runtime-version: '23.08'
|
||||
sdk: org.freedesktop.Sdk
|
||||
sdk-extensions:
|
||||
- org.freedesktop.Sdk.Extension.openjdk17
|
||||
command: /app/bin/ImageTagger/bin/ImageTagger
|
||||
finish-args:
|
||||
# X11 + XShm access
|
||||
- --share=ipc
|
||||
- --socket=fallback-x11
|
||||
# Wayland access
|
||||
- --socket=wayland
|
||||
# GPU acceleration if needed
|
||||
- --device=dri
|
||||
# Needs to save files locally
|
||||
- --filesystem=home
|
||||
modules:
|
||||
- name: ImageTagger
|
||||
buildsystem: simple
|
||||
build-options:
|
||||
env:
|
||||
PATH: /app/bin:/usr/bin:/usr/lib/sdk/openjdk17/bin
|
||||
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
|
||||
build-args:
|
||||
- --share=network
|
||||
build-commands:
|
||||
- cp prediction.onnx img-ai/src/main/resources/dev/nuculabs/imagetagger/ai/
|
||||
- cp prediction_categories.txt img-ai/src/main/resources/dev/nuculabs/imagetagger/ai/
|
||||
- gradle jpackageImage
|
||||
- mkdir -p /app/bin/
|
||||
# Copy ImageTagger folder to /app/bin
|
||||
- ls img-ui/build/jpackage
|
||||
- cp -R img-ui/build/jpackage/ImageTagger /app/bin/
|
||||
- ls /app/bin/
|
||||
# Desktop Integration
|
||||
- mkdir -p bin /app/share/{applications,icons/hicolor/512x512/apps,metainfo}
|
||||
- mv ${FLATPAK_ID}.desktop /app/share/applications/${FLATPAK_ID}.desktop
|
||||
- mv ${FLATPAK_ID}.png /app/share/icons/hicolor/512x512/apps/${FLATPAK_ID}.png
|
||||
sources:
|
||||
- type: git
|
||||
url: https://github.com/dnutiu/ImageTagger
|
||||
- type: archive
|
||||
url: https://github.com/dnutiu/ImageTagger/releases/download/v1/AIModels.zip
|
||||
sha256: "bbe80bf135621897bc6186d8f17b889064717ed3b9951702b33be869e522321c"
|
||||
- type: file
|
||||
path: dev.nuculabs.ImageTagger.png
|
||||
- type: file
|
||||
path: dev.nuculabs.ImageTagger.desktop
|
|
@ -1,9 +0,0 @@
|
|||
# Flatpak
|
||||
|
||||
This directory contains the flatpak build files.
|
||||
|
||||
If you are on Linux and would like to install this application as a Flatpak then execute
|
||||
|
||||
```shell
|
||||
./build.sh
|
||||
```
|
|
@ -1,20 +1,19 @@
|
|||
plugins {
|
||||
id("java")
|
||||
kotlin("jvm") version "1.9.25"
|
||||
kotlin("jvm") version "1.8.22"
|
||||
id("org.javamodularity.moduleplugin") version "1.8.12"
|
||||
|
||||
}
|
||||
|
||||
var junitVersion = "5.10.0"
|
||||
group = "dev.nuculabs.imagetagger.ai"
|
||||
version = "1.4"
|
||||
version = "1.1"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":img-core"))
|
||||
implementation("com.microsoft.onnxruntime:onnxruntime:1.17.1")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
|
||||
|
|
|
@ -3,6 +3,5 @@ module dev.nuculabs.imagetagger.ai {
|
|||
requires java.desktop;
|
||||
requires java.logging;
|
||||
requires kotlin.stdlib;
|
||||
requires dev.nuculabs.imagetagger.core;
|
||||
exports dev.nuculabs.imagetagger.ai;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package dev.nuculabs.imagetagger.core.abstractions
|
||||
package dev.nuculabs.imagetagger.ai
|
||||
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.InputStream
|
|
@ -3,7 +3,6 @@ package dev.nuculabs.imagetagger.ai
|
|||
import ai.onnxruntime.OnnxTensor
|
||||
import ai.onnxruntime.OrtEnvironment
|
||||
import ai.onnxruntime.OrtSession
|
||||
import dev.nuculabs.imagetagger.core.abstractions.IImageTagsPrediction
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
plugins {
|
||||
id("java")
|
||||
kotlin("jvm") version "1.9.25"
|
||||
id("org.javamodularity.moduleplugin") version "1.8.12"
|
||||
}
|
||||
|
||||
group = "dev.nuculabs.imagetagger.core"
|
||||
version = "1.4"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation(kotlin("test"))
|
||||
implementation("com.drewnoakes:metadata-extractor:2.19.0")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
module dev.nuculabs.imagetagger.core {
|
||||
requires kotlin.stdlib;
|
||||
requires java.desktop;
|
||||
requires java.logging;
|
||||
requires metadata.extractor;
|
||||
exports dev.nuculabs.imagetagger.core;
|
||||
exports dev.nuculabs.imagetagger.core.abstractions;
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
package dev.nuculabs.imagetagger.core
|
||||
|
||||
import dev.nuculabs.imagetagger.core.abstractions.IImageTagsPrediction
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.File
|
||||
import java.util.logging.Logger
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
|
||||
/**
|
||||
* AnalyzedImage represents an Analyzed Image inside the ImageTagger application.
|
||||
*/
|
||||
class AnalyzedImage(private val file: File, imageTagsPrediction: IImageTagsPrediction) {
|
||||
|
||||
private var imageFile: File = file
|
||||
private var bufferedImage: BufferedImage? = null
|
||||
private var predictedTags: List<String> = emptyList()
|
||||
private val logger: Logger = Logger.getLogger("AnalyzedImage")
|
||||
private lateinit var imageMetadata: IImageMetadata
|
||||
private var error: String = ""
|
||||
private var hasError: Boolean = false
|
||||
|
||||
/**
|
||||
* Initializes the analyzed image and predicts its tags.
|
||||
*/
|
||||
init {
|
||||
try {
|
||||
imageFile = File(file.absolutePath)
|
||||
bufferedImage = ImageIO.read(imageFile)
|
||||
predictedTags = imageTagsPrediction.predictTags(bufferedImage!!)
|
||||
imageMetadata = ImageMetadata(imageFile)
|
||||
} catch (e: NullPointerException) {
|
||||
val message = "Error while predicting image: invalid image type or type not supported."
|
||||
logger.warning(message)
|
||||
hasError = true
|
||||
error = message
|
||||
} catch (e: Exception) {
|
||||
val message = "Error loading image $e"
|
||||
logger.warning(message)
|
||||
hasError = true
|
||||
error = e.message.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an image's metadata
|
||||
*/
|
||||
fun metadata(): IImageMetadata {
|
||||
return imageMetadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating if the image analysis has errors.
|
||||
* The flag is `True` if it has errors, `False` otherwise.
|
||||
*/
|
||||
fun hasError(): Boolean {
|
||||
return hasError
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prediction error
|
||||
*/
|
||||
fun errorMessage(): String {
|
||||
return error
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute file path of the image.
|
||||
*/
|
||||
fun absolutePath(): String {
|
||||
return file.absolutePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the predicted tags.
|
||||
*/
|
||||
fun tags(): List<String> {
|
||||
return predictedTags
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
package dev.nuculabs.imagetagger.core
|
||||
|
||||
import com.drew.imaging.ImageMetadataReader
|
||||
import com.drew.metadata.Metadata
|
||||
import com.drew.metadata.exif.ExifIFD0Directory
|
||||
import com.drew.metadata.exif.ExifSubIFDDirectory
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Interface that holds common image metadata objects used by this application.
|
||||
*/
|
||||
interface IImageMetadata {
|
||||
val artist: String
|
||||
val aperture: String
|
||||
val shutterSpeed: String
|
||||
val iso: String
|
||||
val lensModel: String
|
||||
val cameraModel: String
|
||||
val cameraBrand: String
|
||||
}
|
||||
|
||||
/**
|
||||
* An image metadata provider that uses
|
||||
*/
|
||||
class ImageMetadata internal constructor(file: File) : IImageMetadata {
|
||||
private var metadata: Metadata? = null
|
||||
private lateinit var _cameraBrand: String
|
||||
private lateinit var _cameraModel: String
|
||||
private lateinit var _lensModel: String
|
||||
private lateinit var _artist: String
|
||||
private lateinit var _aperture: String
|
||||
private lateinit var _iso: String
|
||||
private lateinit var _shutterSpeed: String
|
||||
|
||||
init {
|
||||
metadata = ImageMetadataReader.readMetadata(file)
|
||||
if (metadata != null) {
|
||||
val exifDirectory = metadata?.getFirstDirectoryOfType(ExifIFD0Directory::class.java)
|
||||
val exifSubDirectory = metadata?.getFirstDirectoryOfType(ExifSubIFDDirectory::class.java)
|
||||
|
||||
_cameraBrand = exifDirectory?.getString(ExifIFD0Directory.TAG_MAKE) ?: "Unknown"
|
||||
_cameraModel = exifDirectory?.getString(ExifIFD0Directory.TAG_MODEL) ?: "Unknown"
|
||||
_lensModel = exifSubDirectory?.getString(ExifIFD0Directory.TAG_LENS_MODEL) ?: "Unknown"
|
||||
_artist = exifDirectory?.getString(ExifIFD0Directory.TAG_ARTIST) ?: "Unknown"
|
||||
_aperture = exifSubDirectory?.getString(ExifIFD0Directory.TAG_FNUMBER) ?: "Unknown"
|
||||
_iso = exifSubDirectory?.getString(ExifIFD0Directory.TAG_ISO_EQUIVALENT) ?: "Unknown"
|
||||
_shutterSpeed = exifSubDirectory?.getString(ExifIFD0Directory.TAG_EXPOSURE_TIME) ?: "Unknown"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override val cameraBrand: String
|
||||
get() {
|
||||
if (metadata == null) {
|
||||
return "Unknown"
|
||||
}
|
||||
return _cameraBrand
|
||||
}
|
||||
|
||||
override val cameraModel: String
|
||||
get() {
|
||||
if (metadata == null) {
|
||||
return "Unknown"
|
||||
}
|
||||
return _cameraModel
|
||||
}
|
||||
|
||||
override val lensModel: String
|
||||
get() {
|
||||
if (metadata == null) {
|
||||
return "Unknown"
|
||||
}
|
||||
return _lensModel
|
||||
}
|
||||
|
||||
override val artist: String
|
||||
get() {
|
||||
if (metadata == null) {
|
||||
return "Unknown"
|
||||
}
|
||||
return _artist
|
||||
}
|
||||
|
||||
override val aperture: String
|
||||
get() {
|
||||
if (metadata == null) {
|
||||
return "Unknown"
|
||||
}
|
||||
return _aperture
|
||||
}
|
||||
|
||||
override val iso: String
|
||||
get() {
|
||||
if (metadata == null) {
|
||||
return "Unknown"
|
||||
}
|
||||
return _iso
|
||||
}
|
||||
|
||||
override val shutterSpeed: String
|
||||
get() {
|
||||
if (metadata == null) {
|
||||
return "Unknown"
|
||||
}
|
||||
return _shutterSpeed
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ImageMetadata(cameraBrand='$cameraBrand', cameraModel='$cameraModel', lensModel='$lensModel', artist='$artist', aperture='$aperture', iso='$iso', shutterSpeed='$shutterSpeed')"
|
||||
}
|
||||
}
|
69
img-ui/build.gradle
Normal file
69
img-ui/build.gradle
Normal file
|
@ -0,0 +1,69 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.8.22'
|
||||
id 'org.javamodularity.moduleplugin' version '1.8.12'
|
||||
id 'org.openjfx.javafxplugin' version '0.0.13'
|
||||
id 'org.beryx.jlink' version '2.25.0'
|
||||
}
|
||||
|
||||
group 'com.nuculabs.dev'
|
||||
version '1.1'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
ext {
|
||||
junitVersion = '5.10.0'
|
||||
}
|
||||
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
application {
|
||||
mainModule = 'dev.nuculabs.imagetagger.ui'
|
||||
mainClass = 'dev.nuculabs.imagetagger.ui.MainPage'
|
||||
}
|
||||
kotlin {
|
||||
jvmToolchain( 17 )
|
||||
}
|
||||
|
||||
javafx {
|
||||
version = '21'
|
||||
modules = ['javafx.controls', 'javafx.fxml']
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":img-ai"))
|
||||
implementation('org.controlsfx:controlsfx:11.1.2')
|
||||
implementation('com.dlsc.formsfx:formsfx-core:11.6.0') {
|
||||
exclude(group: 'org.openjfx')
|
||||
}
|
||||
implementation('net.synedra:validatorfx:0.4.0') {
|
||||
exclude(group: 'org.openjfx')
|
||||
}
|
||||
implementation('org.kordamp.ikonli:ikonli-javafx:12.3.1')
|
||||
implementation('org.kordamp.ikonli:ikonli-fontawesome5-pack:12.3.1')
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1-Beta")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
jlink {
|
||||
imageZip = project.file("${buildDir}/distributions/ImageTagger-${javafx.platform.classifier}-${version}.zip")
|
||||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
|
||||
launcher {
|
||||
name = 'ImageTagger'
|
||||
}
|
||||
}
|
||||
|
||||
jlinkZip {
|
||||
group = 'distribution'
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
plugins {
|
||||
id("java")
|
||||
id("application")
|
||||
id("org.jetbrains.kotlin.jvm") version "1.9.25"
|
||||
id("org.javamodularity.moduleplugin") version "1.8.12"
|
||||
id("org.openjfx.javafxplugin") version "0.0.13"
|
||||
id("org.beryx.jlink") version "2.25.0"
|
||||
}
|
||||
|
||||
group "com.nuculabs.dev"
|
||||
version "1.4"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
|
||||
tasks.withType(JavaCompile::class.java) {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
application {
|
||||
mainModule = "dev.nuculabs.imagetagger.ui"
|
||||
mainClass = "dev.nuculabs.imagetagger.ui.MainPage"
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
javafx {
|
||||
version = "21"
|
||||
modules = listOf("javafx.controls", "javafx.fxml")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":img-ai"))
|
||||
implementation(project(":img-core"))
|
||||
implementation("org.controlsfx:controlsfx:11.1.2")
|
||||
implementation("com.dlsc.formsfx:formsfx-core:11.6.0") {
|
||||
exclude("org.openjfx")
|
||||
}
|
||||
implementation("net.synedra:validatorfx:0.4.0") {
|
||||
exclude("org.openjfx")
|
||||
}
|
||||
implementation("org.kordamp.ikonli:ikonli-javafx:12.3.1")
|
||||
implementation("org.kordamp.ikonli:ikonli-fontawesome5-pack:12.3.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1-Beta")
|
||||
implementation("org.apache.commons:commons-lang3:3.14.0")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
jlink {
|
||||
val buildDirectory = layout.buildDirectory.asFile.get().absolutePath
|
||||
imageZip = project.file("$buildDirectory/distributions/ImageTagger-${javafx.platform.classifier}.zip")
|
||||
options = listOf("--strip-debug", "--compress", "2", "--no-header-files", "--no-man-pages")
|
||||
launcher {
|
||||
unixScriptTemplate = project.file("${layout.projectDirectory}/src/main/resources/unixExecutableScriptTemplate.txt")
|
||||
name = "ImageTagger"
|
||||
}
|
||||
jpackage {
|
||||
icon = "${layout.projectDirectory}/src/main/resources/dev/nuculabs/imagetagger/ui/image-analysis.png"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.jlinkZip {
|
||||
group = "distribution"
|
||||
}
|
|
@ -14,11 +14,9 @@ module dev.nuculabs.imagetagger.ui {
|
|||
requires org.kordamp.ikonli.fontawesome5;
|
||||
requires kotlinx.coroutines.core;
|
||||
requires dev.nuculabs.imagetagger.ai;
|
||||
requires dev.nuculabs.imagetagger.core;
|
||||
requires org.apache.commons.lang3;
|
||||
|
||||
opens dev.nuculabs.imagetagger.ui to javafx.fxml, javafx.graphics;
|
||||
opens dev.nuculabs.imagetagger.ui.controls to javafx.fxml, javafx.graphics, javafx.base;
|
||||
opens dev.nuculabs.imagetagger.ui.controls to javafx.fxml, javafx.graphics;
|
||||
opens dev.nuculabs.imagetagger.ui.pages to javafx.fxml, javafx.graphics;
|
||||
exports dev.nuculabs.imagetagger.ui;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package dev.nuculabs.imagetagger.ui
|
||||
|
||||
import dev.nuculabs.imagetagger.core.abstractions.IImageTagsPrediction
|
||||
import dev.nuculabs.imagetagger.ai.IImageTagsPrediction
|
||||
|
||||
/**
|
||||
* BasicServiceLocator is implemented to avoid polluting the apps with singletons.
|
||||
|
@ -10,7 +10,6 @@ import dev.nuculabs.imagetagger.core.abstractions.IImageTagsPrediction
|
|||
*/
|
||||
class BasicServiceLocator private constructor() {
|
||||
internal lateinit var imageTagsPrediction: IImageTagsPrediction
|
||||
internal lateinit var mainPageController: MainPageController
|
||||
|
||||
// Singleton Pattern
|
||||
companion object {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package dev.nuculabs.imagetagger.ui
|
||||
|
||||
import dev.nuculabs.imagetagger.core.abstractions.IImageTagsPrediction
|
||||
import dev.nuculabs.imagetagger.ai.IImageTagsPrediction
|
||||
import dev.nuculabs.imagetagger.ai.ImageTagsPrediction
|
||||
import dev.nuculabs.imagetagger.ui.controls.programatic.ApplicationMenuBar
|
||||
import javafx.application.Application
|
||||
import javafx.application.Platform
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.Scene
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.layout.BorderPane
|
||||
import javafx.stage.Stage
|
||||
import java.awt.Taskbar
|
||||
import java.awt.Toolkit
|
||||
|
@ -32,24 +34,25 @@ class MainPage : Application() {
|
|||
setUpApplicationIcon()
|
||||
|
||||
// Load the FXML.
|
||||
val scene = Scene(fxmlLoader.load(), 740.0, 760.0)
|
||||
val scene = Scene(fxmlLoader.load(), 640.0, 760.0)
|
||||
|
||||
// Initialize the controller.
|
||||
val mainPageController = fxmlLoader.getController<MainPageController>()
|
||||
mainPageController.initialize()
|
||||
// Set MainPage controller.
|
||||
serviceLocator.mainPageController = fxmlLoader.getController()
|
||||
|
||||
// Set up the stage.
|
||||
stage.title = "Image Tagger"
|
||||
stage.scene = scene
|
||||
stage.minWidth = 960.0
|
||||
stage.minWidth = 640.0
|
||||
stage.minHeight = 760.0
|
||||
// Whe the main window is hidden we exit the application.
|
||||
stage.setOnHidden {
|
||||
Platform.exit()
|
||||
}
|
||||
|
||||
// Add menu bar
|
||||
(scene.root as BorderPane).children.add(ApplicationMenuBar(mainPageController))
|
||||
|
||||
stage.show()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +1,23 @@
|
|||
package dev.nuculabs.imagetagger.ui
|
||||
|
||||
import dev.nuculabs.imagetagger.core.AnalyzedImage
|
||||
import dev.nuculabs.imagetagger.ui.controls.ImageTagsDisplayMode
|
||||
import dev.nuculabs.imagetagger.ui.controls.ImageTagsEntryControl
|
||||
import dev.nuculabs.imagetagger.ui.controls.ImageTagsSessionHeader
|
||||
import javafx.application.Platform
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Button
|
||||
import javafx.scene.control.ChoiceBox
|
||||
import javafx.scene.control.ProgressBar
|
||||
import javafx.scene.control.Separator
|
||||
import javafx.scene.layout.HBox
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.stage.FileChooser
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.Semaphore
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.logging.Logger
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
|
||||
class MainPageController {
|
||||
|
@ -36,7 +34,7 @@ class MainPageController {
|
|||
private val maxImagesPredictionInProgress = Runtime.getRuntime().availableProcessors()
|
||||
|
||||
/**
|
||||
* Semaphore to limit the maximum number of predictions submitted to the tread pool.
|
||||
* Semaphore to limit the maximum amount of predictions submitted to the tread pool.
|
||||
*/
|
||||
private val workerSemaphore: Semaphore = Semaphore(maxImagesPredictionInProgress)
|
||||
|
||||
|
@ -56,21 +54,11 @@ class MainPageController {
|
|||
private val imageTagsPrediction = BasicServiceLocator.getInstance().imageTagsPrediction
|
||||
|
||||
/**
|
||||
* A boolean that when set to, true it will stop the current image tagging operation.
|
||||
* When a new operation is started, the boolean is reset to false.
|
||||
* A boolean that when set to true it will stop the current image tagging operation.
|
||||
* When a new operation is started the boolean is reset to false.
|
||||
*/
|
||||
private var isCurrentTagsOperationCancelled: Boolean = false
|
||||
|
||||
/**
|
||||
* Holds a list of predicted images controls that are rendered in the view.
|
||||
*/
|
||||
private var predictedImages: MutableList<ImageTagsEntryControl> = ArrayList()
|
||||
|
||||
/**
|
||||
* Controls how image tags are displayed on the screen.
|
||||
*/
|
||||
private var tagsDisplayMode: ImageTagsDisplayMode = ImageTagsDisplayMode.Comma
|
||||
|
||||
@FXML
|
||||
private lateinit var progressBar: ProgressBar
|
||||
|
||||
|
@ -80,40 +68,12 @@ class MainPageController {
|
|||
@FXML
|
||||
private lateinit var cancelButton: Button
|
||||
|
||||
@FXML
|
||||
private lateinit var tagImagesButton: Button
|
||||
|
||||
@FXML
|
||||
private lateinit var tagsDisplayModeSelection: ChoiceBox<String>
|
||||
|
||||
/**
|
||||
* Initializes the controller. Needs to be called after the dependencies have been injected.
|
||||
*/
|
||||
fun initialize() {
|
||||
HBox.setHgrow(progressBar, Priority.ALWAYS)
|
||||
HBox.setHgrow(cancelButton, Priority.ALWAYS)
|
||||
|
||||
initializeTagsDisplayMode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the tags display mode.
|
||||
*/
|
||||
private fun initializeTagsDisplayMode() {
|
||||
// Tags display mode
|
||||
tagsDisplayModeSelection.items = FXCollections.observableArrayList(
|
||||
ImageTagsDisplayMode.entries.map { it.toString() }
|
||||
)
|
||||
tagsDisplayModeSelection.value = ImageTagsDisplayMode.default().toString()
|
||||
|
||||
tagsDisplayModeSelection.selectionModel.selectedItemProperty().addListener { _, oldValue, newValue ->
|
||||
if (oldValue != newValue && newValue != null) {
|
||||
tagsDisplayMode = ImageTagsDisplayMode.fromString(newValue)
|
||||
predictedImages.forEach {
|
||||
it.setTagsDisplayMode(tagsDisplayMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,10 +84,7 @@ class MainPageController {
|
|||
synchronized(this) {
|
||||
val fileChooser = FileChooser().apply { title = "Choose images" }
|
||||
val filePaths = fileChooser.showOpenMultipleDialog(null) ?: return
|
||||
if (tagImagesButton.isDisable) {
|
||||
return
|
||||
}
|
||||
tagImagesButton.isDisable = true
|
||||
|
||||
isCurrentTagsOperationCancelled = false
|
||||
progressBar.isVisible = true
|
||||
cancelButton.isVisible = true
|
||||
|
@ -155,12 +112,19 @@ class MainPageController {
|
|||
return@submit
|
||||
}
|
||||
|
||||
val analyzedImage = AnalyzedImage(filePath, this.imageTagsPrediction)
|
||||
workerSemaphore.release()
|
||||
Platform.runLater {
|
||||
// Add image and prediction to the view.
|
||||
addNewImagePredictionEntry(analyzedImage)
|
||||
updateProgressBar()
|
||||
predictImageTags(
|
||||
filePath,
|
||||
onError = {
|
||||
workerSemaphore.release()
|
||||
}
|
||||
) { imagePath, imageTags ->
|
||||
// Add newly predicted tags to UI.
|
||||
Platform.runLater {
|
||||
// Add image and prediction to the view.
|
||||
addNewImagePredictionEntry(imagePath, imageTags)
|
||||
updateProgressBar()
|
||||
workerSemaphore.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -178,18 +142,38 @@ class MainPageController {
|
|||
cancelButton.isVisible = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Predicts an image tags and executes an action with it.
|
||||
*
|
||||
* @param filePath - The image file's absolute path.
|
||||
*/
|
||||
fun predictImageTags(
|
||||
filePath: File,
|
||||
onError: (Exception) -> Unit,
|
||||
onSuccess: (String, List<String>) -> Unit
|
||||
) {
|
||||
try {
|
||||
// Get predictions for the image.
|
||||
val imageFile = ImageIO.read(File(filePath.absolutePath))
|
||||
val tags: List<String> = imageTagsPrediction.predictTags(imageFile)
|
||||
onSuccess(filePath.absolutePath, tags)
|
||||
} catch (e: Exception) {
|
||||
logger.warning("Error while predicting images $e")
|
||||
onError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the UI with a new ImagePredictionEntry.
|
||||
*
|
||||
* @param analyzedImage - The analyzed image instance.
|
||||
* @param imagePath - The image path.
|
||||
* @param imageTags - The image's tags.
|
||||
*/
|
||||
fun addNewImagePredictionEntry(
|
||||
analyzedImage: AnalyzedImage,
|
||||
imagePath: String,
|
||||
imageTags: List<String>,
|
||||
) {
|
||||
val control = ImageTagsEntryControl(analyzedImage)
|
||||
control.setTagsDisplayMode(tagsDisplayMode)
|
||||
verticalBox.children.add(control)
|
||||
predictedImages.add(control)
|
||||
verticalBox.children.add(ImageTagsEntryControl(imagePath, imageTags))
|
||||
verticalBox.children.add(Separator())
|
||||
}
|
||||
|
||||
|
@ -207,16 +191,13 @@ class MainPageController {
|
|||
* Updates the progress bar of the UI.
|
||||
*/
|
||||
fun updateProgressBar() {
|
||||
synchronized(this) {
|
||||
logger.info("Progress ${processedImageFilesCount.get()}/${imageFilesTotal} ${progressBar.progress}")
|
||||
val processedImages = processedImageFilesCount.incrementAndGet()
|
||||
progressBar.progress = ((processedImages * 100) / imageFilesTotal).toDouble() / 100.0
|
||||
if (processedImageFilesCount.get() == imageFilesTotal) {
|
||||
progressBar.isVisible = false
|
||||
cancelButton.isVisible = false
|
||||
tagImagesButton.isDisable = false
|
||||
logger.info("Finished processing images.")
|
||||
}
|
||||
logger.info("Progress ${processedImageFilesCount.get()}/${imageFilesTotal} ${progressBar.progress}")
|
||||
progressBar.progress =
|
||||
((processedImageFilesCount.incrementAndGet() * 100) / imageFilesTotal).toDouble() / 100.0
|
||||
if (processedImageFilesCount.get() == imageFilesTotal) {
|
||||
progressBar.isVisible = false
|
||||
cancelButton.isVisible = false
|
||||
logger.info("Finished processing images.")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
package dev.nuculabs.imagetagger.ui.controls
|
||||
|
||||
import dev.nuculabs.imagetagger.ui.BasicServiceLocator
|
||||
import dev.nuculabs.imagetagger.ui.MainPageController
|
||||
import dev.nuculabs.imagetagger.ui.pages.AboutPage
|
||||
import javafx.fxml.FXML
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.control.MenuBar
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Used as the application menu bar.
|
||||
*/
|
||||
class ApplicationMenuBar : MenuBar() {
|
||||
private val mainPageController: MainPageController by lazy {
|
||||
BasicServiceLocator.getInstance().mainPageController
|
||||
}
|
||||
|
||||
init {
|
||||
// Load component
|
||||
val fxmlLoader = FXMLLoader(
|
||||
ImageTagsSessionHeader::class.java.getResource("application-menu-bar.fxml")
|
||||
)
|
||||
fxmlLoader.setRoot(this)
|
||||
fxmlLoader.setController(this)
|
||||
try {
|
||||
fxmlLoader.load()
|
||||
} catch (exception: IOException) {
|
||||
throw RuntimeException(exception)
|
||||
}
|
||||
|
||||
useSystemMenuBarProperty().set(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the tag images functionality from the main controller.
|
||||
* The controller is retrieved in a lazy-like fashion.
|
||||
*/
|
||||
@FXML
|
||||
private fun fileMenuTagImages() {
|
||||
mainPageController.onTagImagesButtonClick()
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the about dialog.
|
||||
*/
|
||||
@FXML
|
||||
private fun aboutMenuShowAbout() {
|
||||
AboutPage.show()
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package dev.nuculabs.imagetagger.ui.controls
|
||||
|
||||
/**
|
||||
* Determines how tags are displayed
|
||||
*/
|
||||
enum class ImageTagsDisplayMode {
|
||||
Comma,
|
||||
HashTags,
|
||||
Space;
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Builds the enum value from a given string.
|
||||
*
|
||||
* @param value - The string
|
||||
* @throws IllegalArgumentException when an invalid value is provided.
|
||||
*/
|
||||
fun fromString(value: String): ImageTagsDisplayMode {
|
||||
return when (value) {
|
||||
"Comma" -> Comma
|
||||
"Space" -> Space
|
||||
"HashTags" -> HashTags
|
||||
else -> throw IllegalArgumentException("Invalid argument $value")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default tags display mode.
|
||||
*/
|
||||
fun default(): ImageTagsDisplayMode {
|
||||
return Comma
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,10 @@
|
|||
package dev.nuculabs.imagetagger.ui.controls
|
||||
|
||||
import dev.nuculabs.imagetagger.core.AnalyzedImage
|
||||
import dev.nuculabs.imagetagger.ui.alerts.ErrorAlert
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.fxml.FXML
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.control.Button
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.TableView
|
||||
import javafx.scene.control.TextArea
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.image.ImageView
|
||||
|
@ -16,19 +12,16 @@ import javafx.scene.input.Clipboard
|
|||
import javafx.scene.input.ClipboardContent
|
||||
import javafx.scene.input.MouseEvent
|
||||
import javafx.scene.layout.HBox
|
||||
import javafx.scene.layout.VBox
|
||||
import org.apache.commons.lang3.SystemUtils
|
||||
import java.awt.Desktop
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.util.logging.Logger
|
||||
|
||||
|
||||
/**
|
||||
* This class is used to create a custom control for the image prediction entry.
|
||||
*/
|
||||
class ImageTagsEntryControl(private val image: AnalyzedImage) : HBox() {
|
||||
class ImageTagsEntryControl(private val imagePath: String, predictions: List<String>) : HBox() {
|
||||
private val logger: Logger = Logger.getLogger("ImageTagsEntryControl")
|
||||
|
||||
/**
|
||||
|
@ -43,16 +36,6 @@ class ImageTagsEntryControl(private val image: AnalyzedImage) : HBox() {
|
|||
@FXML
|
||||
private lateinit var predictedImageTags: TextArea
|
||||
|
||||
/**
|
||||
* Holds the tags.
|
||||
*/
|
||||
private var tags: List<String> = ArrayList()
|
||||
|
||||
/**
|
||||
* Sets the default image tags display mode.
|
||||
*/
|
||||
private var tagsDisplayMode: ImageTagsDisplayMode = ImageTagsDisplayMode.Comma
|
||||
|
||||
/**
|
||||
* The file name label.
|
||||
*/
|
||||
|
@ -65,15 +48,6 @@ class ImageTagsEntryControl(private val image: AnalyzedImage) : HBox() {
|
|||
@FXML
|
||||
private lateinit var copyTagsButton: Button
|
||||
|
||||
/**
|
||||
* Metadata related UI fields.
|
||||
*/
|
||||
@FXML
|
||||
private lateinit var metadataVbox: VBox
|
||||
|
||||
@FXML
|
||||
private lateinit var metadataTableView: TableView<ImageTagsEntryModel>
|
||||
|
||||
init {
|
||||
val resource = ImageTagsEntryControl::class.java.getResource("image-tags-entry.fxml")
|
||||
logger.fine("Using resource URL: $resource")
|
||||
|
@ -85,15 +59,8 @@ class ImageTagsEntryControl(private val image: AnalyzedImage) : HBox() {
|
|||
} catch (exception: IOException) {
|
||||
throw RuntimeException(exception)
|
||||
}
|
||||
setImage(image)
|
||||
if (image.hasError()) {
|
||||
setTags(listOf(image.errorMessage()))
|
||||
metadataVbox.isVisible = false
|
||||
} else {
|
||||
setTags(image.tags())
|
||||
setMetadata()
|
||||
}
|
||||
|
||||
setImage(imagePath)
|
||||
setText(predictions)
|
||||
|
||||
setupEventHandlers()
|
||||
}
|
||||
|
@ -120,83 +87,20 @@ class ImageTagsEntryControl(private val image: AnalyzedImage) : HBox() {
|
|||
*
|
||||
* @param predictions The prediction list.
|
||||
*/
|
||||
fun setTags(predictions: List<String>) {
|
||||
tags = predictions
|
||||
updateTags()
|
||||
private fun setText(predictions: List<String>) {
|
||||
predictedImageTags.text = predictions.joinToString { it }
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tags display mode.
|
||||
*
|
||||
* @param mode The image tags display mode.
|
||||
*/
|
||||
fun setTagsDisplayMode(mode: ImageTagsDisplayMode) {
|
||||
tagsDisplayMode = mode
|
||||
updateTags()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the tags text.
|
||||
*/
|
||||
private fun updateTags() {
|
||||
predictedImageTags.text = when (tagsDisplayMode) {
|
||||
ImageTagsDisplayMode.Comma -> {
|
||||
tags.joinToString { it }
|
||||
}
|
||||
ImageTagsDisplayMode.HashTags -> {
|
||||
tags.joinToString(separator = " ") {
|
||||
"#${it}"
|
||||
}
|
||||
}
|
||||
ImageTagsDisplayMode.Space -> {
|
||||
tags.joinToString(separator = " ") { it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets and displays the image metadata.
|
||||
*/
|
||||
private fun setMetadata() {
|
||||
val imageMetadata = image.metadata()
|
||||
val metadataValues = listOf(
|
||||
ImageTagsEntryModel("Author", imageMetadata.artist),
|
||||
ImageTagsEntryModel("Brand", imageMetadata.cameraBrand),
|
||||
ImageTagsEntryModel("Model", imageMetadata.cameraModel),
|
||||
ImageTagsEntryModel("Lens", imageMetadata.lensModel),
|
||||
ImageTagsEntryModel("ISO", imageMetadata.iso),
|
||||
ImageTagsEntryModel("Aperture", imageMetadata.aperture),
|
||||
ImageTagsEntryModel("Shutter Speed", imageMetadata.shutterSpeed),
|
||||
).filterNot { it.getValue() == "Unknown" }
|
||||
val data: ObservableList<ImageTagsEntryModel> = FXCollections.observableArrayList(
|
||||
metadataValues
|
||||
)
|
||||
if (data.size == 0) {
|
||||
metadataVbox.isVisible = false
|
||||
return
|
||||
}
|
||||
metadataTableView.items = data
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setter for setting the image.
|
||||
*/
|
||||
private fun setImage(analyzedImage: AnalyzedImage) {
|
||||
val file = File(analyzedImage.absolutePath())
|
||||
if (analyzedImage.hasError()) {
|
||||
fileNameLabel.text = "Failed: ${file.name}"
|
||||
fileNameLabel.styleClass.add("errorLabel")
|
||||
imageView.image = Image(this.javaClass.getResourceAsStream("images/failed.png"))
|
||||
private fun setImage(imagePath: String) {
|
||||
val file = File(imagePath)
|
||||
file.inputStream().use {
|
||||
imageView.image = Image(it, 244.0, 244.0, true, true)
|
||||
imageView.isCache = true
|
||||
} else {
|
||||
file.inputStream().use {
|
||||
imageView.image = Image(it, 244.0, 244.0, true, true)
|
||||
imageView.isCache = true
|
||||
}
|
||||
fileNameLabel.text = "File: ${file.name}"
|
||||
}
|
||||
fileNameLabel.text = "File: ${file.name}"
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -204,22 +108,14 @@ class ImageTagsEntryControl(private val image: AnalyzedImage) : HBox() {
|
|||
* If the operation fails it will display an error alert.
|
||||
*/
|
||||
fun onOpenImageClick() {
|
||||
if (image.hasError()) {
|
||||
return
|
||||
}
|
||||
if (SystemUtils.IS_OS_LINUX) {
|
||||
val status = Runtime.getRuntime().exec(arrayOf("xdg-open", image.absolutePath()))
|
||||
logger.fine(InputStreamReader(status.errorStream).readText())
|
||||
} else {
|
||||
if (Desktop.isDesktopSupported()) {
|
||||
val desktop = Desktop.getDesktop()
|
||||
if (desktop.isSupported(Desktop.Action.OPEN)) {
|
||||
desktop.open(File(image.absolutePath()))
|
||||
}
|
||||
} else {
|
||||
logger.severe("Cannot open image ${image.absolutePath()}. Desktop action not supported!")
|
||||
ErrorAlert("Can't open file: ${image.absolutePath()}\nOperation is not supported!")
|
||||
if (Desktop.isDesktopSupported()) {
|
||||
val desktop = Desktop.getDesktop()
|
||||
if (desktop.isSupported(Desktop.Action.OPEN)) {
|
||||
desktop.open(File(imagePath))
|
||||
}
|
||||
} else {
|
||||
logger.severe("Cannot open image $imagePath. Desktop action not supported!")
|
||||
ErrorAlert("Can't open file: $imagePath\nOperation is not supported!")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
@file:Suppress("unused")
|
||||
|
||||
package dev.nuculabs.imagetagger.ui.controls
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.beans.property.StringProperty
|
||||
|
||||
/**
|
||||
* ImageTagsEntryModel represents a metadata and it's associated value.
|
||||
*/
|
||||
class ImageTagsEntryModel(metadata: String, value: String) {
|
||||
private val metadata: StringProperty = SimpleStringProperty(metadata)
|
||||
private val value: StringProperty = SimpleStringProperty(value)
|
||||
|
||||
fun getMetadata(): String {
|
||||
return metadata.get()
|
||||
}
|
||||
|
||||
fun setMetadata(metadata: String) {
|
||||
this.metadata.set(metadata)
|
||||
}
|
||||
|
||||
fun metadataProperty(): StringProperty {
|
||||
return metadata
|
||||
}
|
||||
|
||||
fun getValue(): String {
|
||||
return value.get()
|
||||
}
|
||||
|
||||
fun setValue(value: String) {
|
||||
this.value.set(value)
|
||||
}
|
||||
|
||||
fun valueProperty(): StringProperty {
|
||||
return value
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@ import javafx.fxml.FXMLLoader
|
|||
import javafx.scene.control.Button
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.layout.HBox
|
||||
import org.apache.commons.lang3.SystemUtils
|
||||
import java.awt.Desktop
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
@ -46,9 +45,6 @@ class ImageTagsSessionHeader : HBox() {
|
|||
}
|
||||
|
||||
updateHeader(0)
|
||||
openDirectoryButton.setOnAction {
|
||||
openDirectory()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,30 +61,25 @@ class ImageTagsSessionHeader : HBox() {
|
|||
|
||||
// update header title
|
||||
val shortDate = dateTimeProvider.getTodayShortDate()
|
||||
val shortTime = dateTimeProvider.getTodayTime()
|
||||
this.title.text = "$shortDate - $shortTime - ($numberOfImages Images)"
|
||||
this.title.text = "$shortDate ($numberOfImages Images)"
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the directory in the user's Desktop.
|
||||
*/
|
||||
fun openDirectory() {
|
||||
directoryPath?.let {
|
||||
if (SystemUtils.IS_OS_LINUX) {
|
||||
Runtime.getRuntime().exec("xdg-open $directoryPath")
|
||||
} else {
|
||||
if (Desktop.isDesktopSupported()) {
|
||||
val desktop = Desktop.getDesktop()
|
||||
if (desktop.isSupported(Desktop.Action.OPEN)) {
|
||||
desktop.open(File(it))
|
||||
} else {
|
||||
logger.severe("Cannot open image directory $it. Desktop action not supported!")
|
||||
ErrorAlert("Can't open file: $it\nOperation is not supported!")
|
||||
}
|
||||
this.directoryPath?.let {
|
||||
if (Desktop.isDesktopSupported()) {
|
||||
val desktop = Desktop.getDesktop()
|
||||
if (desktop.isSupported(Desktop.Action.OPEN)) {
|
||||
desktop.open(File(it))
|
||||
} else {
|
||||
logger.severe("Cannot open image directory $it. Desktop action not supported!")
|
||||
ErrorAlert("Can't open file: $it\nOperation is not supported!")
|
||||
}
|
||||
} else {
|
||||
logger.severe("Cannot open image directory $it. Desktop action not supported!")
|
||||
ErrorAlert("Can't open file: $it\nOperation is not supported!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package dev.nuculabs.imagetagger.ui.controls.programatic
|
||||
|
||||
import dev.nuculabs.imagetagger.ui.MainPageController
|
||||
import dev.nuculabs.imagetagger.ui.pages.AboutPage
|
||||
import javafx.scene.control.Menu
|
||||
import javafx.scene.control.MenuBar
|
||||
import javafx.scene.control.MenuItem
|
||||
|
||||
/**
|
||||
* Used as the application menu bar.
|
||||
*/
|
||||
class ApplicationMenuBar(private val mainPageController: MainPageController) : MenuBar() {
|
||||
private val fileMenu = Menu("File")
|
||||
private val aboutMenu = Menu("About")
|
||||
|
||||
init {
|
||||
useSystemMenuBarProperty().set(true)
|
||||
menus.addAll(fileMenu, aboutMenu)
|
||||
setupFileMenu()
|
||||
setupAboutMenu()
|
||||
}
|
||||
|
||||
private fun setupAboutMenu() {
|
||||
val aboutMenuItem = MenuItem("About")
|
||||
aboutMenuItem.setOnAction {
|
||||
AboutPage.show()
|
||||
}
|
||||
aboutMenu.items.add(aboutMenuItem)
|
||||
}
|
||||
|
||||
private fun setupFileMenu() {
|
||||
val tagImagesMenuItem = MenuItem("Tag Images")
|
||||
tagImagesMenuItem.setOnAction {
|
||||
mainPageController.onTagImagesButtonClick()
|
||||
}
|
||||
fileMenu.items.add(tagImagesMenuItem)
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ import javafx.scene.Parent
|
|||
import javafx.scene.Scene
|
||||
import javafx.scene.control.Button
|
||||
import javafx.stage.Stage
|
||||
import org.apache.commons.lang3.SystemUtils
|
||||
import java.awt.Desktop
|
||||
import java.net.URL
|
||||
|
||||
|
@ -17,20 +16,12 @@ class AboutPage {
|
|||
|
||||
@FXML
|
||||
fun openBlog() {
|
||||
if (SystemUtils.IS_OS_LINUX) {
|
||||
Runtime.getRuntime().exec("xdg-open https://blog.nuculabs.dev")
|
||||
} else {
|
||||
Desktop.getDesktop().browse(URL("https://blog.nuculabs.dev").toURI())
|
||||
}
|
||||
Desktop.getDesktop().browse(URL("https://blog.nuculabs.dev").toURI())
|
||||
}
|
||||
|
||||
@FXML
|
||||
fun openGithub() {
|
||||
if (SystemUtils.IS_OS_LINUX) {
|
||||
Runtime.getRuntime().exec("xdg-open https://github.com/dnutiu/ImageTagger")
|
||||
} else {
|
||||
Desktop.getDesktop().browse(URL("https://github.com/dnutiu/ImageTagger").toURI())
|
||||
}
|
||||
Desktop.getDesktop().browse(URL("https://github.com/dnutiu/ImageTagger").toURI())
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
|
|
@ -9,8 +9,6 @@ interface IDateTimeProvider {
|
|||
* Returns today's short date. For example: 09 April 2024
|
||||
*/
|
||||
fun getTodayShortDate(): String
|
||||
|
||||
fun getTodayTime(): String
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,13 +24,4 @@ class DateTimeProvider : IDateTimeProvider {
|
|||
val date = Date()
|
||||
return dateFormat.format(date)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns today's short time. Example: 15:30
|
||||
*/
|
||||
override fun getTodayTime(): String {
|
||||
val dateFormat: DateFormat = SimpleDateFormat("HH:mm", Locale.ENGLISH)
|
||||
val date = Date()
|
||||
return dateFormat.format(date)
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<fx:root type="javafx.scene.control.MenuBar" xmlns:fx="http://javafx.com/fxml">
|
||||
<Menu text="File">
|
||||
<MenuItem text="Tag Images" onAction="#fileMenuTagImages"/>
|
||||
</Menu>
|
||||
<Menu text="About">
|
||||
<MenuItem text="About" onAction="#aboutMenuShowAbout"/>
|
||||
</Menu>
|
||||
</fx:root>
|
|
@ -1,5 +0,0 @@
|
|||
|
||||
.errorLabel {
|
||||
-fx-text-background-color: red;
|
||||
-fx-font-family: "Inter Bold"
|
||||
}
|
|
@ -6,23 +6,17 @@
|
|||
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
||||
<?import javafx.scene.control.cell.PropertyValueFactory?>
|
||||
<fx:root type="javafx.scene.layout.HBox" xmlns:fx="http://javafx.com/fxml" stylesheets="@image-tags-entry.css"
|
||||
minHeight="250">
|
||||
<StackPane minWidth="244" prefWidth="244" prefHeight="244">
|
||||
<fx:root type="javafx.scene.layout.HBox" xmlns:fx="http://javafx.com/fxml">
|
||||
<StackPane style="-fx-background-color: lightgray;" prefWidth="244" prefHeight="244">
|
||||
<ImageView fx:id="imageView"/>
|
||||
</StackPane>
|
||||
<VBox minWidth="300" >
|
||||
<VBox>
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
|
||||
</padding>
|
||||
<HBox>
|
||||
<VBox>
|
||||
<Label fx:id="fileNameLabel"/>
|
||||
<Label text="Tags:"/>
|
||||
<TextArea fx:id="predictedImageTags" editable="false" wrapText="true" prefColumnCount="20"/>
|
||||
</VBox>
|
||||
</HBox>
|
||||
<Label fx:id="fileNameLabel"/>
|
||||
<Label text="Predicted tags:"/>
|
||||
<TextArea fx:id="predictedImageTags" editable="false" wrapText="true" prefColumnCount="20"/>
|
||||
<HBox>
|
||||
<padding>
|
||||
<Insets top="5.0"/>
|
||||
|
@ -34,20 +28,5 @@
|
|||
</Button>
|
||||
</HBox>
|
||||
</VBox>
|
||||
<VBox fx:id="metadataVbox" VBox.vgrow="ALWAYS">
|
||||
<TableView fx:id="metadataTableView" minWidth="400" maxWidth="Infinity" VBox.vgrow="ALWAYS">
|
||||
<columns>
|
||||
<TableColumn text="Metadata">
|
||||
<cellValueFactory>
|
||||
<PropertyValueFactory property="metadata"/>
|
||||
</cellValueFactory>
|
||||
</TableColumn>
|
||||
<TableColumn text="Value">
|
||||
<cellValueFactory>
|
||||
<PropertyValueFactory property="value"/>
|
||||
</cellValueFactory>
|
||||
</TableColumn>
|
||||
</columns>
|
||||
</TableView>
|
||||
</VBox>
|
||||
|
||||
</fx:root>
|
|
@ -12,7 +12,7 @@
|
|||
</padding>
|
||||
<Label fx:id="title" text="9 Apr. 2024" styleClass="sessionTitle"/>
|
||||
<Pane HBox.hgrow="ALWAYS"/>
|
||||
<Button fx:id="openDirectoryButton" text="Open Folder">
|
||||
<Button fx:id="openDirectoryButton" onAction="#openDirectory" text="Open Directory">
|
||||
<tooltip>
|
||||
<Tooltip text="Open directory of the images"/>
|
||||
</tooltip>
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 33 KiB |
Binary file not shown.
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 4.6 KiB |
|
@ -5,40 +5,31 @@
|
|||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
||||
<?import dev.nuculabs.imagetagger.ui.controls.ApplicationMenuBar?>
|
||||
<BorderPane xmlns:fx="http://javafx.com/fxml/1"
|
||||
<BorderPane prefHeight="537.0" prefWidth="725.0"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
xmlns="http://javafx.com/javafx/17.0.2-ea"
|
||||
fx:controller="dev.nuculabs.imagetagger.ui.MainPageController"
|
||||
stylesheets="@main-window-view.css"
|
||||
>
|
||||
<padding>
|
||||
<Insets bottom="20.0" left="30.0" right="30.0" top="20.0"/>
|
||||
</padding>
|
||||
<top>
|
||||
<VBox>
|
||||
<ApplicationMenuBar/>
|
||||
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||
<padding>
|
||||
<Insets bottom="5" left="5" right="5"/>
|
||||
</padding>
|
||||
<Button fx:id="tagImagesButton" onAction="#onTagImagesButtonClick" text="Tag Images">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="far-images" iconSize="16"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Separator orientation="VERTICAL" style="-fx-padding: 10px"/>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<ChoiceBox fx:id="tagsDisplayModeSelection">
|
||||
<tooltip>
|
||||
<Tooltip text="Select how tags are displayed"/>
|
||||
</tooltip>
|
||||
</ChoiceBox>
|
||||
</HBox>
|
||||
</VBox>
|
||||
<HBox alignment="CENTER_LEFT">
|
||||
<padding>
|
||||
<Insets bottom="5" left="5" right="5"/>
|
||||
</padding>
|
||||
<Button onAction="#onTagImagesButtonClick" text="Tag Images">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="far-images" iconSize="16"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Separator orientation="VERTICAL" style="-fx-padding: 10px"/>
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<ScrollPane fitToWidth="true" fitToHeight="true">
|
||||
<VBox fx:id="verticalBox">
|
||||
<padding>
|
||||
<Insets left="10" right="10"/>
|
||||
</padding>
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
</center>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
fx:controller="dev.nuculabs.imagetagger.ui.pages.AboutPage"
|
||||
stylesheets="@about-page.css"
|
||||
>
|
||||
<VBox prefHeight="350.0" maxWidth="350" spacing="10">
|
||||
<VBox prefHeight="250.0" maxWidth="350" spacing="10">
|
||||
<padding>
|
||||
<Insets top="25" bottom="25" right="15" left="15"/>
|
||||
</padding>
|
||||
|
@ -25,8 +25,6 @@
|
|||
<Image
|
||||
url="@../image-analysis.png"
|
||||
backgroundLoading="true"
|
||||
requestedWidth="64"
|
||||
requestedHeight="64"
|
||||
/>
|
||||
</ImageView>
|
||||
</HBox>
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# NucuLabs.dev's Image Tagger Application
|
||||
SCRIPT_NAME=\$(basename "\$0")
|
||||
APP_NAME=\${SCRIPT_NAME%.sh}
|
||||
|
||||
DIR="\${0%/*}"
|
||||
|
||||
<% if ( System.properties['BADASS_CDS_ARCHIVE_FILE_LINUX'] ) { %>
|
||||
CDS_ARCHIVE_FILE="<%= System.properties['BADASS_CDS_ARCHIVE_FILE_LINUX'] %>"
|
||||
CDS_JVM_OPTS="-XX:ArchiveClassesAtExit=\$CDS_ARCHIVE_FILE"
|
||||
if [ -f "\$CDS_ARCHIVE_FILE" ]; then
|
||||
CDS_JVM_OPTS="-XX:SharedArchiveFile=\$CDS_ARCHIVE_FILE"
|
||||
fi
|
||||
<% } %>
|
||||
|
||||
# Make jexec and jspawnhelper executable otherwise they won't work properly.
|
||||
chmod +x \$DIR/../lib/jexec
|
||||
chmod +x \$DIR/../lib/jspawnhelper
|
||||
|
||||
"\$DIR/java" \$CDS_JVM_OPTS ${jvmArgs} -p "\$DIR/../app" -m ${moduleName}/${mainClassName} ${args} "\$@"
|
49
readme.md
49
readme.md
|
@ -1,7 +1,6 @@
|
|||
# ![](./docs/image-analysis.png) Image Tagger
|
||||
# ![](./docs/image-analysis.png) Image Tagger
|
||||
|
||||
Image Tagger is a simple software application for predicting an image's keywords using a deep learning model based on
|
||||
resnet.
|
||||
Image Tagger is a simple software application for predicting an image's keywords using a deep learning model based on resnet.
|
||||
|
||||
It allows photographers to automate the image tagging process. 📸
|
||||
|
||||
|
@ -11,68 +10,38 @@ It allows photographers to automate the image tagging process. 📸
|
|||
|
||||
1. Download a release from the release page.
|
||||
2. Unzip the release.
|
||||
3. Run `ImageTagger\image\bin\ImageTagger`.
|
||||
3. Run `ImageTagger\image\bin\ImageTagger`.
|
||||
|
||||
![./docs/application.png](./docs/application.png)
|
||||
|
||||
Photo credit: [https://unsplash.com/@ndcphoto](https://unsplash.com/@ndcphoto)
|
||||
|
||||
Alternatively see [Flatpak](./flatpak/readme.md) installation instructions.
|
||||
|
||||
## Development
|
||||
|
||||
If you want to build the application yourself, you will need Java 17 JDK and the
|
||||
If you want to build the application yourself, you will need Java 17 JDK and the
|
||||
AI models available in the AIModels release.
|
||||
|
||||
The release archive is in the [releases page](https://github.com/dnutiu/ImageTagger/releases).
|
||||
|
||||
Note: On Linux desktop related features (opening images, folders) are handled
|
||||
via [xdg-open](https://linux.die.net/man/1/xdg-open).
|
||||
|
||||
### Building and Running from source
|
||||
|
||||
To build from source you will need Java 17 JDK and Gradle.
|
||||
|
||||
Due to some GitHub limitations that do not allow me to upload large files, you'll need to download the AIModels
|
||||
zip file which contains the deep learning models and place them into the
|
||||
zip file which contains the deep learning models and place them into the
|
||||
`ImageTagger/img-ai/src/main/resources/dev/nuculabs/imagetagger/ai/` path.
|
||||
|
||||
To build the project run:
|
||||
|
||||
```bash
|
||||
gradlew build
|
||||
gradle build
|
||||
```
|
||||
|
||||
To run:
|
||||
|
||||
```bash
|
||||
gradlew run
|
||||
```
|
||||
|
||||
### Building the Flatpak
|
||||
|
||||
To build the Flatpak run the following commands:
|
||||
|
||||
```shell
|
||||
cd flatpak
|
||||
./build.sh
|
||||
```
|
||||
|
||||
It will build the flatpak using the latest sources from this repo.
|
||||
|
||||
### Building a package (Fedora Example)
|
||||
|
||||
To build a package run
|
||||
|
||||
```shell
|
||||
gradle jpackage <<< "--type rpm"
|
||||
```
|
||||
|
||||
To install and run the application:
|
||||
|
||||
```shell
|
||||
dnf install ./img-ui/build/jpackage/imagetagger-1.0-1.x86_64.rpm
|
||||
/opt/imagetagger/bin/ImageTagger
|
||||
gradle run
|
||||
```
|
||||
|
||||
# Blog
|
||||
|
@ -81,6 +50,4 @@ You can visit my tech blog at [https://blog.nuculabs.dev](https://blog.nuculabs.
|
|||
|
||||
# Credits
|
||||
|
||||
- Icons: <a href="https://www.flaticon.com/free-icons/image-analysis" title="image analysis icons">Image analysis icons
|
||||
created by Dewi Sari - Flaticon</a>
|
||||
|
||||
- Icons: <a href="https://www.flaticon.com/free-icons/image-analysis" title="image analysis icons">Image analysis icons created by Dewi Sari - Flaticon</a>
|
7
settings.gradle
Normal file
7
settings.gradle
Normal file
|
@ -0,0 +1,7 @@
|
|||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0'
|
||||
}
|
||||
rootProject.name = "ImageTagger-Solution"
|
||||
include "img-ui"
|
||||
include 'img-ai'
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
plugins {
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
|
||||
}
|
||||
rootProject.name = "ImageTagger"
|
||||
include("img-ui")
|
||||
include("img-ai")
|
||||
include("img-core")
|
||||
|
Loading…
Reference in a new issue