diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 83e44400..84e8dd1f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,54 +1,87 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven - name: Java CI with Maven on: push: branches: [ "master" ] + tags: [ "ffmpeg-*" ] pull_request: branches: [ "master" ] + workflow_dispatch: + inputs: + skipTests: + description: 'Skip tests during publish' + required: false + default: 'false' jobs: - build: + test: runs-on: ubuntu-latest - # Enable debugging to help resolve: - # https://github.com/federicocarboni/setup-ffmpeg/issues/19 - environment: debug strategy: fail-fast: false matrix: - # Long term supported versions java-version: [11, 17, 21] - # TODO Should we test locales? The old travis setup did, see: - # https://github.com/bramp/ffmpeg-cli-wrapper/pull/55 - name: JDK ${{ matrix.java-version }} steps: - - uses: actions/checkout@v6 - - - name: Set up FFmpeg - uses: FedericoCarboni/setup-ffmpeg@v3 - id: setup-ffmpeg - with: - ffmpeg-version: release - - - name: Set up JDK ${{ matrix.java-version }} - uses: actions/setup-java@v5 - with: - java-version: ${{ matrix.java-version }} - distribution: 'temurin' - cache: maven - - - name: Compile with Maven - run: mvn --batch-mode --update-snapshots compile - - - name: Test with Maven, Package and Verify with Maven - run: mvn --batch-mode --update-snapshots verify -Dgpg.skip - - # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - - name: Update dependency graph - uses: advanced-security/maven-dependency-submission-action@v5 - continue-on-error: true + - uses: actions/checkout@v4 + + - name: Set up FFmpeg + uses: FedericoCarboni/setup-ffmpeg@v3 + with: + ffmpeg-version: release + + - name: Set up JDK ${{ matrix.java-version }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java-version }} + distribution: 'temurin' + cache: maven + + - name: Test with Maven + run: mvn --batch-mode verify -Dgpg.skip -DskipTests=${{ github.event.inputs.skipTests || 'false' }} + + - name: Update dependency graph + if: matrix.java-version == '21' && github.event_name == 'push' && github.ref == 'refs/heads/master' + uses: advanced-security/maven-dependency-submission-action@v4 + continue-on-error: true + + publish: + needs: test + # Only publish on tags (ffmpeg-*) or manual dispatch + # and only if it's not a pull request + if: | + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/ffmpeg-')) || + (github.event_name == 'workflow_dispatch') + runs-on: ubuntu-latest + environment: publish + steps: + - uses: actions/checkout@v4 + + - name: Set up FFmpeg + uses: FedericoCarboni/setup-ffmpeg@v3 + with: + ffmpeg-version: release + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + server-id: central + server-username: CENTRAL_USERNAME + server-password: CENTRAL_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + cache: 'maven' + + - name: Publish to Sonatype Central Portal + run: | + mvn clean deploy \ + -DskipTests=true \ + --no-transfer-progress \ + -P java11plus + env: + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/README.md b/README.md index d9386ad1..ebb6b250 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ We currently support Java 11 and above. Use Maven to install the dependency. net.bramp.ffmpeg ffmpeg - 0.8.1-SNAPSHOT + 0.9.0-SNAPSHOT ``` diff --git a/pom.xml b/pom.xml index 4d6f410f..2ae50880 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 net.bramp.ffmpeg ffmpeg - 0.8.1-SNAPSHOT + 0.9.0-SNAPSHOT FFmpeg Wrapper Simple Java wrapper around FFmpeg command-line interface @@ -11,7 +11,6 @@ https://github.com/bramp/ffmpeg-cli-wrapper scm:git:git@github.com:bramp/ffmpeg-cli-wrapper.git - ffmpeg-0.7.0 @@ -208,13 +207,9 @@ - ossrh + central https://oss.sonatype.org/content/repositories/snapshots - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - @@ -314,9 +309,9 @@ 3.0.1 - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 com.spotify.fmt @@ -431,6 +426,12 @@ sign + + + --pinentry-mode + loopback + + @@ -447,13 +448,14 @@ - org.sonatype.plugins - nexus-staging-maven-plugin + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 true - ossrh - https://oss.sonatype.org/ - true + central + true + published @@ -732,7 +734,7 @@ - Java 11+ + java11plus [11,) diff --git a/src/main/java/net/bramp/ffmpeg/FFmpegUtils.java b/src/main/java/net/bramp/ffmpeg/FFmpegUtils.java index 53ba3b8c..ca92a3a9 100644 --- a/src/main/java/net/bramp/ffmpeg/FFmpegUtils.java +++ b/src/main/java/net/bramp/ffmpeg/FFmpegUtils.java @@ -24,7 +24,7 @@ public final class FFmpegUtils { static final Gson gson = FFmpegUtils.setupGson(); static final Pattern BITRATE_REGEX = Pattern.compile("(\\d+(?:\\.\\d+)?)kbits/s"); - static final Pattern TIME_REGEX = Pattern.compile("(\\d+):(\\d+):(\\d+(?:\\.\\d+)?)"); + static final Pattern TIME_REGEX = Pattern.compile("(-?)(\\d+):(\\d+):(\\d+(?:\\.\\d+)?)"); static final CharMatcher ZERO = CharMatcher.is('0'); FFmpegUtils() { @@ -55,9 +55,11 @@ public static String millisecondsToString(long milliseconds) { * @return the timecode representation. */ public static String toTimecode(long duration, TimeUnit units) { - // FIXME Negative durations are also supported. - // https://www.ffmpeg.org/ffmpeg-utils.html#Time-duration - checkArgument(duration >= 0, "duration must be positive"); + String prefix = ""; + if (duration < 0) { + prefix = "-"; + duration = Math.abs(duration); + } long nanoseconds = units.toNanos(duration); // TODO This will clip at Long.MAX_VALUE long seconds = units.toSeconds(duration); @@ -69,11 +71,14 @@ public static String toTimecode(long duration, TimeUnit units) { long hours = MINUTES.toHours(minutes); minutes -= HOURS.toMinutes(hours); + String result; if (ns == 0) { - return String.format("%02d:%02d:%02d", hours, minutes, seconds); + result = String.format("%02d:%02d:%02d", hours, minutes, seconds); + } else { + result = ZERO.trimTrailingFrom(String.format("%02d:%02d:%02d.%09d", hours, minutes, seconds, ns)); } - return ZERO.trimTrailingFrom(String.format("%02d:%02d:%02d.%09d", hours, minutes, seconds, ns)); + return prefix + result; } /** @@ -95,11 +100,12 @@ public static long fromTimecode(String time) { throw new IllegalArgumentException("invalid time '" + time + "'"); } - long hours = Long.parseLong(m.group(1)); - long mins = Long.parseLong(m.group(2)); - double secs = Double.parseDouble(m.group(3)); + long sign = m.group(1).equals("-") ? -1 : 1; + long hours = Long.parseLong(m.group(2)); + long mins = Long.parseLong(m.group(3)); + double secs = Double.parseDouble(m.group(4)); - return HOURS.toNanos(hours) + MINUTES.toNanos(mins) + (long) (SECONDS.toNanos(1) * secs); + return sign * (HOURS.toNanos(hours) + MINUTES.toNanos(mins) + (long) (SECONDS.toNanos(1) * secs)); } /** diff --git a/src/test/java/net/bramp/ffmpeg/FFmpegUtilsTest.java b/src/test/java/net/bramp/ffmpeg/FFmpegUtilsTest.java index 4993fb1b..035bc69f 100644 --- a/src/test/java/net/bramp/ffmpeg/FFmpegUtilsTest.java +++ b/src/test/java/net/bramp/ffmpeg/FFmpegUtilsTest.java @@ -23,22 +23,23 @@ public void testMillisecondsToString() { assertEquals("00:00:00.001", millisecondsToString(1)); } - @Test(expected = IllegalArgumentException.class) + @Test @SuppressWarnings({"deprecation", "InlineMeInliner"}) public void testMillisecondsToStringNegative() { - millisecondsToString(-1); + assertEquals("-00:00:00.001", millisecondsToString(-1)); } - @Test(expected = IllegalArgumentException.class) + @Test @SuppressWarnings({"deprecation", "InlineMeInliner"}) - public void testMillisecondsToStringNegativeMinValue() { - millisecondsToString(Long.MIN_VALUE); + public void testMillisecondsToStringNegativeLarge() { + assertEquals("-34:17:36.789", millisecondsToString(-123456789)); } @Test public void testToTimecode() { assertEquals("00:00:00", toTimecode(0, TimeUnit.NANOSECONDS)); assertEquals("00:00:00.000000001", toTimecode(1, TimeUnit.NANOSECONDS)); + assertEquals("-00:00:00.000000001", toTimecode(-1, TimeUnit.NANOSECONDS)); assertEquals("00:00:00.000001", toTimecode(1, TimeUnit.MICROSECONDS)); assertEquals("00:00:00.001", toTimecode(1, TimeUnit.MILLISECONDS)); assertEquals("00:00:01", toTimecode(1, TimeUnit.SECONDS)); @@ -49,6 +50,7 @@ public void testToTimecode() { @Test public void testFromTimecode() { assertEquals(63123000000L, fromTimecode("00:01:03.123")); + assertEquals(-63123000000L, fromTimecode("-00:01:03.123")); assertEquals(63000000000L, fromTimecode("00:01:03")); assertEquals(5025678000000L, fromTimecode("01:23:45.678")); assertEquals(0, fromTimecode("00:00:00"));