Skip to content

Commit

Permalink
add python venv provider
Browse files Browse the repository at this point in the history
  • Loading branch information
NathanCheshire committed Sep 15, 2024
1 parent 18b051e commit f677a15
Show file tree
Hide file tree
Showing 18 changed files with 518 additions and 586 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ jobs:
- name: Install dependencies on Windows
shell: powershell
run: |
choco install ffmpeg python
python -m pip install --upgrade pip
pip install mutagen
choco install ffmpeg
python -m venv cyderenv
.\cyderenv\Scripts\pip install --upgrade pip
Invoke-WebRequest https://github.com/duncanthrax/scream/releases/download/4.0/Scream4.0.zip -OutFile Scream4.0.zip
Expand-Archive -Path Scream4.0.zip -DestinationPath Scream
openssl req -batch -verbose -x509 -newkey rsa -keyout ScreamCertificate.pvk -out ScreamCertificate.cer -nodes -extensions v3_req
openssl pkcs12 -export -nodes -in ScreamCertificate.cer -inkey ScreamCertificate.pvk -out ScreamCertificate.pfx -passout pass:
env:
CYDER_PYTHON_EXECUTABLE_PATH: ${{ github.workspace }}\cyderenv\Scripts\python.exe

- name: Setup MSVC Dev Cmd
uses: ilammy/msvc-dev-cmd@v1
Expand All @@ -45,7 +47,7 @@ jobs:
- name: Start Windows Audio Service
run: net start audiosrv
shell: powershell

- name: Build with Gradle
run: ./gradlew clean build --info --stacktrace --no-daemon

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# CyderUtils

## A Java utility library

# At one point all sound cards were working but Gradle got in the way somehow: https://github.com/NathanCheshire/CyderUtils/actions/runs/9733328552
At one point all sound cards were working but Gradle got in the way somehow: https://github.com/NathanCheshire/CyderUtils/actions/runs/9733328552
7 changes: 6 additions & 1 deletion project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ dreamifies
dreamifying
dreamifier
ffmpeg
lowpass
lowpass
cyder
cyderutils
natche
nathancheshire
nightcore
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,18 @@
import com.github.natche.cyderutils.audio.ffmpeg.FfmpegStreamEntry;
import com.github.natche.cyderutils.audio.validation.SupportedAudioFileType;
import com.github.natche.cyderutils.constants.CyderRegexPatterns;
import com.github.natche.cyderutils.files.temporary.CyderTemporaryFile;
import com.github.natche.cyderutils.files.FileUtil;
import com.github.natche.cyderutils.files.temporary.CyderTemporaryFile;
import com.github.natche.cyderutils.process.*;
import com.github.natche.cyderutils.strings.StringUtil;
import com.github.natche.cyderutils.threads.CyderThreadFactory;
import com.github.natche.cyderutils.time.TimeUtil;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;

import java.io.File;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Function;
Expand All @@ -32,10 +31,10 @@ public enum DetermineAudioLengthMethod {
FFMPEG(DetermineAudioLengthMethod::getLengthViaFfmpeg),

/** Determine an audio file's length using the Python package Mutagen. */
PYTHON_MUTAGEN(DetermineAudioLengthMethod::getLengthViaMutagen);
PYTHON_MUTAGEN(DetermineAudioLengthMethod::getLengthViaMutagen),

// todo add wave file duration clip? Maybe just clip if it'll work for other audio file types
// since we can get duration from a wave directly so conv to wave and then get that from the cyder wav file
/** Determine an audio file's length using a {@link javax.sound.sampled.Clip}. */
AUDIO_CLIP(DetermineAudioLengthMethod::getLengthViaAudioClip);

/**
* The pattern used to extract the duration seconds floating point number from the
Expand Down Expand Up @@ -136,8 +135,7 @@ private static Future<Duration> getLengthViaFfmpeg(File audioFile) {
* @throws NullPointerException if the provided audio file is null
* @throws IllegalArgumentException if the provided audio file is not a file,
* does not exist, or is not a supported audio type
* @throws CyderProcessException if {@link PythonPackage#MUTAGEN} is not and cannot be
* installed or a valid working Python command cannot be found
* @throws CyderProcessException if mutagen is not present and fails to install
*/
private static Future<Duration> getLengthViaMutagen(File audioFile) {
Preconditions.checkNotNull(audioFile);
Expand All @@ -147,13 +145,15 @@ private static Future<Duration> getLengthViaMutagen(File audioFile) {

CyderThreadFactory threadFactory = getThreadFactory(DetermineAudioLengthMethod.PYTHON_MUTAGEN, audioFile);
return Executors.newSingleThreadExecutor(threadFactory).submit(() -> {
Future<Boolean> mutagenInstalled = PythonPackage.MUTAGEN.isInstalled();
while (!mutagenInstalled.isDone()) Thread.onSpinWait();

if (!mutagenInstalled.get()) {
Future<Boolean> installationRequest = PythonPackage.MUTAGEN.install();
while (!installationRequest.isDone()) Thread.onSpinWait();
if (!installationRequest.get()) throw new CyderProcessException("Failed to install Mutagen");
PythonVirtualEnvironment pev = PevProvider.INSTANCE.getProvider();
Future<Boolean> mutagenPresent = pev.isPipDependencyPresent("mutagen");
while (!mutagenPresent.isDone()) Thread.onSpinWait();
if (!mutagenPresent.get()) {
Future<Boolean> installedMutagen = pev.installRequirement("mutagen");
while (!installedMutagen.isDone()) Thread.onSpinWait();
if (!installedMutagen.get()) {
throw new CyderProcessException("Failed to install mutagen");
}
}

ImmutableList<String> script = ImmutableList.of(
Expand All @@ -170,26 +170,21 @@ private static Future<Duration> getLengthViaMutagen(File audioFile) {
File scriptFile = temporaryPythonScriptFile.buildFile();
FileUtil.writeLinesToFile(scriptFile, script, false);

Future<Optional<String>> firstWorkingPythonInvocableCommand
= PythonProgram.PYTHON.getFirstWorkingProgramName();
while (!firstWorkingPythonInvocableCommand.isDone()) Thread.onSpinWait();
Optional<String> firstCommandOptional = firstWorkingPythonInvocableCommand.get();
if (firstCommandOptional.isEmpty())
throw new CyderProcessException("Failed to find working Python command");

Future<ProcessResult> mutagenLengthResult = ProcessUtil.getProcessOutput(
ImmutableList.of(firstCommandOptional.get(), scriptFile.getAbsolutePath()));
ImmutableList.of(
pev.getPythonExecutable().getAbsolutePath(),
scriptFile.getAbsolutePath()
)
);
while (!mutagenLengthResult.isDone()) Thread.onSpinWait();
ProcessResult result = mutagenLengthResult.get();

if (result.containsErrors()) {
throw new CyderProcessException(
"Mutagen length process result contains errors: " + result.getErrorOutput());
throw new CyderProcessException("Mutagen length process result contains errors");
}

List<String> output = result.getStandardOutput();
ImmutableList<String> output = result.getStandardOutput();
if (output.size() > 1) {
throw new CyderProcessException("Mutagen length process result contains more than one line: " + output);
throw new CyderProcessException("Mutagen length process unexpected output: " + output);
}

float seconds = Float.parseFloat(output.get(0));
Expand All @@ -198,6 +193,12 @@ private static Future<Duration> getLengthViaMutagen(File audioFile) {
});
}

private static Future<Duration> getLengthViaAudioClip(File audioFile) {
// todo add wave file duration clip? Maybe just clip if it'll work for other audio file types
// since we can get duration from a wave directly so conv to wave and then get that from the cyder wav file
return Futures.immediateFuture(Duration.ofMillis(0));
}

/**
* Returns a new {@link CyderThreadFactory} for the {@link java.util.concurrent.ExecutorService}
* used to determine the audio length using a particular method.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.github.natche.cyderutils.process;

import com.google.common.base.Preconditions;

import java.io.File;

/** The standard {@link PythonVirtualEnvironment} provider for use throughout Cyder. */
public enum PevProvider {
/** The {@link PythonVirtualEnvironment} provider instance. */
INSTANCE;

/**
* The environment variable used to register the default {@link PythonVirtualEnvironment}
* used by Cyder. This environment variable must point to a valid executable for this
* provider to not throw when {@link #getProvider()} is invoked.
*/
public static final String CYDER_PYTHON_EXECUTABLE_PATH = "CYDER_PYTHON_EXECUTABLE_PATH";

/** The encapsulated provider instance. */
private final PythonVirtualEnvironment provider;

PevProvider() {
String cyderPythonExecutablePath = System.getenv(CYDER_PYTHON_EXECUTABLE_PATH);
File file = new File(cyderPythonExecutablePath);

PythonVirtualEnvironment localProvider;

try {
localProvider = PythonVirtualEnvironment.from(file);
} catch (Exception ignored) {
localProvider = null;
}

this.provider = localProvider;
}

/**
* Returns the provider instance.
* This object is created at runtime via the environment variable pointed to be
* {@link #CYDER_PYTHON_EXECUTABLE_PATH}. This method will throw if this variable is not
* present in the system or points to an invalid binary/executable.
*
* @return the provider instance
* @throws CyderProcessException if the provider is not available
*/
public PythonVirtualEnvironment getProvider() {
Preconditions.checkState(provider != null);
return provider;
}
}
54 changes: 31 additions & 23 deletions src/main/java/com/github/natche/cyderutils/process/ProcessUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

Expand Down Expand Up @@ -77,29 +79,35 @@ public static Future<ProcessResult> getProcessOutput(String command) throws Cyde
Preconditions.checkArgument(!command.trim().isEmpty());

String threadName = "getProcessOutput, command: " + StringUtil.surroundWithQuotes(command);
return Executors.newSingleThreadExecutor(new CyderThreadFactory(threadName)).submit(() -> {
ArrayList<String> standardOutput = new ArrayList<>();
ArrayList<String> errorOutput = new ArrayList<>();

try {
Process process = Runtime.getRuntime().exec(command);
process.getOutputStream().close();

String outputLine;
BufferedReader outReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((outputLine = outReader.readLine()) != null) standardOutput.add(outputLine);
outReader.close();

String errorLine;
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
while ((errorLine = errorReader.readLine()) != null) errorOutput.add(errorLine);
errorReader.close();
} catch (IOException e) {
throw new CyderProcessException(e);
}

return ProcessResult.from(standardOutput, errorOutput);
});
ExecutorService executorService = Executors.newSingleThreadExecutor(new CyderThreadFactory(threadName));

try {
return executorService.submit(() -> {
ArrayList<String> standardOutput = new ArrayList<>();
ArrayList<String> errorOutput = new ArrayList<>();

try {
Process process = Runtime.getRuntime().exec(command);
process.getOutputStream().close();

String outputLine;
BufferedReader outReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((outputLine = outReader.readLine()) != null) standardOutput.add(outputLine);
outReader.close();

String errorLine;
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
while ((errorLine = errorReader.readLine()) != null) errorOutput.add(errorLine);
errorReader.close();
} catch (IOException e) {
throw new CyderProcessException(e);
}

return ProcessResult.from(standardOutput, errorOutput);
});
} finally {
executorService.shutdown();
}
}

/**
Expand Down

This file was deleted.

Loading

0 comments on commit f677a15

Please sign in to comment.