diff --git a/Build.bat b/Build.bat index cf8a0fa..fe11707 100644 --- a/Build.bat +++ b/Build.bat @@ -1,24 +1,40 @@ @echo off -echo === Building RadioDJ-TunaBridge === +echo. +echo === ๐Ÿš€ Building RadioDJ-TunaBridge with Java 17 === +echo. -REM Clean previous build +REM Manually set Java 17 location +set "JAVA_HOME=C:\java_runtime\17" +set "PATH=%JAVA_HOME%\bin;%PATH%" + +REM Step 1: Clean project +echo ๐Ÿงน Cleaning... mvn clean - -REM Compile and package the JAR -mvn package - IF %ERRORLEVEL% NEQ 0 ( - echo โŒ Build failed. + echo โŒ Maven clean failed. pause exit /b %ERRORLEVEL% ) -REM Copy final JAR to top-level folder (optional) -IF EXIST target\radiodj-tuna-bridge-1.0-SNAPSHOT.jar ( - copy target\radiodj-tuna-bridge-1.0-SNAPSHOT.jar radiodj-tuna-bridge.jar >nul - echo โœ… Build complete. Output: radiodj-tuna-bridge.jar -) ELSE ( - echo โŒ JAR not found! +REM Step 2: Compile and package +echo ๐Ÿ”จ Compiling... +mvn package +IF %ERRORLEVEL% NEQ 0 ( + echo โŒ Maven build failed. + pause + exit /b %ERRORLEVEL% ) +REM Step 3: Copy JAR to top level +set "TARGET_JAR=target\radiodj-tuna-bridge-1.0-SNAPSHOT.jar" +set "OUTPUT_JAR=radiodj-tuna-bridge.jar" + +IF EXIST "%TARGET_JAR%" ( + copy "%TARGET_JAR%" "%OUTPUT_JAR%" >nul + echo โœ… JAR ready: %OUTPUT_JAR% +) ELSE ( + echo โŒ No JAR found in target folder. +) + +echo. pause \ No newline at end of file diff --git a/pom.xml b/pom.xml index c5a6201..b5a9bee 100644 --- a/pom.xml +++ b/pom.xml @@ -1,42 +1,64 @@ - - - 4.0.0 + - com.example - radiodj-tuna-bridge - 1.0-SNAPSHOT + 4.0.0 + com.minster586 + radiodj-tuna-bridge + 1.0-SNAPSHOT + RadioDJ Tuna Bridge - - 17 - 17 - + + 17 + 17 + UTF-8 + - + + + + org.yaml + snakeyaml + 2.2 + - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.17.0 - + + + xerces + xercesImpl + 2.12.2 + + - - - org.yaml - snakeyaml - 2.2 - - - - - com.dorkbox - SystemTray - 3.17 - - - + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.6.0 + + + jar-with-dependencies + + + + com.minster586.radiodjtuna.MainApp + + + + + + assemble-all + package + + single + + + + + + \ No newline at end of file diff --git a/src/main/java/com/minster586/radiodjtuna/Config.java b/src/main/java/com/minster586/radiodjtuna/Config.java index aa27636..e977a71 100644 --- a/src/main/java/com/minster586/radiodjtuna/Config.java +++ b/src/main/java/com/minster586/radiodjtuna/Config.java @@ -3,9 +3,21 @@ package com.minster586.radiodjtuna; public class Config { private String radioDjApiUrl; private String albumArtDirectory; - private int updateIntervalSeconds; private String fallbackArtFilename; + private int updateIntervalSeconds; + // โœ… Required no-arg constructor for SnakeYAML + public Config() {} + + // โœ… Full constructor for manual creation + public Config(String radioDjApiUrl, String albumArtDirectory, String fallbackArtFilename, int updateIntervalSeconds) { + this.radioDjApiUrl = radioDjApiUrl; + this.albumArtDirectory = albumArtDirectory; + this.fallbackArtFilename = fallbackArtFilename; + this.updateIntervalSeconds = updateIntervalSeconds; + } + + // โœ… Getters and Setters public String getRadioDjApiUrl() { return radioDjApiUrl; } @@ -22,14 +34,6 @@ public class Config { this.albumArtDirectory = albumArtDirectory; } - public int getUpdateIntervalSeconds() { - return updateIntervalSeconds; - } - - public void setUpdateIntervalSeconds(int updateIntervalSeconds) { - this.updateIntervalSeconds = updateIntervalSeconds; - } - public String getFallbackArtFilename() { return fallbackArtFilename; } @@ -37,4 +41,12 @@ public class Config { public void setFallbackArtFilename(String fallbackArtFilename) { this.fallbackArtFilename = fallbackArtFilename; } + + public int getUpdateIntervalSeconds() { + return updateIntervalSeconds; + } + + public void setUpdateIntervalSeconds(int updateIntervalSeconds) { + this.updateIntervalSeconds = updateIntervalSeconds; + } } \ No newline at end of file diff --git a/src/main/java/com/minster586/radiodjtuna/ConfigManager.java b/src/main/java/com/minster586/radiodjtuna/ConfigManager.java index de21ef6..499bd92 100644 --- a/src/main/java/com/minster586/radiodjtuna/ConfigManager.java +++ b/src/main/java/com/minster586/radiodjtuna/ConfigManager.java @@ -1,14 +1,21 @@ package com.minster586.radiodjtuna; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.Constructor; - import java.io.File; +import java.io.FileInputStream; import java.io.FileWriter; -import java.io.InputStream; +import java.io.IOException; import java.nio.file.Path; import java.util.Scanner; +import java.awt.image.BufferedImage; + +import javax.imageio.ImageIO; + +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.constructor.Constructor; +import org.yaml.snakeyaml.representer.Representer; +import org.yaml.snakeyaml.nodes.Tag; public class ConfigManager { @@ -19,56 +26,90 @@ public class ConfigManager { } public Config loadOrCreateConfig() { - File configFile = configPath.toFile(); - if (!configFile.exists()) { - System.out.println("โš™๏ธ Creating new config.yml..."); - Config newConfig = promptForConfig(); - saveConfig(newConfig); - return newConfig; + File file = configPath.toFile(); + if (file.exists()) { + try (FileInputStream in = new FileInputStream(file)) { + Yaml yaml = new Yaml(new Constructor(Config.class, new LoaderOptions())); + return yaml.load(in); + } catch (Exception e) { + System.err.println("โŒ Failed to load config.yml: " + e.getMessage()); + } } - try (InputStream input = java.nio.file.Files.newInputStream(configPath)) { - Yaml yaml = new Yaml(new Constructor(Config.class)); - Config loaded = yaml.load(input); - System.out.println("โœ… Config loaded successfully."); - return loaded; - } catch (Exception e) { - System.err.println("โŒ Failed to load config: " + e.getMessage()); - System.exit(1); - return null; // unreachable, but satisfies compiler - } + System.out.println("๐Ÿ“ Creating new config.yml..."); + Config config = promptUserConfig(); + saveConfig(config); + convertFallbackToPngIfNecessary(config); + return config; } - private Config promptForConfig() { + private Config promptUserConfig() { Scanner scanner = new Scanner(System.in); - Config config = new Config(); - System.out.print("๐Ÿ”— Enter RadioDJ API URL (e.g. http://localhost:1234/playing): "); - config.setRadioDjApiUrl(scanner.nextLine().trim()); + System.out.print("๐Ÿ”— Enter RadioDJ API URL (e.g., http://localhost:1234/playing): "); + String apiUrl = scanner.nextLine().trim(); - System.out.print("๐Ÿ“ Enter full path to RadioDJ album art folder: "); - config.setAlbumArtDirectory(scanner.nextLine().trim()); + System.out.print("๐Ÿ–ผ๏ธ Enter path to album art folder: "); + String artDir = scanner.nextLine().trim(); - System.out.print("โฑ๏ธ Enter update interval (seconds): "); - config.setUpdateIntervalSeconds(Integer.parseInt(scanner.nextLine().trim())); + System.out.print("๐Ÿงฉ Enter filename of fallback album art (e.g., fallback.jpg): "); + String fallback = scanner.nextLine().trim(); - System.out.print("๐Ÿ–ผ๏ธ Enter fallback album art filename (e.g. fallback-art.jpg): "); - config.setFallbackArtFilename(scanner.nextLine().trim()); + int interval = promptForInterval(scanner); - return config; + return new Config(apiUrl, artDir, fallback, interval); + } + + private int promptForInterval(Scanner scanner) { + while (true) { + System.out.print("โฑ๏ธ Enter metadata update interval (seconds): "); + String input = scanner.nextLine().trim(); + if (!input.isEmpty()) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + System.out.println("โš ๏ธ Please enter a valid number."); + } + } else { + System.out.println("โš ๏ธ This field is required."); + } + } } private void saveConfig(Config config) { try (FileWriter writer = new FileWriter(configPath.toFile())) { DumperOptions options = new DumperOptions(); - options.setIndent(2); - options.setPrettyFlow(true); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - Yaml yaml = new Yaml(options); + options.setPrettyFlow(true); + + Representer representer = new Representer(options); + representer.addClassTag(Config.class, Tag.MAP); // ๐Ÿ‘ˆ Prevents !!class tag + + Yaml yaml = new Yaml(representer, options); yaml.dump(config, writer); - System.out.println("๐Ÿ’พ Config saved to " + configPath); - } catch (Exception e) { - System.err.println("โŒ Failed to save config: " + e.getMessage()); + System.out.println("โœ… Config saved: " + configPath.getFileName()); + } catch (IOException e) { + System.err.println("โŒ Failed to save config.yml: " + e.getMessage()); + } + } + + private void convertFallbackToPngIfNecessary(Config config) { + String fallback = config.getFallbackArtFilename(); + if (fallback.toLowerCase().endsWith(".jpg")) { + File jpgFile = new File(config.getAlbumArtDirectory(), fallback); + File pngFile = new File(config.getAlbumArtDirectory(), fallback.replaceAll("(?i)\\.jpg$", ".png")); + + try { + BufferedImage image = ImageIO.read(jpgFile); + if (image != null) { + ImageIO.write(image, "png", pngFile); + System.out.println("๐ŸŽจ Converted fallback image to PNG: " + pngFile.getName()); + config.setFallbackArtFilename(pngFile.getName()); + saveConfig(config); // Resave with updated filename + } + } catch (IOException e) { + System.err.println("โŒ Failed to convert fallback image: " + e.getMessage()); + } } } } \ No newline at end of file diff --git a/src/main/java/com/minster586/radiodjtuna/MainApp.java b/src/main/java/com/minster586/radiodjtuna/MainApp.java index da53824..67051cf 100644 --- a/src/main/java/com/minster586/radiodjtuna/MainApp.java +++ b/src/main/java/com/minster586/radiodjtuna/MainApp.java @@ -3,48 +3,77 @@ package com.minster586.radiodjtuna; import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Scanner; +import java.util.concurrent.*; public class MainApp { - // Base paths determined at runtime private static Path jarDir; private static Path outputDir; private static Path configFile; private static Path cacheFile; - // Core components - private static Config config; - private static MetadataCacheManager cacheManager; - private static TrackInfo lastPlayed; - public static void main(String[] args) { System.out.println("๐Ÿ”Š RadioDJ Tuna Bridge starting up..."); initializePaths(); ensureOutputFolder(); - // Load or create config ConfigManager configManager = new ConfigManager(configFile); - config = configManager.loadOrCreateConfig(); + Config config = configManager.loadOrCreateConfig(); - // Set up metadata cache manager - cacheManager = new MetadataCacheManager(cacheFile); - lastPlayed = cacheManager.loadCache(); + MetadataCacheManager cacheManager = new MetadataCacheManager(cacheFile); + final TrackInfo[] lastPlayed = { cacheManager.loadCache() }; - if (lastPlayed != null) { - System.out.println("๐Ÿ—ƒ๏ธ Loaded cached track: " + lastPlayed.getTitle()); - } else { - System.out.println("๐Ÿ“ No metadata cache found yet."); + RadioDjApiClient apiClient = new RadioDjApiClient(config.getRadioDjApiUrl()); + OutputManager outputManager = new OutputManager( + outputDir, + config.getAlbumArtDirectory(), + config.getFallbackArtFilename() + ); + + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + Runnable pollingTask = () -> { + try { + TrackInfo current = apiClient.fetchNowPlaying(); + if (current == null || current.getTitle().isBlank()) { + System.out.println("โš ๏ธ No track data received."); + return; + } + + if (lastPlayed[0] == null || !current.equals(lastPlayed[0])) { + System.out.println("๐ŸŽถ New track detected: " + current.getTitle()); + outputManager.writeOutputFiles(current); + cacheManager.saveCache(current); + lastPlayed[0] = current; + } else { + System.out.println("โณ No change โ€” current track: " + current.getTitle()); + } + + } catch (Exception e) { + System.err.println("๐Ÿšซ Failed to fetch or process now playing data: " + e.getMessage()); + } + }; + + int interval = Math.max(config.getUpdateIntervalSeconds(), 3); + scheduler.scheduleAtFixedRate(pollingTask, 0, interval, TimeUnit.SECONDS); + + System.out.println("โŒจ๏ธ Press 0 + Enter to stop the app..."); + + try (Scanner scanner = new Scanner(System.in)) { + while (true) { + String input = scanner.nextLine().trim(); + if ("0".equals(input)) { + System.out.println("๐Ÿ›‘ Shutdown requested. Closing..."); + scheduler.shutdownNow(); + break; + } + } + } catch (Exception e) { + System.err.println("โŒ Error reading input: " + e.getMessage()); } - // TODO: Poll RadioDJ API using config.getRadioDjApiUrl() - // TODO: Compare result to lastPlayed using lastPlayed.equals() - // TODO: If different, write new metadata to output folder - // TODO: Save new metadata to last-played.xml - // TODO: Handle album art copy or fallback - // TODO: Show toast if API is unreachable - - System.out.println("โœ… System initialized. Ready for polling phase."); + System.exit(0); } private static void initializePaths() { diff --git a/src/main/java/com/minster586/radiodjtuna/OutputManager.java b/src/main/java/com/minster586/radiodjtuna/OutputManager.java index c8652d6..d6536c9 100644 --- a/src/main/java/com/minster586/radiodjtuna/OutputManager.java +++ b/src/main/java/com/minster586/radiodjtuna/OutputManager.java @@ -3,6 +3,9 @@ package com.minster586.radiodjtuna; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.*; +import java.awt.image.BufferedImage; + +import javax.imageio.ImageIO; public class OutputManager { @@ -21,9 +24,14 @@ public class OutputManager { writeTextFile("track-title.txt", track.getTitle()); writeTextFile("track-artist.txt", track.getArtist()); writeTextFile("track-album.txt", track.getAlbum()); - copyAlbumArt(track.getAlbumArt()); } catch (IOException e) { - System.err.println("โŒ Failed writing output files: " + e.getMessage()); + System.err.println("โŒ Failed writing metadata text files: " + e.getMessage()); + } + + try { + convertAlbumArtToPng(track.getAlbumArt()); + } catch (Exception e) { + System.err.println("โŒ Failed converting album art: " + e.getMessage()); } } @@ -31,45 +39,57 @@ public class OutputManager { Path file = outputDir.resolve(fileName); Files.writeString(file, content != null ? content : "", StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + System.out.println("๐Ÿ“„ Wrote file: " + file.getFileName()); } - private void copyAlbumArt(String albumArtFileName) { - Path destination = outputDir.resolve("album-art.jpg"); + private void convertAlbumArtToPng(String albumArtFileName) { + Path destination = outputDir.resolve("album-art.png"); if (albumArtFileName == null || albumArtFileName.isBlank()) { System.out.println("โ„น๏ธ No album art in tag, using fallback."); - copyFallback(destination); + convertFallbackToPng(destination); return; } Path sourceFile = albumArtSourceFolder.resolve(albumArtFileName); - if (!Files.exists(sourceFile)) { System.out.println("โš ๏ธ Album art not found: " + sourceFile.getFileName() + ", using fallback."); - copyFallback(destination); + convertFallbackToPng(destination); return; } try { - Files.copy(sourceFile, destination, StandardCopyOption.REPLACE_EXISTING); - System.out.println("๐ŸŽจ Album art updated: " + albumArtFileName); + BufferedImage image = ImageIO.read(sourceFile.toFile()); + if (image != null) { + ImageIO.write(image, "png", destination.toFile()); + System.out.println("๐ŸŽจ Album art converted to PNG: " + destination.getFileName()); + } else { + System.out.println("โš ๏ธ Could not read album art image, using fallback."); + convertFallbackToPng(destination); + } } catch (IOException e) { - System.err.println("โŒ Failed copying album art: " + e.getMessage()); - copyFallback(destination); + System.err.println("โŒ Failed converting album art: " + e.getMessage()); + convertFallbackToPng(destination); } } - private void copyFallback(Path destination) { + private void convertFallbackToPng(Path destination) { Path fallbackSource = outputDir.resolve(fallbackArtFilename); - if (Files.exists(fallbackSource)) { - try { - Files.copy(fallbackSource, destination, StandardCopyOption.REPLACE_EXISTING); - System.out.println("๐Ÿ–ผ๏ธ Fallback album art used."); - } catch (IOException e) { - System.err.println("โŒ Failed to copy fallback art: " + e.getMessage()); - } - } else { + if (!Files.exists(fallbackSource)) { System.err.println("โŒ Fallback art file is missing: " + fallbackSource); + return; + } + + try { + BufferedImage image = ImageIO.read(fallbackSource.toFile()); + if (image != null) { + ImageIO.write(image, "png", destination.toFile()); + System.out.println("๐Ÿ–ผ๏ธ Fallback album art converted to PNG."); + } else { + System.err.println("โŒ Could not read fallback image."); + } + } catch (IOException e) { + System.err.println("โŒ Failed to convert fallback art: " + e.getMessage()); } } } \ No newline at end of file diff --git a/target/classes/com/minster586/radiodjtuna/Config.class b/target/classes/com/minster586/radiodjtuna/Config.class deleted file mode 100644 index 3d96209..0000000 Binary files a/target/classes/com/minster586/radiodjtuna/Config.class and /dev/null differ diff --git a/target/classes/com/minster586/radiodjtuna/ConfigManager.class b/target/classes/com/minster586/radiodjtuna/ConfigManager.class deleted file mode 100644 index fae0cff..0000000 Binary files a/target/classes/com/minster586/radiodjtuna/ConfigManager.class and /dev/null differ diff --git a/target/classes/com/minster586/radiodjtuna/MainApp.class b/target/classes/com/minster586/radiodjtuna/MainApp.class deleted file mode 100644 index f4dcc1f..0000000 Binary files a/target/classes/com/minster586/radiodjtuna/MainApp.class and /dev/null differ diff --git a/target/classes/com/minster586/radiodjtuna/MetadataCacheManager.class b/target/classes/com/minster586/radiodjtuna/MetadataCacheManager.class deleted file mode 100644 index 45bce32..0000000 Binary files a/target/classes/com/minster586/radiodjtuna/MetadataCacheManager.class and /dev/null differ diff --git a/target/classes/com/minster586/radiodjtuna/OutputManager.class b/target/classes/com/minster586/radiodjtuna/OutputManager.class deleted file mode 100644 index 17f7663..0000000 Binary files a/target/classes/com/minster586/radiodjtuna/OutputManager.class and /dev/null differ diff --git a/target/classes/com/minster586/radiodjtuna/RadioDjApiClient.class b/target/classes/com/minster586/radiodjtuna/RadioDjApiClient.class deleted file mode 100644 index eff1f5a..0000000 Binary files a/target/classes/com/minster586/radiodjtuna/RadioDjApiClient.class and /dev/null differ diff --git a/target/classes/com/minster586/radiodjtuna/TrackInfo.class b/target/classes/com/minster586/radiodjtuna/TrackInfo.class deleted file mode 100644 index f538a5a..0000000 Binary files a/target/classes/com/minster586/radiodjtuna/TrackInfo.class and /dev/null differ