18 Commits

Author SHA1 Message Date
47e8ec4f43 Version bump to v0.3.4
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-25 01:02:10 -05:00
c0c96c7a51 Updated documentation to use 17
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-25 00:57:06 -05:00
8cb640d804 Updated woodpecker config to use eclipse temurin Alpine image
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-24 23:18:33 -05:00
1189f4b61c Changed dnf commands to apt for the new image
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-01-15 17:07:25 -05:00
0f5b9eb45a Changed to using the eclipse temurin JDK builds
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-01-15 17:04:52 -05:00
9a567b7178 Version bump to v0.3.3
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2022-08-30 00:40:24 -04:00
88f63e040e Updated woodpecker config for new OpenJDK docker image
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-08-30 00:37:11 -04:00
aabf1d49a7 Updating target to Java 17 (using openjdk 18)
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2022-08-30 00:25:22 -04:00
3b789eb623 Updating target to Java 17
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2022-08-30 00:08:18 -04:00
ad0115e5f2 Fixed the install dependencies for the generated .deb package
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-07-17 20:13:22 -04:00
3c6079807e Version bump to v0.3.2
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-07-17 14:18:56 -04:00
b21d98ef73 Updated woodpecker config to skip tests on packaging (since they would've already been run earlier)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2022-07-17 14:14:55 -04:00
2e55876076 Changed the default config location to /etc/dragoon/config.toml; added a CLI option (-c) to specify a different config file.
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2022-07-17 14:04:48 -04:00
dad3e6c1cf Added .deb and .rpm package build step
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-07-17 13:32:58 -04:00
8c36855593 Removed some unused imports; closed an open file in a unit test
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-06-16 00:12:16 -04:00
6bce649458 Version bump to v0.3.1
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2022-05-20 08:10:17 -04:00
d017cb19f3 Added lsof to maven test docker image
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-05-20 08:08:40 -04:00
5bc2acac1d Added ability for the transcoder to determine if a video file is open in another program to avoid trying to transcode/remove a partially written file; reworked the main transcode loop to handle one file at a time instead of archiving everything, then transcoding, then cleanup
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/tag/woodpecker Pipeline failed
2022-05-20 07:58:52 -04:00
17 changed files with 442 additions and 154 deletions

View File

@ -1,14 +1,23 @@
pipeline:
test:
image: maven:3-jdk-11
image: maven:3-eclipse-temurin-17-alpine
commands:
- apk add lsof
- mvn test
build:
image: maven:3-jdk-11
image: maven:3-eclipse-temurin-17-alpine
commands:
- mvn clean compile assembly:single
package:
image: maven:3-eclipse-temurin-17-alpine
commands:
- apk add rpm
- mvn clean compile package -Dmaven.test.skip
when:
event: tag
gitea_release:
image: plugins/gitea-release
settings:
@ -17,6 +26,8 @@ pipeline:
base_url: https://git.metaunix.net
title: "${CI_COMMIT_TAG}"
files:
- target/Dragoon-*.jar
- target/dragoon-*.jar
- target/dragoon-*.deb
- target/rpm/dragoon/RPMS/noarch/dragoon-*.rpm
when:
event: tag

View File

@ -4,17 +4,15 @@ The Bit Goblin video transcoder.
## Building
Currently this project is targeting Java 11 LTS and uses Maven to manage the software lifecycle. Thus, you must have a Java 11 JDK and Maven installed to build this project.
*NOTE:* The targeted Java version will likely change to 17 LTS soon.
Currently this project is targeting Java 17 LTS and uses Maven to manage the software lifecycle. Thus, you must have a Java 17 JDK and Maven installed to build this project.
### Ubuntu
`sudo apt install openjdk-11-jdk maven`
`sudo apt install openjdk-17-jdk maven`
### Red Hat/Almalinux
`sudo dnf install java-11-openjdk-devel maven`
`sudo dnf install java-17-openjdk-devel maven`
### Actually Building

151
pom.xml
View File

@ -5,19 +5,48 @@
<modelVersion>4.0.0</modelVersion>
<groupId>tech.bitgoblin</groupId>
<artifactId>Dragoon</artifactId>
<version>0.3.0</version>
<artifactId>dragoon</artifactId>
<version>0.3.4</version>
<name>Dragoon</name>
<url>https://www.bitgoblin.tech</url>
<description>Automated video transcoder service.</description>
<inceptionYear>2022</inceptionYear>
<organization>
<name>Bit Goblin</name>
<url>https://www.bitgoblin.tech</url>
</organization>
<developers>
<developer>
<id>gballantine</id>
<name>Gregory Ballantine</name>
<email>gballantine@bitgoblin.tech</email>
</developer>
</developers>
<licenses>
<license>
<name>BSD</name>
<url>https://opensource.org/licenses/BSD-2-Clause</url>
<distribution>repo</distribution>
<comments>Simplified BSD license.</comments>
</license>
</licenses>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.tomlj</groupId>
<artifactId>tomlj</artifactId>
@ -42,7 +71,6 @@
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
@ -99,6 +127,118 @@
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<groupId>com.aerse.maven</groupId>
<artifactId>deb-maven-plugin</artifactId>
<version>1.16</version>
<executions>
<execution>
<id>package</id>
<phase>package</phase>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
<configuration>
<unixUserId>dragoon</unixUserId>
<unixGroupId>dragoon</unixGroupId>
<debBaseDir>${project.basedir}/src/build/deb</debBaseDir>
<installDir>/opt</installDir>
<osDependencies>
<openjdk-17-jre></openjdk-17-jre>
<ffmpeg></ffmpeg>
</osDependencies>
<javaServiceWrapper>false</javaServiceWrapper>
<generateVersion>false</generateVersion>
<fileSets>
<fileSet>
<source>${basedir}/src/build/deb</source>
<target>/</target>
</fileSet>
<fileSet>
<source>${basedir}/target/dragoon-${project.version}-jar-with-dependencies.jar</source>
<target>/opt/dragoon/dragoon.jar</target>
</fileSet>
</fileSets>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>rpm-maven-plugin</artifactId>
<version>2.2.0</version>
<executions>
<execution>
<id>generate-rpm</id>
<goals>
<goal>rpm</goal>
</goals>
</execution>
</executions>
<configuration>
<license>BSD 2-Clause</license>
<group>Applications/Multimedia</group>
<icon>src/main/resources/icon.gif</icon>
<packager>Bit Goblin</packager>
<prefix>/opt</prefix>
<changelogFile>${project.basedir}/src/build/changelog.txt</changelogFile>
<targetOs>linux</targetOs>
<mappings>
<mapping>
<directory>/opt/dragoon</directory>
<filemode>755</filemode>
<username>dragoon</username>
<groupname>dragoon</groupname>
</mapping>
<mapping>
<directory>/opt/dragoon/dragoon.jar</directory>
<filemode>755</filemode>
<username>dragoon</username>
<groupname>dragoon</groupname>
<sources>
<source>
<location>${basedir}/target/dragoon-${project.version}-jar-with-dependencies.jar</location>
</source>
</sources>
</mapping>
<mapping>
<directory>/etc/dragoon</directory>
<configuration>true</configuration>
<filemode>755</filemode>
<username>dragoon</username>
<groupname>dragoon</groupname>
<sources>
<source>
<location>${project.basedir}/src/build/deb/etc/dragoon</location>
</source>
</sources>
</mapping>
<mapping>
<directory>/etc/systemd/system/dragoon.service</directory>
<filemode>644</filemode>
<username>root</username>
<groupname>root</groupname>
<sources>
<source>
<location>${project.basedir}/src/build/deb/etc/systemd/system/dragoon.service</location>
</source>
</sources>
</mapping>
</mappings>
<requires>
<require>java-17-openjdk</require>
<require>ffmpeg</require>
</requires>
<preinstallScriptlet>
<script>echo "installing ${project.name} now"</script>
</preinstallScriptlet>
<postinstallScriptlet>
<scriptFile>src/build/scripts/postinstall.sh</scriptFile>
<fileEncoding>utf-8</fileEncoding>
<filter>true</filter>
</postinstallScriptlet>
</configuration>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
@ -117,6 +257,5 @@
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

1
src/build/changelog.txt Normal file
View File

@ -0,0 +1 @@
# Check https://git.metaunix.net/bitgoblin/dragoon/releases for the current changelog

View File

@ -0,0 +1,8 @@
# This example transcodes footage to DNxHD 1080p60 for use in video editors like DaVinci Resolve.
[transcoder]
repo_path = '~/videos' # location of the videos to transcode
video_format = 'mov' # video container format
video_codec = 'dnxhd' # video codec to use
video_parameters = 'scale=1920x1080,fps=60,format=yuv422p' # video extra format parameters flag - this will be broken later into separate attributes
video_profile = 'dnxhr_hq' # DNxHD has multiple presets for various video qualities
audio_codec = 'pcm_s16le' # audio codec to use

View File

@ -0,0 +1,11 @@
[Unit]
Description=Dragoon video transcoder service
[Service]
User=dragoon
Group=dragoon
ExecStart=/usr/bin/java -jar '/opt/dragoon/dragoon.jar'
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,23 @@
#!/bin/sh
GETENT_USER=$(getent passwd dragoon)
GETENT_GROUP=$(getent group dragoon)
# Create the dragoon user if it doesn't already exist
if [ "$GETENT_USER" = "" ]; then
useradd -r dragoon
else
echo "The 'dragoon' user already exists, skipping creation."
fi
# Create the dragoon group if it doesn't already exist
if [ "$GETENT_GROUP" = "" ]; then
groupadd dragoon
usermod -aG dragoon dragoon
else
echo "The 'dragoon' group already exists, skipping creation."
fi
# Change the directory ownership of /opt and /etc
chown -R dragoon:dragoon /etc/dragoon
chown -R dragoon:dragoon /opt/dragoon

View File

@ -1,5 +1,7 @@
package tech.bitgoblin;
import org.apache.commons.cli.ParseException;
import tech.bitgoblin.config.Cmd;
import tech.bitgoblin.config.Config;
import tech.bitgoblin.transcoder.RunTranscoderTask;
import tech.bitgoblin.transcoder.Transcoder;
@ -12,13 +14,15 @@ import java.util.Timer;
*/
public class App {
private static final String configFile = "~/.config/dragoon.toml";
private static final int msToMinutes = 60 * 1000;
public static void main(String[] args) {
public static void main(String[] args) throws ParseException {
// parse command-line options
Cmd cmd = new Cmd(args);
// read our config file
Config c = new Config(configFile);
Config c = new Config(cmd.getConfigPath());
// create new Transcoder object and start the service
Transcoder t = new Transcoder(c);
Timer timer = new Timer();

View File

@ -0,0 +1,30 @@
package tech.bitgoblin.config;
import org.apache.commons.cli.*;
public class Cmd {
private String configPath = "/etc/dragoon/config.toml";
public Cmd(String[] args) throws ParseException {
Options options = new Options();
Option configPath = new Option("c", "configPath", true, "configuration file path (defaults to /etc/dragoon/config.toml)");
configPath.setRequired(false);
options.addOption(configPath);
CommandLineParser parser = new DefaultParser();
HelpFormatter formatter = new HelpFormatter();
CommandLine cmd = parser.parse(options, args);
// set the configPath variable if the option was passed to the program
if (cmd.hasOption("configPath")) {
this.configPath = cmd.getOptionValue("configPath");
}
}
public String getConfigPath() {
return this.configPath;
}
}

View File

@ -1,6 +1,5 @@
package tech.bitgoblin.config;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

View File

@ -1,6 +1,6 @@
package tech.bitgoblin.io;
import java.io.File;
import java.io.*;
public class IOUtils {
@ -24,4 +24,23 @@ public class IOUtils {
return path;
}
// checks to see if a file is currently locked/being written to.
public static boolean isFileLocked(File file) throws IOException {
String[] cmd = {"lsof", file.toString()};
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader reader = new BufferedReader(new InputStreamReader(
process.getInputStream()));
boolean isOpen = false; // we'll change this if lsof returns that the file is open
String s;
while ((s = reader.readLine()) != null) {
if (s.endsWith(file.toString())) {
isOpen = true;
}
}
return isOpen;
}
}

View File

@ -40,9 +40,8 @@ public class Repository {
}
// archives files in the ingest directory
public void archiveIngest(File[] sourceFiles) {
for (File f : sourceFiles) {
Path filePath = Path.of(f.toString());
public void archiveFile(File sourceFile) {
Path filePath = Path.of(sourceFile.toString());
String filename = filePath.getFileName().toString();
String archivePath = Paths.get(this.archivePath, filename).toString();
@ -52,13 +51,10 @@ public class Repository {
throw new RuntimeException(e);
}
}
}
// clean up the ingest directory once we're done
public void cleanupIngest(File[] sourceFiles) {
for (File f : sourceFiles) {
f.delete();
}
public void cleanupFile(File sourceFile) {
sourceFile.delete();
}
// returns the repository's path

View File

@ -1,5 +1,6 @@
package tech.bitgoblin.transcoder;
import java.io.IOException;
import java.util.TimerTask;
public class RunTranscoderTask extends TimerTask {
@ -13,7 +14,11 @@ public class RunTranscoderTask extends TimerTask {
@Override
public void run() {
// archive the files
try {
transcoder.run();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -4,11 +4,7 @@ import java.io.File;
import java.io.IOException;
import java.lang.InterruptedException;
import java.lang.Process;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Timer;
import tech.bitgoblin.Logger;
import tech.bitgoblin.config.Config;
@ -31,36 +27,45 @@ public class Transcoder {
}
// create a periodic timer task and start it
public void run() {
public void run() throws IOException {
// pull list of files to transcode
File[] sourceFiles = this.repo.searchIngest();
// check if we have anything to process
if (sourceFiles.length == 0) {
Logger.logger.info("There is nothing in ingest, skipping transcode run.");
}
// loop through each file
for (File sf : sourceFiles) {
// check if the file is open before attempting to transcode it
if (IOUtils.isFileLocked(sf)) {
Logger.logger.info(String.format("Skipping %s because it is open in another program.", sf.toString()));
continue;
}
// archive files
this.repo.archiveIngest(sourceFiles);
this.repo.archiveFile(sf);
// transcode files
this.transcode(sourceFiles);
this.transcodeFile(sf);
// cleanup old files
this.repo.cleanupIngest(sourceFiles);
this.repo.cleanupFile(sf);
}
// end output
Logger.logger.info("------------ End of transcoding ------------");
Logger.logger.info("");
}
// transcode files in the working directory
public void transcode(File[] sourceFiles) {
// check if the ingest directory is empty
if (sourceFiles.length == 0) {
Logger.logger.info("There is nothing to transcode in ingest.");
return;
}
// transcode
Logger.logger.info("Transcoding video files ingest...");
for (File f : sourceFiles) {
String filePath = f.toString().substring(0, f.toString().lastIndexOf("."));
public void transcodeFile(File sourceFile) throws IOException {
String filePath = sourceFile.toString().substring(0, sourceFile.toString().lastIndexOf("."));
String filename = Paths.get(filePath).getFileName().toString();
String outputPath = Paths.get(this.repo.getOutputPath(), String.format("%s.mov", filename)).toString();
String cmd = String.format("%s -i %s -y -f %s -c:v %s -vf %s -profile:v %s -c:a %s %s",
this.ffmpeg_path, // FFMPEG binary path
f.toString(), // input file
sourceFile.toString(), // input file
this.config.getString("transcoder.video_format"), // video container format
this.config.getString("transcoder.video_codec"), // video codec
this.config.getString("transcoder.video_parameters"), // video format
@ -74,16 +79,11 @@ public class Transcoder {
try {
Process process = pb.start();
int ret = process.waitFor();
Logger.logger.info("Program exited with code: %d", ret);
Logger.logger.info(String.format("Program exited with code: %d", ret));
Logger.logger.info("");
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
// end output
Logger.logger.info("------------ End of transcoding ------------");
Logger.logger.info("");
}
}

BIN
src/main/resources/icon.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@ -0,0 +1,17 @@
package tech.bitgoblin.config;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.apache.commons.cli.ParseException;
public class CmdTest {
@Test
public void shouldDefaultToEtc() throws ParseException {
Cmd cmd = new Cmd(new String[]{});
assertTrue(cmd.getConfigPath().equals("/etc/dragoon/config.toml"));
}
}

View File

@ -1,15 +1,20 @@
package tech.bitgoblin.io;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
public class IOUtilsTest {
private static String testDir = "test-temp";
private static String testFile = "test.txt";
@Test
public void shouldCreateDirectory() {
@ -28,9 +33,31 @@ public class IOUtilsTest {
IOUtils.resolveTilda("~test");
}
@Test
public void fileShouldBeLocked() throws IOException {
RandomAccessFile raf = new RandomAccessFile(testFile, "rw");
assertTrue(IOUtils.isFileLocked(new File(testFile)));
raf.close();
}
@Test
public void fileShouldNotBeLocked() throws IOException {
RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");
raf.close();
assertFalse(IOUtils.isFileLocked(new File(testFile)));
}
@BeforeClass
// ensure that test files are created before running tests
public static void setup() throws IOException {
new File(testFile).createNewFile();
}
@AfterClass
// ensure test files are cleaned up from the environment
public static void cleanup() {
new File(testDir).delete();
new File(testFile).delete();
}
}