Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6ae7b03
Call ffmpeg using a 2pass encoding, to get the best quality and a gua…
MartelliEnrico Aug 28, 2025
e2f180c
Extract helper method
MartelliEnrico Aug 28, 2025
5fc9387
Reuse constraints constants
MartelliEnrico Aug 28, 2025
8742939
Fix passlog file handling
MartelliEnrico Aug 28, 2025
35a3f83
Fix concurrency error
MartelliEnrico Aug 28, 2025
10d60a0
Suppress file deletion error
MartelliEnrico Aug 28, 2025
d651a49
Improve ProcessHelper ergonomics
MartelliEnrico Aug 31, 2025
fc630ed
Nits
MartelliEnrico Aug 31, 2025
2722b50
Use ffmpeg also for image conversion
MartelliEnrico Sep 7, 2025
b5d03ad
Added JSpecify support, updated baseline, fixed some nits
MartelliEnrico Sep 7, 2025
534561d
Properly use @NullMarked annotation, fix some possible NPE surfaces
MartelliEnrico Sep 7, 2025
04f820f
More nits and fixes
MartelliEnrico Sep 7, 2025
aab0e8e
Maybe fix process helper problem
MartelliEnrico Sep 8, 2025
eb47dd5
Disable qodana for the moment
MartelliEnrico Sep 8, 2025
d10a593
Fix windows script, use env to enable Qodana
MartelliEnrico Sep 8, 2025
7abf3c7
Revert regex
MartelliEnrico Sep 10, 2025
984e551
Code review fixes
MartelliEnrico Sep 11, 2025
21c217b
Remove unneeded junit platform module
rob93c Sep 12, 2025
c91070f
Suppress all jave logs unless they are errors
rob93c Sep 12, 2025
ce6606b
Rename local variable
rob93c Sep 12, 2025
43e0f9e
Updated useful information to reflect current dependencies being used
rob93c Sep 12, 2025
fd3bb3b
Add missing constructors to BaseException
rob93c Sep 13, 2025
e123075
Minor fixes
rob93c Sep 20, 2025
011172d
Change jspecify to be a compile only dependency
rob93c Sep 20, 2025
17c9fb3
Suppress unneeded warning
rob93c Sep 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@ FROM eclipse-temurin:24-alpine AS builder

WORKDIR /app
COPY . .
RUN --mount=type=cache,target=/root/.gradle ./gradlew jlink shadowJar
RUN --mount=type=cache,target=/root/.gradle ./gradlew installDist

# bump: alpine /FROM alpine:([\d.]+)/ docker:alpine|^3
# bump: alpine link "Release notes" https://alpinelinux.org/posts/Alpine-$LATEST-released.html
FROM alpine:3.22.1 AS bot

RUN apk --no-cache add libwebp-tools

# bump: ffmpeg /static-ffmpeg:([\d.]+)/ docker:mwader/static-ffmpeg|~7.0
COPY --from=mwader/static-ffmpeg:7.0.2 /ffmpeg /usr/bin/
ENV FFMPEG_PATH=/usr/bin/ffmpeg
COPY --from=builder /app/build/install/Stickerify/ .

COPY --from=builder /app/build/jlink/jre jre
COPY --from=builder /app/build/libs/*-all.jar Stickerify.jar

CMD ["jre/bin/java", "-Dcom.sksamuel.scrimage.webp.binary.dir=/usr/bin", "-jar", "Stickerify.jar"]
ENV CONCURRENT_PROCESSES=5
ENV FFMPEG_PATH=/usr/bin/ffmpeg
CMD ["./bin/Stickerify"]
4 changes: 2 additions & 2 deletions Railway.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ ARG DOCKER_TAG=latest
FROM rob93c/stickerify:$DOCKER_TAG
ARG STICKERIFY_TOKEN
ARG LOG_LEVEL
ARG CONCURRENT_THREADS
ARG CONCURRENT_PROCESSES
ENV STICKERIFY_TOKEN=$STICKERIFY_TOKEN \
LOG_LEVEL=$LOG_LEVEL \
CONCURRENT_THREADS=$CONCURRENT_THREADS
CONCURRENT_PROCESSES=$CONCURRENT_PROCESSES
57 changes: 20 additions & 37 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,30 @@ import com.github.stickerifier.stickerify.JlinkJavaLauncher
import com.github.stickerifier.stickerify.JlinkTask

plugins {
id('application')
id('java')
id('jacoco')
alias(libs.plugins.shadow)
id('application')
}

repositories {
mavenCentral()
}

dependencies {
implementation(libs.batik)
implementation(libs.gson)
implementation(libs.imageio.batik)
implementation(libs.imageio.psd)
implementation(libs.jave)
implementation(libs.logback.classic)
implementation(libs.logback.core)
implementation(libs.scrimage.core)
implementation(libs.scrimage.formats.extra)
implementation(libs.scrimage.webp)
implementation(libs.slf4j.api)
implementation(libs.telegram.bot.api)
implementation(libs.tika)

testImplementation(libs.hamcrest)
testImplementation(libs.junit.jupiter)
testImplementation(libs.mockwebserver)
testRuntimeOnly(libs.junit.platform)

constraints {
implementation('org.apache.commons:commons-lang3') {
version {
strictly('[3.18.0, 4)')
}
because('CVE-2025-48924: Apache Commons Lang is vulnerable to Uncontrolled Recursion when processing long inputs')
}
}
}

group = 'com.github.stickerifier'
version = '1.0'
version = '2.0'
description = 'Telegram bot to convert medias in the format required to be used as Telegram stickers'

java.toolchain {
Expand All @@ -58,7 +40,12 @@ updateDaemonJvm {

def jlink = tasks.register('jlink', JlinkTask) {
options = ['--strip-debug', '--no-header-files', '--no-man-pages']
modules = ['java.desktop', 'java.instrument', 'java.naming', 'java.sql', 'jdk.crypto.ec', 'jdk.unsupported']
modules = [
'java.instrument', // for junit
'java.naming', // for logback
'java.sql', // for tika
'jdk.unsupported', // for gson
]
Comment thread
MartelliEnrico marked this conversation as resolved.
includeModulePath = false

group = 'build'
Expand All @@ -73,7 +60,7 @@ test {
finalizedBy(jacocoTestReport)

testLogging {
events 'passed', 'failed', 'skipped'
events('passed', 'failed', 'skipped')
}
}

Expand All @@ -99,19 +86,15 @@ application {
mainClass = 'com.github.stickerifier.stickerify.runner.Main'
}

shadowJar {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
mergeServiceFiles()
failOnDuplicateEntries = true

exclude('dist_webp_binaries/',
'META-INF/LICENSE',
'META-INF/LICENSE.txt',
'META-INF/NOTICE',
'META-INF/NOTICE.txt',
'license/LICENSE',
'license/LICENSE.dom-documentation.txt',
'license/LICENSE.dom-software.txt',
'license/NOTICE',
'license/README.dom.txt')
distributions {
main {
contents {
from(jlink)
}
}
}

tasks.named('startScripts', CreateStartScripts) {
unixStartScriptGenerator.template = resources.text.fromFile('src/main/resources/customUnixStartScript.txt')
windowsStartScriptGenerator.template = resources.text.fromFile('src/main/resources/customWindowsStartScript.txt')
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public JlinkTask(ProjectLayout layout, JavaToolchainService javaToolchain) {
getOptions().convention(List.of());
getModules().convention(List.of("ALL-MODULE-PATH"));
getIncludeModulePath().convention(true);
getOutputDirectory().convention(layout.getBuildDirectory().dir("jlink"));
getOutputDirectory().convention(layout.getBuildDirectory().dir(getName()));

var toolchain = getProject().getExtensions().getByType(JavaPluginExtension.class).getToolchain();
getJavaCompiler().convention(javaToolchain.compilerFor(toolchain));
Expand Down
18 changes: 1 addition & 17 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,26 +1,10 @@
[versions]
logback = "1.5.18"
scrimage = "4.3.3"
twelvemonkeys = "3.12.0"

[libraries]
batik = "org.apache.xmlgraphics:batik-transcoder:1.19"
gson = "com.google.code.gson:gson:2.13.1"
Comment thread
MartelliEnrico marked this conversation as resolved.
hamcrest = "org.hamcrest:hamcrest:3.0"
imageio-batik = { module = "com.twelvemonkeys.imageio:imageio-batik", version.ref = "twelvemonkeys" }
imageio-psd = { module = "com.twelvemonkeys.imageio:imageio-psd", version.ref = "twelvemonkeys" }
jave = "ws.schild:jave-core:3.5.0"
junit-jupiter = "org.junit.jupiter:junit-jupiter:5.13.4"
junit-platform = { module = "org.junit.platform:junit-platform-launcher" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" }
logback-classic = "ch.qos.logback:logback-classic:1.5.18"
mockwebserver = "com.squareup.okhttp3:mockwebserver3-junit5:5.1.0"
scrimage-core = { module = "com.sksamuel.scrimage:scrimage-core", version.ref = "scrimage" }
scrimage-formats-extra = { module = "com.sksamuel.scrimage:scrimage-formats-extra", version.ref = "scrimage" }
scrimage-webp = { module = "com.sksamuel.scrimage:scrimage-webp", version.ref = "scrimage" }
slf4j-api = "org.slf4j:slf4j-api:2.0.17"
telegram-bot-api = "com.github.pengrad:java-telegram-bot-api:9.2.0"
tika = "org.apache.tika:tika-core:3.2.2"

[plugins]
shadow = "com.gradleup.shadow:9.1.0"
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import static com.github.stickerifier.stickerify.telegram.Answer.FILE_TOO_LARGE;
import static com.pengrad.telegrambot.model.request.ParseMode.MarkdownV2;
import static java.util.HashSet.newHashSet;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static java.util.concurrent.Executors.newThreadPerTaskExecutor;

import com.github.stickerifier.stickerify.exception.BaseException;
import com.github.stickerifier.stickerify.exception.CorruptedVideoException;
Expand Down Expand Up @@ -48,7 +48,7 @@
*
* @author Roberto Cella
*/
public record Stickerify(TelegramBot bot, Executor executor) implements ExceptionHandler {
public record Stickerify(TelegramBot bot, Executor executor) implements UpdatesListener, ExceptionHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(Stickerify.class);
private static final String BOT_TOKEN = System.getenv("STICKERIFY_TOKEN");
Expand All @@ -60,7 +60,7 @@
* @see Stickerify
*/
public Stickerify() {
this(new TelegramBot.Builder(BOT_TOKEN).updateListenerSleep(500).build(), newFixedThreadPool(getMaxConcurrentThreads(), VIRTUAL_THREAD_FACTORY));
this(new TelegramBot.Builder(BOT_TOKEN).updateListenerSleep(500).build(), newThreadPerTaskExecutor(VIRTUAL_THREAD_FACTORY));
}

/**
Expand All @@ -69,15 +69,11 @@
* @see Stickerify
*/
public Stickerify {
bot.setUpdatesListener(this::handleUpdates, this, new GetUpdates().timeout(50));
bot.setUpdatesListener(this, this, new GetUpdates().timeout(50));
}

@Override
public void onException(TelegramException e) {
LOGGER.atError().log("There was an unexpected failure: {}", e.getMessage());
}

private int handleUpdates(List<Update> updates) {
public int process(List<Update> updates) {

Check warning on line 76 in src/main/java/com/github/stickerifier/stickerify/bot/Stickerify.java

View workflow job for this annotation

GitHub Actions / Qodana for JVM

Check Kotlin and Java source code coverage

Method `process` coverage is below the threshold 50%

Check warning

Code scanning / QDJVM

Check Kotlin and Java source code coverage Warning

Method process coverage is below the threshold 50%
Comment thread Dismissed
updates.forEach(update -> executor.execute(() -> {
if (update.message() != null) {
var request = new TelegramRequest(update.message());
Expand All @@ -90,6 +86,11 @@
return UpdatesListener.CONFIRMED_UPDATES_ALL;
}

@Override
public void onException(TelegramException e) {
LOGGER.atError().log("There was an unexpected failure: {}", e.getMessage());
}

private void answer(TelegramRequest request) {
var file = request.getFile();

Expand Down Expand Up @@ -136,6 +137,8 @@
}
} catch (TelegramApiException | MediaException e) {
processFailure(request, e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
deleteTempFiles(pathsToDelete);
}
Expand Down Expand Up @@ -226,9 +229,4 @@
}
}
}

private static int getMaxConcurrentThreads() {
var value = System.getenv("CONCURRENT_THREADS");
return value == null ? 5 : Integer.parseInt(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ public FileOperationException(Throwable cause) {
super(cause);
}

public FileOperationException(String message) {
super(message);
}

public FileOperationException(String message, Throwable cause) {
super(message, cause);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
public final class MediaConstraints {

static final int MAX_SIDE_LENGTH = 512;
static final float MAX_VIDEO_FRAMES = 30F;
static final long MAX_VIDEO_DURATION_MILLIS = 3_000L;
static final int MAX_VIDEO_FRAMES = 30;
static final int MAX_VIDEO_DURATION_MILLIS = 3000;
static final String VP9_CODEC = "vp9";
static final String MATROSKA_FORMAT = "matroska";
static final long MAX_IMAGE_FILE_SIZE = 512_000L;
static final long MAX_VIDEO_FILE_SIZE = 256_000L;
static final long MAX_ANIMATION_FILE_SIZE = 64_000L;
static final int MAX_ANIMATION_FRAME_RATE = 60;
static final float MAX_ANIMATION_DURATION_SECONDS = 3F;
static final int MAX_ANIMATION_DURATION_SECONDS = 3;

private MediaConstraints() {
throw new UnsupportedOperationException();
Expand Down
Loading
Loading