diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ab9e636d..2e75bf85 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,14 +42,14 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: - distribution: 'adopt' - java-version: 17 + distribution: 'temurin' + java-version: 21 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -57,7 +57,7 @@ jobs: # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: # be careful not to include ~/.m2/settings.xml which contains credentials path: | @@ -70,4 +70,4 @@ jobs: run: bash mvnw clean install -B -DskipTests - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b049672e..6f35ebba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,12 +19,12 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: - distribution: 'adopt' - java-version: 11 + distribution: 'temurin' + java-version: 21 - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: # be careful not to include ~/.m2/settings.xml which contains credentials path: | diff --git a/.github/workflows/sonar-analysis.yml b/.github/workflows/sonar-analysis.yml index 972982e2..ff4d47aa 100644 --- a/.github/workflows/sonar-analysis.yml +++ b/.github/workflows/sonar-analysis.yml @@ -31,22 +31,22 @@ jobs: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'adopt' - java-version: 17 + distribution: 'temurin' + java-version: 21 - name: Install ffmpeg on Ubuntu run: sudo apt-get update && sudo apt-get install -y ffmpeg && ffmpeg -version - name: Cache SonarCloud packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: # be careful not to include ~/.m2/settings.xml which contains credentials path: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fda833c4..cf2618ad 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -25,21 +25,23 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, macos-latest, windows-latest ] - java-version: [ 8, 11, 17 ] + os: + - ubuntu-latest + - macos-latest + - windows-latest fail-fast: false steps: - uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'adopt' - java-version: ${{ matrix.java-version }} + distribution: 'temurin' + java-version: 21 - name: Cache Maven Packages - uses: actions/cache@v3 + uses: actions/cache@v4 with: # be careful not to include ~/.m2/settings.xml which contains credentials path: | @@ -49,35 +51,28 @@ jobs: restore-keys: ${{ runner.os }}-m2 - name: Cache Test Artifacts - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | .artifacts key: ${{ runner.os }}-artifacts-${{ hashFiles('**/Artifacts.java') }} - name: Install ffmpeg on Ubuntu - run: sudo apt-get update && sudo apt-get install -y ffmpeg && ffmpeg -version + run: sudo apt-get update && sudo apt-get install -y ffmpeg if: startsWith(matrix.os, 'ubuntu') - + - name: Install ffmpeg on MacOS - run: brew install ffmpeg && ls -lhtr /usr/local/opt/ffmpeg/ && ffmpeg -version + run: brew install ffmpeg if: startsWith(matrix.os, 'macos') - name: Install ffmpeg on Windows - run: choco install ffmpeg && ffmpeg -version + run: choco install ffmpeg if: startsWith(matrix.os, 'windows') - - name: Build on Ubuntu - run: bash mvnw clean package -B - if: startsWith(matrix.os, 'ubuntu') + - run: ffmpeg -version - - name: Build on MacOS - run: bash mvnw clean package -B - if: startsWith(matrix.os, 'macos') - - - name: Build on Windows + - name: Build and test run: ./mvnw clean package -B - if: startsWith(matrix.os, 'windows') test-release: runs-on: ubuntu-latest @@ -85,15 +80,15 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: - distribution: 'adopt' - java-version: 11 + distribution: 'temurin' + java-version: 21 - name: Set up RANDOM GPG key run: gpg --quick-generate-key --batch --passphrase '' test42 - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: # be careful not to include ~/.m2/settings.xml which contains credentials path: | diff --git a/pom.xml b/pom.xml index 02eb3274..86bd55e9 100644 --- a/pom.xml +++ b/pom.xml @@ -70,13 +70,13 @@ com.grack nanojson - 1.7 + 1.9 ch.qos.logback logback-classic - 1.2.10 + 1.5.7 test @@ -150,7 +150,7 @@ org.apache.maven.plugins maven-source-plugin - 3.2.1 + 3.3.1 attach-sources @@ -180,7 +180,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.0.1 + 3.2.5 sign-artifacts @@ -241,7 +241,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.1.2 + 3.5.0 4 checkstyle.xml diff --git a/src/main/java/com/github/kokorin/jaffree/util/ParseUtil.java b/src/main/java/com/github/kokorin/jaffree/util/ParseUtil.java index 604cb23f..88cb3e37 100644 --- a/src/main/java/com/github/kokorin/jaffree/util/ParseUtil.java +++ b/src/main/java/com/github/kokorin/jaffree/util/ParseUtil.java @@ -30,8 +30,7 @@ */ @SuppressWarnings("checkstyle:MagicNumber") public final class ParseUtil { - - private static final String KBYTES_SUFFIX = "kB"; + private static final String[] KBYTES_SUFFIXES = {"kB", "KiB"}; private static final String KBITS_PER_SECOND_SUFFIX = "kbits/s"; private static final String SPEED_SUFFIX = "x"; private static final String PERCENT_SUFFIX = "%"; @@ -78,33 +77,40 @@ public static Double parseDouble(final String value) { } /** - * Parses size in kilobytes without exception. + * Parses size in kibibytes without exception. * * @param value string to parse * @return parsed long or null if value can't be parsed */ public static Long parseSizeInBytes(final String value) { - Long result = parseSizeInKiloBytes(value); + Long result = parseSizeInKibiBytes(value); if (result == null) { return null; } - return result * 1000; + return result * 1024; } /** - * Parses size in kilobytes without exception. + * Parses size in kibibytes without exception. * * @param value string to parse * @return parsed long or null if value can't be parsed */ - public static Long parseSizeInKiloBytes(final String value) { + public static Long parseSizeInKibiBytes(final String value) { if (value == null || value.isEmpty()) { return null; } - return parseLongWithSuffix(value.trim(), KBYTES_SUFFIX); + final String trimmedValue = value.trim(); + Long result = null; + + for (int i = 0; i < KBYTES_SUFFIXES.length && result == null; i++) { + result = parseLongWithSuffix(trimmedValue, KBYTES_SUFFIXES[i]); + } + + return result; } /** diff --git a/src/test/java/com/github/kokorin/jaffree/ffmpeg/FFmpegTest.java b/src/test/java/com/github/kokorin/jaffree/ffmpeg/FFmpegTest.java index fdc4e57f..c1c99d44 100644 --- a/src/test/java/com/github/kokorin/jaffree/ffmpeg/FFmpegTest.java +++ b/src/test/java/com/github/kokorin/jaffree/ffmpeg/FFmpegTest.java @@ -43,6 +43,7 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -240,6 +241,43 @@ public void onProgress(FFmpegProgress progress) { .execute(); } + @Test + public void testFrameCountingWithStreamCopyAndProgressListener() throws Exception { + final AtomicBoolean ffmpegHasStreamCopyBug = new AtomicBoolean(false); + + final OutputListener outputListener = message -> { + // Don't check the frame count in at FFmpeg 6.1.x and 7.0.x due to a stream copy bug, + // which is addressed on the master branch, see: + // https://github.com/FFmpeg/FFmpeg/commit/598f541ba49cb682dcd74e86858c9a4985149e1f + if (message.contains("ffmpeg version 6.1") || message.contains("ffmpeg version 7.0")) { + ffmpegHasStreamCopyBug.set(true); + } + }; + + final AtomicReference frameRef = new AtomicReference<>(); + + final ProgressListener progressListener = new ProgressListener() { + @Override + public void onProgress(FFmpegProgress progress) { + System.out.println(progress); + frameRef.set(progress.getFrame()); + } + }; + + final FFmpegResult result = FFmpeg.atPath(Config.FFMPEG_BIN) + .addInput(UrlInput.fromPath(Artifacts.VIDEO_NUT)) + .addOutput(new NullOutput()) + .setOutputListener(outputListener) + .setProgressListener(progressListener) + .execute(); + + if (ffmpegHasStreamCopyBug.get()) { + LOGGER.warn("Detected buggy FFmpeg version, frame count not checked"); + } else { + assertNotNull(frameRef.get()); + } + } + @Test public void testForceStopWithThreadInterruption() throws Exception { Path tempDir = Files.createTempDirectory("jaffree"); @@ -519,14 +557,24 @@ public void testExceptionIsThrownIfFfmpegExitsWithError() { .addOutput(new NullOutput()) .execute(); } catch (JaffreeAbnormalExitException e) { - assertEquals("Process execution has ended with non-zero status: 1. Check logs for detailed error message.", e.getMessage()); - assertEquals(1, e.getProcessErrorLogMessages().size()); - assertEquals("[error] non_existent.mp4: No such file or directory", e.getProcessErrorLogMessages().get(0).message); + if ("Process execution has ended with non-zero status: 254. Check logs for detailed error message.".equals(e.getMessage())) { + // FFmpeg 6+ + assertEquals(3, e.getProcessErrorLogMessages().size()); + assertEquals("[error] Error opening input file non_existent.mp4.", e.getProcessErrorLogMessages().get(1).message); + } else if ("Process execution has ended with non-zero status: -2. Check logs for detailed error message.".equals(e.getMessage())) { + // FFmpeg 7 + assertEquals(3, e.getProcessErrorLogMessages().size()); + assertEquals("[error] Error opening input file non_existent.mp4.", e.getProcessErrorLogMessages().get(1).message); + } else if ("Process execution has ended with non-zero status: 1. Check logs for detailed error message.".equals(e.getMessage())) { + assertEquals(1, e.getProcessErrorLogMessages().size()); + assertEquals("[error] non_existent.mp4: No such file or directory", e.getProcessErrorLogMessages().get(0).message); + } else { + fail("Unknown FFmpeg output format (update test code!): " + e.getMessage()); + } return; } fail("JaffreeAbnormalExitException should have been thrown!"); - } @Test diff --git a/src/test/java/com/github/kokorin/jaffree/util/ParseUtilTest.java b/src/test/java/com/github/kokorin/jaffree/util/ParseUtilTest.java index e932cf7c..111a89a1 100644 --- a/src/test/java/com/github/kokorin/jaffree/util/ParseUtilTest.java +++ b/src/test/java/com/github/kokorin/jaffree/util/ParseUtilTest.java @@ -63,13 +63,25 @@ public void parseLogLevel() { } @Test - public void parsResult() throws Exception { + public void parseKibiByteFormats() { + final Long oldFormat = ParseUtil.parseSizeInKibiBytes("2904kB"); + Assert.assertEquals(2904L, oldFormat.longValue()); + + final Long newFormat = ParseUtil.parseSizeInKibiBytes("2904KiB"); + Assert.assertEquals(2904L, newFormat.longValue()); + + final Long unknownFormat = ParseUtil.parseSizeInKibiBytes("2904KB"); + Assert.assertNull(unknownFormat); + } + + @Test + public void parseResult() throws Exception { String value = "video:1417kB audio:113kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown"; FFmpegResult result = ParseUtil.parseResult(value); Assert.assertNotNull(result); - Assert.assertEquals((Long) 1_417_000L, result.getVideoSize()); - Assert.assertEquals((Long) 113_000L, result.getAudioSize()); + Assert.assertEquals((Long) 1_451_008L, result.getVideoSize()); + Assert.assertEquals((Long) 115_712L, result.getAudioSize()); Assert.assertEquals((Long) 0L, result.getSubtitleSize()); Assert.assertEquals((Long) 0L, result.getOtherStreamsSize()); Assert.assertEquals((Long) 0L, result.getGlobalHeadersSize()); @@ -98,5 +110,4 @@ public void parseResultWhichDoesntContainResult() throws Exception { Assert.assertNull(result); } - } \ No newline at end of file