From e3dbe80669f5724cd05df2ff5b4ac233c6cb7bd4 Mon Sep 17 00:00:00 2001 From: minster586 <43217359+minster586@users.noreply.github.com> Date: Sat, 28 Jun 2025 21:50:19 -0400 Subject: [PATCH] working project --- Build.bat | 42 +++++-- pom.xml | 92 ++++++++------ .../com/minster586/radiodjtuna/Config.java | 30 +++-- .../minster586/radiodjtuna/ConfigManager.java | 117 ++++++++++++------ .../com/minster586/radiodjtuna/MainApp.java | 75 +++++++---- .../minster586/radiodjtuna/OutputManager.java | 60 ++++++--- .../com/minster586/radiodjtuna/Config.class | Bin 1272 -> 0 bytes .../radiodjtuna/ConfigManager.class | Bin 3721 -> 0 bytes .../com/minster586/radiodjtuna/MainApp.class | Bin 3136 -> 0 bytes .../radiodjtuna/MetadataCacheManager.class | Bin 5000 -> 0 bytes .../radiodjtuna/OutputManager.class | Bin 4359 -> 0 bytes .../radiodjtuna/RadioDjApiClient.class | Bin 2809 -> 0 bytes .../minster586/radiodjtuna/TrackInfo.class | Bin 1833 -> 0 bytes 13 files changed, 278 insertions(+), 138 deletions(-) delete mode 100644 target/classes/com/minster586/radiodjtuna/Config.class delete mode 100644 target/classes/com/minster586/radiodjtuna/ConfigManager.class delete mode 100644 target/classes/com/minster586/radiodjtuna/MainApp.class delete mode 100644 target/classes/com/minster586/radiodjtuna/MetadataCacheManager.class delete mode 100644 target/classes/com/minster586/radiodjtuna/OutputManager.class delete mode 100644 target/classes/com/minster586/radiodjtuna/RadioDjApiClient.class delete mode 100644 target/classes/com/minster586/radiodjtuna/TrackInfo.class 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 3d962099aad5380f498f2dc9ed69f30fac989fef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1272 zcmaiy-%b-j6vn@4ODkoerL+rD1quRfP24Mui9pgoNSZX7SmA!UJJhNBhitbbzLOW4 zNKCx&0emRqIZKnq?Ka-b*_l1xIluGG{QmRv7l2c=3&;pG?7*`;?T;fBzI^@43Z

@iQuVtutKRwJvA2szk=>2zQ zA}6 z_m9l!a)pM0M_8s*s<0q6*X%NcNyRSb4d)_928xVfQjwGtfnqK5?Fb7Dlhm2 zph5C1J+T}zYzuv5c%(QISRAP~MQ%hFNb4%?EQ*5SQu_zizQt)}TJ!N?7VGrQsg!^U zHYk!wsL}eb_&dz6^fQB*xR@&5!sDsg`mJdJ?3P3i31^q@DyZsomxR5H^@WcCbrp(viI{2}ib5LH2Pl70(>0-(xQcvO^%d n3qbgW8RS{2BZo;x_ESNQ(;YcXf*cXZ@d6NjVhqxZM{oTFfs@ZK diff --git a/target/classes/com/minster586/radiodjtuna/ConfigManager.class b/target/classes/com/minster586/radiodjtuna/ConfigManager.class deleted file mode 100644 index fae0cff560f92b0f29877c99fdc34ce490dd50a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3721 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 diff --git a/target/classes/com/minster586/radiodjtuna/MetadataCacheManager.class b/target/classes/com/minster586/radiodjtuna/MetadataCacheManager.class deleted file mode 100644 index 45bce329ffda04565a2ac32abdd9abf02886f3c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5000 zcmb7Id3+Pc75+w+y^_3KA{-(lF_;9Os6Zja3J_y(agdFJ9TSrDSW9bLAnmHFm9g8V zBx!olv}sBU`Ozk&M?#xkkR}F0NsqK?o8I@Gw)8&JEE%)B?> zd+&R1-tf$S&pZiWBmS+xClF1TnP?_u^U z@74@$Shp1T1?KM6Mzv^KGlru(2lwg;TOgQ_n|d{SMBuWxvu>o!=uj%HN9Alxpyv9N zk+L@lENZB

aUK-(e>85UNnCLV+q!)zCN)gurYS)u<7e6Hgg>Pd+oKTm9N#S{De% z&4iX7(5#gF_9p%INGd0=I{trJN0h;|sU;nY1sWO}FQ%d2(h~c+j3F~ti-lOMU=iU= zH(f;pOK84rcG4U49<@5V=pQ)~3t=g)RB(lWS~{=dD%4Rvl8p3j{P*6{7 za_d-hER&9AH7loEx#(6ik6m6RBNMO-I~3elmiT?+EEyRUadeZKx@8G0 zaMdwLy|mb--MW@x*mvS41-%0E9d8EO~I`NOy!VO+>Vzs##w1a z+Mr<-oavM+S>9Njigr8cOvxcP^#q!nr5R-z#RlEP6{x?o7)w=E(o6^}EaP8+*j)Bo zrb&V>C|fV^lU%bUsbvA33X4Zol2UU>$z7UcB{)5ZZ=DRL@o?Mi{e_&?oDgsdQz4GmcG~E zwFH$Q5pBJg>jW~*7BhU>luXIHRNO5& z9*{MoP)P1k@kYFfG1rnww~*{AFC<0F%9IwuH{F2gGt!S8c#Dd+%F}~9-K{io_H8Qe zEnspAjhuXkiu>ebt=uZZ&ix_Wk3$OHMT@73wry!fZpgGUQPv)JRCe9Go5U}>5-ujd z0&zbMt2lxOm`c1}N^~+4F0Ch~;HbcrmEi5n+u6LmLm$rt@IJCSWh7bc19*tZGp^@C zIED`@_<+Dwm8466sQ3^*EHE#p+pf-D%gpMQJua}a0-GuYL%}25Tpi5l21A%Y#KQwL5|^s zij#PZ1Sx89F11QPU9~aCI=7}3Wuh+bSmQvL*W}HzyvXi|w3%7blXXR1I`5KY6%Xm3 zq8p1%jp>9}U}?q89wI_X8DznWZS#z(#W{Rd!IQiaN*(1ToQluk^Sqgu8Qx58%Xq&w z+@ob!4Vx-ZV0tl#^Ok3e@)W+L;ENZuD6)RYbrnzJ%Wks9bz@j|(`@^?q|{ecJcF;X ztfzSI%7AuFKZ5GNfp01JCa>yJE2MfA-NE>pDy!_eVN-v z6no_X{6XM>(s+8iU4`2>tnSrHkJ7+e#{h`;;o{ z6Fn6jS~}gwu*c@q;5PyMnce^SqsN{*eP3Ot#ttM|XPb5M*;1G0{nwgxF2GfM8MM`j z0RGDU{QQIWmTZ-Ga)qsYesmdzZgo&gPR}WLj*`>7R>=^fk3=@0+$~}wK#Da%eq@8u(GF>Q_XGuh<_5R z+3Hs}MXJNsPT;z>8adi1M~WP6mZN|iwaZcPEIRf?Y9dM`5DA{bwiDftVLKJ}oIyV@ zfm_502TKA;w!&489h;n=3yEnFaW5v)r5s%$AA+by9s8f3X2bU%%vZ4VdGso@M<9z=dtSbqT4e-} zPq}mgmh@MVw<%}w=$?rG6h7V-U|a$bWdff%j`>q@`Lq+4 zlY|W%!r@|AI%z7fnr>P{L)J1#>*$d6bk7FPTt`Q>p@%QP+xh<|No=B9Hsb(i57NDd z*g+k^Ry;xiOIi*&ggfqqhfcxcc!DST*<`ul2~y4t&m7$2g=Y!Iz3?RY1fIYthUWlj zd>WGs&q1O(gR>0J5n`JPN)W#1ahTzl!zl5A>c#_i@ykyr_027l_>2!dt&{kIz(WYP zR!!n7KI;6sAh7E2cTRAdOU)34z!zVDWRP|=xhjfw)I0d3=j!o262!-ym*e~R0p%q@ zR7sFJ1y8jr_#s!>=6Z0R;UPZ3wHA8UNZS62jC`GX%S$n<4Osz6~7?Ur8%t*R9SUe@hn06sbvLz>EwX~-2vYV zEF?7wgn~5+&Jnxw=Nx>_*P0h-M5W~_cUjt6gwzcA1U-F=jUJWH_plb MLijuWfqxI*2aO#C(VFwaI$I@VrEzbze$e6Ba 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