diff --git a/server/src/main/java/xyz/e3ndr/athena/Athena.java b/server/src/main/java/xyz/e3ndr/athena/Athena.java index d6e5e04..83279a6 100644 --- a/server/src/main/java/xyz/e3ndr/athena/Athena.java +++ b/server/src/main/java/xyz/e3ndr/athena/Athena.java @@ -37,11 +37,17 @@ public class Athena { public static final int STREAMING_BUFFER_SIZE = 64/*kb*/ * 1000; public static final int TRANSCODING_BUFFER_SIZE = 512/*kb*/ * 1000; - public static File mediaDirectory; - public static File cacheDirectory; - public static File ingestDirectory; + public static final File mediaDirectory = new File("./Media"); + public static final File cacheDirectory = new File("./Cache"); + public static final File ingestDirectory = new File("./Ingest"); + + static { + Athena.mediaDirectory.mkdirs(); + Athena.cacheDirectory.mkdirs(); + Athena.ingestDirectory.mkdirs(); + } - public static boolean enableCudaAcceleration; + public static Config config = new Config(); public static List mediaSessions = Collections.synchronizedList(new LinkedList<>()); public static List transcodeSessions = Collections.synchronizedList(new LinkedList<>()); diff --git a/server/src/main/java/xyz/e3ndr/athena/Config.java b/server/src/main/java/xyz/e3ndr/athena/Config.java index 2cfe2b9..a8f95a1 100644 --- a/server/src/main/java/xyz/e3ndr/athena/Config.java +++ b/server/src/main/java/xyz/e3ndr/athena/Config.java @@ -1,39 +1,72 @@ package xyz.e3ndr.athena; -import java.io.File; - import co.casterlabs.rakurai.json.annotating.JsonClass; -import lombok.Getter; +import co.casterlabs.rakurai.json.annotating.JsonField; import lombok.ToString; -@Getter @ToString @JsonClass(exposeAll = true) public class Config { - private boolean debug = false; - private boolean disableColoredConsole = false; + public ConsoleConfig console = new ConsoleConfig(); + public ServiceConfig services = new ServiceConfig(); + public SessionConfig sessions = new SessionConfig(); + public TranscodeConfig transcoding = new TranscodeConfig(); + + @ToString + @JsonClass(exposeAll = true) + public static class ConsoleConfig { + public boolean debug = false; + public @JsonField("disable_color") boolean disableColor = false; + } - private String mediaDirectory = "./Media"; - private String cacheDirectory = "./Cache"; - private String ingestDirectory = "./Ingest"; + @ToString + @JsonClass(exposeAll = true) + public static class ServiceConfig { + public HttpServiceConfig http = new HttpServiceConfig(); + public FtpServiceConfig ftp = new FtpServiceConfig(); + public @JsonField("simple_ui") SimpleUIConfig simpleUI = new SimpleUIConfig(); + public @JsonField("wii_mc") WiiMCServiceConfig wiimc = new WiiMCServiceConfig(); - private boolean enableCudaAcceleration; + @ToString + @JsonClass(exposeAll = true) + public static class HttpServiceConfig { + public boolean enable = true; + public int port = 8125; + } - // -1 to disable. - private int webUiPort = 8127; - private int httpPort = 8125; - private int ftpPort = 8126; + @ToString + @JsonClass(exposeAll = true) + public static class FtpServiceConfig { + public boolean enable = true; + public int port = 8126; + } + + @ToString + @JsonClass(exposeAll = true) + public static class SimpleUIConfig { + public boolean enable = true; + public int port = 8127; + } - public File getMediaDirectory() { - return new File(this.mediaDirectory); } - public File getCacheDirectory() { - return new File(this.cacheDirectory); + @ToString + @JsonClass(exposeAll = true) + public static class SessionConfig { } - public File getIngestDirectory() { - return new File(this.ingestDirectory); + @ToString + @JsonClass(exposeAll = true) + public static class TranscodeConfig { + public TranscodeAcceleration acceleration = TranscodeAcceleration.SOFTWARE_ONLY; + + public static enum TranscodeAcceleration { + SOFTWARE_ONLY, + NVIDIA_PREFERRED, + // TODO AMD & Intel encoders. + // TODO Implement hardware decoding to speed up the transcode pipeline. + // https://trac.ffmpeg.org/wiki/HWAccelIntro + } } } diff --git a/server/src/main/java/xyz/e3ndr/athena/Launcher.java b/server/src/main/java/xyz/e3ndr/athena/Launcher.java index cd675e8..49ff366 100644 --- a/server/src/main/java/xyz/e3ndr/athena/Launcher.java +++ b/server/src/main/java/xyz/e3ndr/athena/Launcher.java @@ -7,7 +7,6 @@ import co.casterlabs.rakurai.json.Rson; import co.casterlabs.rakurai.json.serialization.JsonParseException; import co.casterlabs.sora.SoraFramework; -import lombok.Getter; import xyz.e3ndr.athena.server.ftp.AthenaFtpServer; import xyz.e3ndr.athena.server.http.AthenaHttpServer; import xyz.e3ndr.athena.webui.AthenaUIServer; @@ -20,8 +19,6 @@ public class Launcher { private static final FastLogger logger = new FastLogger(); - private static @Getter Config config; - public static void main(String[] args) throws Exception { ClassLoader.getPlatformClassLoader().setDefaultAssertionStatus(true); @@ -31,62 +28,36 @@ public static void main(String[] args) throws Exception { // Load the config. if (configFile.exists()) { try { - config = Rson.DEFAULT.fromJson(Files.readString(configFile.toPath()), Config.class); - - // disableColoredConsole - FastLoggingFramework.setColorEnabled(!config.isDisableColoredConsole()); - - // debug - FastLoggingFramework.setDefaultLevel(config.isDebug() ? LogLevel.DEBUG : LogLevel.INFO); - - // enableCudaAcceleration - Athena.enableCudaAcceleration = config.isEnableCudaAcceleration(); - - // mediaDirectory - Athena.mediaDirectory = config.getMediaDirectory(); - if (!Athena.mediaDirectory.exists()) { - logger.info("Media directory doesn't exist, creating it now."); - Athena.mediaDirectory.mkdirs(); - } else if (!Athena.mediaDirectory.isDirectory()) { - logger.fatal("Media directory is not actually a directory, crashing."); - System.exit(-1); - } - - // cacheDirectory - Athena.cacheDirectory = config.getCacheDirectory(); - if (!Athena.cacheDirectory.exists()) { - logger.info("Cache directory doesn't exist, creating it now."); - Athena.cacheDirectory.mkdirs(); - } else if (!Athena.cacheDirectory.isDirectory()) { - logger.fatal("Cache directory is not actually a directory, crashing."); - System.exit(-1); - } - - // ingestDirectory - Athena.ingestDirectory = config.getIngestDirectory(); - if (!Athena.ingestDirectory.exists()) { - logger.info("Ingest directory doesn't exist, creating it now."); - Athena.ingestDirectory.mkdirs(); - } else if (!Athena.ingestDirectory.isDirectory()) { - logger.fatal("Ingest directory is not actually a directory, crashing."); - System.exit(-1); - } + Athena.config = Rson.DEFAULT.fromJson(Files.readString(configFile.toPath()), Config.class); - logger.debug("Using config: %s", config); + logger.debug("Using config: %s", Athena.config); } catch (JsonParseException e) { logger.severe("Unable to parse config file, is it malformed?\n%s", e); } } else { - config = new Config(); logger.info("Config file doesn't exist, creating a new file."); } - Files.writeString(configFile.toPath(), Rson.DEFAULT.toJson(config).toString(true)); + Files.writeString(configFile.toPath(), Rson.DEFAULT.toJson(Athena.config).toString(true)); + + // disableColoredConsole + FastLoggingFramework.setColorEnabled(!Athena.config.console.disableColor); + + // debug + FastLoggingFramework.setDefaultLevel(Athena.config.console.debug ? LogLevel.DEBUG : LogLevel.INFO); // Go! - AsyncTask.createNonDaemon(() -> new AthenaHttpServer().start(config)); - AsyncTask.createNonDaemon(() -> new AthenaFtpServer().start(config)); - AsyncTask.createNonDaemon(() -> new AthenaUIServer().start(config)); + if (Athena.config.services.http.enable) { + AsyncTask.createNonDaemon(() -> new AthenaHttpServer().start()); + } + + if (Athena.config.services.http.enable) { + AsyncTask.createNonDaemon(() -> new AthenaFtpServer().start()); + } + + if (Athena.config.services.simpleUI.enable) { + AsyncTask.createNonDaemon(() -> new AthenaUIServer().start()); + } } } diff --git a/server/src/main/java/xyz/e3ndr/athena/server/AthenaServer.java b/server/src/main/java/xyz/e3ndr/athena/server/AthenaServer.java index 92bb40b..360ff60 100644 --- a/server/src/main/java/xyz/e3ndr/athena/server/AthenaServer.java +++ b/server/src/main/java/xyz/e3ndr/athena/server/AthenaServer.java @@ -1,9 +1,7 @@ package xyz.e3ndr.athena.server; -import xyz.e3ndr.athena.Config; - public interface AthenaServer { - public void start(Config config); + public void start(); } diff --git a/server/src/main/java/xyz/e3ndr/athena/server/ftp/AthenaFtpServer.java b/server/src/main/java/xyz/e3ndr/athena/server/ftp/AthenaFtpServer.java index 8a6eb9b..2fa07fe 100644 --- a/server/src/main/java/xyz/e3ndr/athena/server/ftp/AthenaFtpServer.java +++ b/server/src/main/java/xyz/e3ndr/athena/server/ftp/AthenaFtpServer.java @@ -7,7 +7,7 @@ import java.util.LinkedList; import java.util.List; -import xyz.e3ndr.athena.Config; +import xyz.e3ndr.athena.Athena; import xyz.e3ndr.athena.server.AthenaServer; import xyz.e3ndr.fastloggingframework.logging.FastLogger; import xyz.e3ndr.fastloggingframework.logging.LogLevel; @@ -24,9 +24,8 @@ public class AthenaFtpServer implements AthenaServer { @SuppressWarnings("resource") @Override - public void start(Config config) { - int controlPort = config.getFtpPort(); - if (controlPort == -1) return; + public void start() { + int controlPort = Athena.config.services.ftp.port; // Generate a list of ports. for (int idx = 0; idx < +MAX_CLIENTS; idx++) { diff --git a/server/src/main/java/xyz/e3ndr/athena/server/http/AthenaHttpServer.java b/server/src/main/java/xyz/e3ndr/athena/server/http/AthenaHttpServer.java index f08b84e..2205875 100644 --- a/server/src/main/java/xyz/e3ndr/athena/server/http/AthenaHttpServer.java +++ b/server/src/main/java/xyz/e3ndr/athena/server/http/AthenaHttpServer.java @@ -7,7 +7,7 @@ import co.casterlabs.sora.SoraLauncher; import co.casterlabs.sora.api.SoraPlugin; import lombok.NonNull; -import xyz.e3ndr.athena.Config; +import xyz.e3ndr.athena.Athena; import xyz.e3ndr.athena.server.AthenaServer; import xyz.e3ndr.fastloggingframework.logging.FastLogger; import xyz.e3ndr.fastloggingframework.logging.LogLevel; @@ -15,11 +15,9 @@ public class AthenaHttpServer implements AthenaServer { @Override - public void start(Config config) { + public void start() { try { - int port = config.getHttpPort(); - - if (port == -1) return; + int port = Athena.config.services.http.port; SoraFramework framework = new SoraLauncher() .setPort(port) diff --git a/server/src/main/java/xyz/e3ndr/athena/server/http/MetaRoutes.java b/server/src/main/java/xyz/e3ndr/athena/server/http/MetaRoutes.java index b47bc1c..d9d7637 100644 --- a/server/src/main/java/xyz/e3ndr/athena/server/http/MetaRoutes.java +++ b/server/src/main/java/xyz/e3ndr/athena/server/http/MetaRoutes.java @@ -5,7 +5,7 @@ import co.casterlabs.sora.api.http.HttpProvider; import co.casterlabs.sora.api.http.SoraHttpSession; import co.casterlabs.sora.api.http.annotations.HttpEndpoint; -import xyz.e3ndr.athena.Launcher; +import xyz.e3ndr.athena.Athena; class MetaRoutes implements HttpProvider { @@ -18,9 +18,7 @@ public HttpResponse onWellKnown(SoraHttpSession session) { @HttpEndpoint(uri = "/*") public HttpResponse onGetIndex(SoraHttpSession session) { - int webUiPort = Launcher.getConfig().getWebUiPort(); - - if (webUiPort == -1) { + if (Athena.config.services.simpleUI.enable) { return HttpResponse .newFixedLengthResponse( StandardHttpStatus.OK, @@ -31,7 +29,7 @@ public HttpResponse onGetIndex(SoraHttpSession session) { return HttpResponse .newFixedLengthResponse( StandardHttpStatus.OK, - "There's nothing here..... Are you looking for the UI? If so, that's on port " + webUiPort + "." + "There's nothing here..... Are you looking for the UI? If so, that's on port " + Athena.config.services.simpleUI.port + "." ) .setMimeType("text/plain"); } diff --git a/server/src/main/java/xyz/e3ndr/athena/transcoding/FFMpegArgs.java b/server/src/main/java/xyz/e3ndr/athena/transcoding/FFMpegArgs.java index bcffd95..242099c 100644 --- a/server/src/main/java/xyz/e3ndr/athena/transcoding/FFMpegArgs.java +++ b/server/src/main/java/xyz/e3ndr/athena/transcoding/FFMpegArgs.java @@ -4,6 +4,7 @@ import java.util.LinkedList; import java.util.List; +import xyz.e3ndr.athena.Config.TranscodeConfig.TranscodeAcceleration; import xyz.e3ndr.athena.types.AudioCodec; import xyz.e3ndr.athena.types.VideoCodec; import xyz.e3ndr.athena.types.VideoQuality; @@ -27,7 +28,7 @@ public static List a_getFF(AudioCodec codec) { return null; } - public static List v_getFF(VideoCodec codec, VideoQuality quality, boolean enableCuda) { + public static List v_getFF(VideoCodec codec, VideoQuality quality, TranscodeAcceleration acceleration) { // TODO the more advanced parameters for HEVC and AV1 switch (codec) { @@ -36,13 +37,15 @@ public static List v_getFF(VideoCodec codec, VideoQuality quality, boole case H264_BASELINE: case H264_HIGH: - return getH264Args(codec, quality, enableCuda); + return getH264Args(codec, quality, acceleration); case HEVC: - if (enableCuda) { - return Arrays.asList("-c:v", "hevc_nvenc"); - } else { - return Arrays.asList("-c:v", "hevc"); + switch (acceleration) { + case NVIDIA_PREFERRED: + return Arrays.asList("-c:v", "hevc_nvenc"); + + case SOFTWARE_ONLY: + return Arrays.asList("-c:v", "hevc"); } case AV1: @@ -54,15 +57,19 @@ public static List v_getFF(VideoCodec codec, VideoQuality quality, boole return null; } - private static List getH264Args(VideoCodec codec, VideoQuality quality, boolean enableCuda) { + private static List getH264Args(VideoCodec codec, VideoQuality quality, TranscodeAcceleration acceleration) { List args = new LinkedList<>(); - if (enableCuda) { - args.add("-c:v"); - args.add("h264_nvenc"); - } else { - args.add("-c:v"); - args.add("h264"); + switch (acceleration) { + case NVIDIA_PREFERRED: + args.add("-c:v"); + args.add("h264_nvenc"); + break; + + case SOFTWARE_ONLY: + args.add("-c:v"); + args.add("h264"); + break; } switch (codec) { @@ -78,7 +85,8 @@ private static List getH264Args(VideoCodec codec, VideoQuality quality, args.add("high"); args.add("-level"); args.add("5.0"); - if (!enableCuda) { + if (acceleration != TranscodeAcceleration.NVIDIA_PREFERRED) { + // NVIDIA does not support tune. args.add("-tune"); args.add("film"); } diff --git a/server/src/main/java/xyz/e3ndr/athena/transcoding/Transcoder.java b/server/src/main/java/xyz/e3ndr/athena/transcoding/Transcoder.java index 90360d6..27cd619 100644 --- a/server/src/main/java/xyz/e3ndr/athena/transcoding/Transcoder.java +++ b/server/src/main/java/xyz/e3ndr/athena/transcoding/Transcoder.java @@ -57,7 +57,7 @@ public static TranscodeSession start(File targetFile, Media media, VideoQuality } /* ---- Video ---- */ - command.addAll(FFMpegArgs.v_getFF(desiredVCodec, desiredQuality, Athena.enableCudaAcceleration)); + command.addAll(FFMpegArgs.v_getFF(desiredVCodec, desiredQuality, Athena.config.transcoding.acceleration)); if (desiredVCodec != VideoCodec.SOURCE) { command.add("-b:v"); diff --git a/server/src/main/java/xyz/e3ndr/athena/webui/AthenaUIServer.java b/server/src/main/java/xyz/e3ndr/athena/webui/AthenaUIServer.java index 78148d2..c566c93 100644 --- a/server/src/main/java/xyz/e3ndr/athena/webui/AthenaUIServer.java +++ b/server/src/main/java/xyz/e3ndr/athena/webui/AthenaUIServer.java @@ -7,7 +7,7 @@ import co.casterlabs.sora.SoraLauncher; import co.casterlabs.sora.api.SoraPlugin; import lombok.NonNull; -import xyz.e3ndr.athena.Config; +import xyz.e3ndr.athena.Athena; import xyz.e3ndr.athena.server.AthenaServer; import xyz.e3ndr.fastloggingframework.logging.FastLogger; import xyz.e3ndr.fastloggingframework.logging.LogLevel; @@ -15,11 +15,9 @@ public class AthenaUIServer implements AthenaServer { @Override - public void start(Config config) { + public void start() { try { - int port = config.getWebUiPort(); - - if (port == -1) return; + int port = Athena.config.services.simpleUI.port; SoraFramework framework = new SoraLauncher() .setPort(port)