working project
This commit is contained in:
42
Build.bat
42
Build.bat
@@ -1,24 +1,40 @@
|
|||||||
@echo off
|
@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
|
mvn clean
|
||||||
|
|
||||||
REM Compile and package the JAR
|
|
||||||
mvn package
|
|
||||||
|
|
||||||
IF %ERRORLEVEL% NEQ 0 (
|
IF %ERRORLEVEL% NEQ 0 (
|
||||||
echo ❌ Build failed.
|
echo ❌ Maven clean failed.
|
||||||
pause
|
pause
|
||||||
exit /b %ERRORLEVEL%
|
exit /b %ERRORLEVEL%
|
||||||
)
|
)
|
||||||
|
|
||||||
REM Copy final JAR to top-level folder (optional)
|
REM Step 2: Compile and package
|
||||||
IF EXIST target\radiodj-tuna-bridge-1.0-SNAPSHOT.jar (
|
echo 🔨 Compiling...
|
||||||
copy target\radiodj-tuna-bridge-1.0-SNAPSHOT.jar radiodj-tuna-bridge.jar >nul
|
mvn package
|
||||||
echo ✅ Build complete. Output: radiodj-tuna-bridge.jar
|
IF %ERRORLEVEL% NEQ 0 (
|
||||||
) ELSE (
|
echo ❌ Maven build failed.
|
||||||
echo ❌ JAR not found!
|
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
|
pause
|
92
pom.xml
92
pom.xml
@@ -1,42 +1,64 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<groupId>com.example</groupId>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>radiodj-tuna-bridge</artifactId>
|
<groupId>com.minster586</groupId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<artifactId>radiodj-tuna-bridge</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<name>RadioDJ Tuna Bridge</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
</properties>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- ✅ YAML parsing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>2.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Jackson for XML & JSON processing -->
|
<!-- ✅ XML support if needed -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
<groupId>xerces</groupId>
|
||||||
<artifactId>jackson-dataformat-xml</artifactId>
|
<artifactId>xercesImpl</artifactId>
|
||||||
<version>2.17.0</version>
|
<version>2.12.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
<!-- SnakeYAML for config.yml parsing -->
|
<build>
|
||||||
<dependency>
|
<plugins>
|
||||||
<groupId>org.yaml</groupId>
|
<!-- ✅ Assembly Plugin: builds JAR with dependencies -->
|
||||||
<artifactId>snakeyaml</artifactId>
|
<plugin>
|
||||||
<version>2.2</version>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
</dependency>
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>3.6.0</version>
|
||||||
<!-- Dorkbox SystemTray for Windows notifications -->
|
<configuration>
|
||||||
<dependency>
|
<descriptorRefs>
|
||||||
<groupId>com.dorkbox</groupId>
|
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||||
<artifactId>SystemTray</artifactId>
|
</descriptorRefs>
|
||||||
<version>3.17</version>
|
<archive>
|
||||||
</dependency>
|
<manifest>
|
||||||
|
<mainClass>com.minster586.radiodjtuna.MainApp</mainClass>
|
||||||
</dependencies>
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>assemble-all</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
@@ -3,9 +3,21 @@ package com.minster586.radiodjtuna;
|
|||||||
public class Config {
|
public class Config {
|
||||||
private String radioDjApiUrl;
|
private String radioDjApiUrl;
|
||||||
private String albumArtDirectory;
|
private String albumArtDirectory;
|
||||||
private int updateIntervalSeconds;
|
|
||||||
private String fallbackArtFilename;
|
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() {
|
public String getRadioDjApiUrl() {
|
||||||
return radioDjApiUrl;
|
return radioDjApiUrl;
|
||||||
}
|
}
|
||||||
@@ -22,14 +34,6 @@ public class Config {
|
|||||||
this.albumArtDirectory = albumArtDirectory;
|
this.albumArtDirectory = albumArtDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getUpdateIntervalSeconds() {
|
|
||||||
return updateIntervalSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUpdateIntervalSeconds(int updateIntervalSeconds) {
|
|
||||||
this.updateIntervalSeconds = updateIntervalSeconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFallbackArtFilename() {
|
public String getFallbackArtFilename() {
|
||||||
return fallbackArtFilename;
|
return fallbackArtFilename;
|
||||||
}
|
}
|
||||||
@@ -37,4 +41,12 @@ public class Config {
|
|||||||
public void setFallbackArtFilename(String fallbackArtFilename) {
|
public void setFallbackArtFilename(String fallbackArtFilename) {
|
||||||
this.fallbackArtFilename = fallbackArtFilename;
|
this.fallbackArtFilename = fallbackArtFilename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getUpdateIntervalSeconds() {
|
||||||
|
return updateIntervalSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdateIntervalSeconds(int updateIntervalSeconds) {
|
||||||
|
this.updateIntervalSeconds = updateIntervalSeconds;
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,14 +1,21 @@
|
|||||||
package com.minster586.radiodjtuna;
|
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.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.InputStream;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Scanner;
|
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 {
|
public class ConfigManager {
|
||||||
|
|
||||||
@@ -19,56 +26,90 @@ public class ConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Config loadOrCreateConfig() {
|
public Config loadOrCreateConfig() {
|
||||||
File configFile = configPath.toFile();
|
File file = configPath.toFile();
|
||||||
if (!configFile.exists()) {
|
if (file.exists()) {
|
||||||
System.out.println("⚙️ Creating new config.yml...");
|
try (FileInputStream in = new FileInputStream(file)) {
|
||||||
Config newConfig = promptForConfig();
|
Yaml yaml = new Yaml(new Constructor(Config.class, new LoaderOptions()));
|
||||||
saveConfig(newConfig);
|
return yaml.load(in);
|
||||||
return newConfig;
|
} catch (Exception e) {
|
||||||
|
System.err.println("❌ Failed to load config.yml: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try (InputStream input = java.nio.file.Files.newInputStream(configPath)) {
|
System.out.println("📝 Creating new config.yml...");
|
||||||
Yaml yaml = new Yaml(new Constructor(Config.class));
|
Config config = promptUserConfig();
|
||||||
Config loaded = yaml.load(input);
|
saveConfig(config);
|
||||||
System.out.println("✅ Config loaded successfully.");
|
convertFallbackToPngIfNecessary(config);
|
||||||
return loaded;
|
return config;
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("❌ Failed to load config: " + e.getMessage());
|
|
||||||
System.exit(1);
|
|
||||||
return null; // unreachable, but satisfies compiler
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Config promptForConfig() {
|
private Config promptUserConfig() {
|
||||||
Scanner scanner = new Scanner(System.in);
|
Scanner scanner = new Scanner(System.in);
|
||||||
Config config = new Config();
|
|
||||||
|
|
||||||
System.out.print("🔗 Enter RadioDJ API URL (e.g. http://localhost:1234/playing): ");
|
System.out.print("🔗 Enter RadioDJ API URL (e.g., http://localhost:1234/playing): ");
|
||||||
config.setRadioDjApiUrl(scanner.nextLine().trim());
|
String apiUrl = scanner.nextLine().trim();
|
||||||
|
|
||||||
System.out.print("📁 Enter full path to RadioDJ album art folder: ");
|
System.out.print("🖼️ Enter path to album art folder: ");
|
||||||
config.setAlbumArtDirectory(scanner.nextLine().trim());
|
String artDir = scanner.nextLine().trim();
|
||||||
|
|
||||||
System.out.print("⏱️ Enter update interval (seconds): ");
|
System.out.print("🧩 Enter filename of fallback album art (e.g., fallback.jpg): ");
|
||||||
config.setUpdateIntervalSeconds(Integer.parseInt(scanner.nextLine().trim()));
|
String fallback = scanner.nextLine().trim();
|
||||||
|
|
||||||
System.out.print("🖼️ Enter fallback album art filename (e.g. fallback-art.jpg): ");
|
int interval = promptForInterval(scanner);
|
||||||
config.setFallbackArtFilename(scanner.nextLine().trim());
|
|
||||||
|
|
||||||
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) {
|
private void saveConfig(Config config) {
|
||||||
try (FileWriter writer = new FileWriter(configPath.toFile())) {
|
try (FileWriter writer = new FileWriter(configPath.toFile())) {
|
||||||
DumperOptions options = new DumperOptions();
|
DumperOptions options = new DumperOptions();
|
||||||
options.setIndent(2);
|
|
||||||
options.setPrettyFlow(true);
|
|
||||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
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);
|
yaml.dump(config, writer);
|
||||||
System.out.println("💾 Config saved to " + configPath);
|
System.out.println("✅ Config saved: " + configPath.getFileName());
|
||||||
} catch (Exception e) {
|
} catch (IOException e) {
|
||||||
System.err.println("❌ Failed to save config: " + e.getMessage());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -3,48 +3,77 @@ package com.minster586.radiodjtuna;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
public class MainApp {
|
public class MainApp {
|
||||||
|
|
||||||
// Base paths determined at runtime
|
|
||||||
private static Path jarDir;
|
private static Path jarDir;
|
||||||
private static Path outputDir;
|
private static Path outputDir;
|
||||||
private static Path configFile;
|
private static Path configFile;
|
||||||
private static Path cacheFile;
|
private static Path cacheFile;
|
||||||
|
|
||||||
// Core components
|
|
||||||
private static Config config;
|
|
||||||
private static MetadataCacheManager cacheManager;
|
|
||||||
private static TrackInfo lastPlayed;
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println("🔊 RadioDJ Tuna Bridge starting up...");
|
System.out.println("🔊 RadioDJ Tuna Bridge starting up...");
|
||||||
|
|
||||||
initializePaths();
|
initializePaths();
|
||||||
ensureOutputFolder();
|
ensureOutputFolder();
|
||||||
|
|
||||||
// Load or create config
|
|
||||||
ConfigManager configManager = new ConfigManager(configFile);
|
ConfigManager configManager = new ConfigManager(configFile);
|
||||||
config = configManager.loadOrCreateConfig();
|
Config config = configManager.loadOrCreateConfig();
|
||||||
|
|
||||||
// Set up metadata cache manager
|
MetadataCacheManager cacheManager = new MetadataCacheManager(cacheFile);
|
||||||
cacheManager = new MetadataCacheManager(cacheFile);
|
final TrackInfo[] lastPlayed = { cacheManager.loadCache() };
|
||||||
lastPlayed = cacheManager.loadCache();
|
|
||||||
|
|
||||||
if (lastPlayed != null) {
|
RadioDjApiClient apiClient = new RadioDjApiClient(config.getRadioDjApiUrl());
|
||||||
System.out.println("🗃️ Loaded cached track: " + lastPlayed.getTitle());
|
OutputManager outputManager = new OutputManager(
|
||||||
} else {
|
outputDir,
|
||||||
System.out.println("📁 No metadata cache found yet.");
|
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()
|
System.exit(0);
|
||||||
// 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.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void initializePaths() {
|
private static void initializePaths() {
|
||||||
|
@@ -3,6 +3,9 @@ package com.minster586.radiodjtuna;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
public class OutputManager {
|
public class OutputManager {
|
||||||
|
|
||||||
@@ -21,9 +24,14 @@ public class OutputManager {
|
|||||||
writeTextFile("track-title.txt", track.getTitle());
|
writeTextFile("track-title.txt", track.getTitle());
|
||||||
writeTextFile("track-artist.txt", track.getArtist());
|
writeTextFile("track-artist.txt", track.getArtist());
|
||||||
writeTextFile("track-album.txt", track.getAlbum());
|
writeTextFile("track-album.txt", track.getAlbum());
|
||||||
copyAlbumArt(track.getAlbumArt());
|
|
||||||
} catch (IOException e) {
|
} 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);
|
Path file = outputDir.resolve(fileName);
|
||||||
Files.writeString(file, content != null ? content : "", StandardCharsets.UTF_8,
|
Files.writeString(file, content != null ? content : "", StandardCharsets.UTF_8,
|
||||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||||
|
System.out.println("📄 Wrote file: " + file.getFileName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyAlbumArt(String albumArtFileName) {
|
private void convertAlbumArtToPng(String albumArtFileName) {
|
||||||
Path destination = outputDir.resolve("album-art.jpg");
|
Path destination = outputDir.resolve("album-art.png");
|
||||||
|
|
||||||
if (albumArtFileName == null || albumArtFileName.isBlank()) {
|
if (albumArtFileName == null || albumArtFileName.isBlank()) {
|
||||||
System.out.println("ℹ️ No album art in tag, using fallback.");
|
System.out.println("ℹ️ No album art in tag, using fallback.");
|
||||||
copyFallback(destination);
|
convertFallbackToPng(destination);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Path sourceFile = albumArtSourceFolder.resolve(albumArtFileName);
|
Path sourceFile = albumArtSourceFolder.resolve(albumArtFileName);
|
||||||
|
|
||||||
if (!Files.exists(sourceFile)) {
|
if (!Files.exists(sourceFile)) {
|
||||||
System.out.println("⚠️ Album art not found: " + sourceFile.getFileName() + ", using fallback.");
|
System.out.println("⚠️ Album art not found: " + sourceFile.getFileName() + ", using fallback.");
|
||||||
copyFallback(destination);
|
convertFallbackToPng(destination);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.copy(sourceFile, destination, StandardCopyOption.REPLACE_EXISTING);
|
BufferedImage image = ImageIO.read(sourceFile.toFile());
|
||||||
System.out.println("🎨 Album art updated: " + albumArtFileName);
|
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) {
|
} catch (IOException e) {
|
||||||
System.err.println("❌ Failed copying album art: " + e.getMessage());
|
System.err.println("❌ Failed converting album art: " + e.getMessage());
|
||||||
copyFallback(destination);
|
convertFallbackToPng(destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyFallback(Path destination) {
|
private void convertFallbackToPng(Path destination) {
|
||||||
Path fallbackSource = outputDir.resolve(fallbackArtFilename);
|
Path fallbackSource = outputDir.resolve(fallbackArtFilename);
|
||||||
if (Files.exists(fallbackSource)) {
|
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 {
|
|
||||||
System.err.println("❌ Fallback art file is missing: " + 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user