mirror of
https://github.com/aNNiMON/ffmpegbot
synced 2024-09-19 22:54:20 +03:00
Rework /dl command to allow trim
This commit is contained in:
parent
c38dc4323b
commit
bbc2b83746
@ -41,7 +41,7 @@ public class MainBotHandler extends BotHandler {
|
||||
mediaProcessingBundle = new MediaProcessingBundle(sessions, fallbackFileDownloader);
|
||||
commands.registerBundle(mediaProcessingBundle);
|
||||
commands.registerBundle(new InputParametersBundle(sessions));
|
||||
commands.registerBundle(new YtDlpCommandBundle());
|
||||
commands.registerBundle(new YtDlpCommandBundle(sessions));
|
||||
commands.registerBundle(new AdminCommandBundle());
|
||||
commands.register(new HelpCommand());
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ public final class CallbackQueryCommands {
|
||||
public static final String NEXT = "next";
|
||||
public static final String DETAIL = "detail";
|
||||
public static final String PROCESS = "process";
|
||||
public static final String YTDLP_START = "ytdlp_start";
|
||||
|
||||
private CallbackQueryCommands() { }
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
|
||||
public String[] buildCommand(final @NotNull MediaSession session) {
|
||||
final var commands = new ArrayList<String>();
|
||||
commands.addAll(List.of("ffmpeg", "-loglevel", "quiet", "-stats"));
|
||||
commands.addAll(buildTrim(session));
|
||||
commands.addAll(session.getInputParams().asFFmpegCommands());
|
||||
commands.addAll(List.of("-i", FilePath.inputDir() + "/" + session.getInputFile().getName()));
|
||||
if (FileTypes.canContainAudio(session.getFileType())) {
|
||||
commands.addAll(audioCommands);
|
||||
@ -148,22 +148,4 @@ public class FFmpegCommandBuilder implements Visitor<MediaSession> {
|
||||
commands.addAll(List.of("-y", FilePath.outputDir() + "/" + session.getOutputFile().getName()));
|
||||
return commands.toArray(String[]::new);
|
||||
}
|
||||
|
||||
private List<String> buildTrim(@NotNull MediaSession session) {
|
||||
final var commands = new ArrayList<String>();
|
||||
final var inputParams = session.getInputParams();
|
||||
if (!inputParams.getStartPosition().isEmpty()) {
|
||||
commands.add("-ss");
|
||||
commands.add(inputParams.getStartPosition());
|
||||
}
|
||||
if (!inputParams.getEndPosition().isEmpty()) {
|
||||
commands.add("-to");
|
||||
commands.add(inputParams.getEndPosition());
|
||||
}
|
||||
if (!inputParams.getDuration().isEmpty()) {
|
||||
commands.add("-t");
|
||||
commands.add(inputParams.getDuration());
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.annimon.ffmpegbot.commands.ffmpeg;
|
||||
|
||||
import com.annimon.ffmpegbot.session.MediaSession;
|
||||
import com.annimon.ffmpegbot.session.Session;
|
||||
import com.annimon.ffmpegbot.session.Sessions;
|
||||
import com.annimon.tgbotsmodule.api.methods.Methods;
|
||||
import com.annimon.tgbotsmodule.commands.CommandBundle;
|
||||
@ -31,7 +31,7 @@ public class InputParametersBundle implements CommandBundle<For> {
|
||||
sessionCommand(this::cutCommand)));
|
||||
}
|
||||
|
||||
private void cutCommand(RegexMessageContext ctx, MediaSession session) {
|
||||
private void cutCommand(RegexMessageContext ctx, Session session) {
|
||||
final var arg = ctx.group(2);
|
||||
final var inputParams = session.getInputParams();
|
||||
switch (ctx.group(1)) {
|
||||
@ -42,7 +42,7 @@ public class InputParametersBundle implements CommandBundle<For> {
|
||||
editMessage(ctx, session);
|
||||
}
|
||||
|
||||
private void editMessage(MessageContext ctx, MediaSession session) {
|
||||
private void editMessage(MessageContext ctx, Session session) {
|
||||
Methods.editMessageText()
|
||||
.setChatId(session.getChatId())
|
||||
.setMessageId(session.getMessageId())
|
||||
@ -52,7 +52,7 @@ public class InputParametersBundle implements CommandBundle<For> {
|
||||
.callAsync(ctx.sender);
|
||||
}
|
||||
|
||||
private Consumer<RegexMessageContext> sessionCommand(BiConsumer<RegexMessageContext, MediaSession> consumer) {
|
||||
private Consumer<RegexMessageContext> sessionCommand(BiConsumer<RegexMessageContext, Session> consumer) {
|
||||
return ctx -> {
|
||||
final var msg = ctx.message().getReplyToMessage();
|
||||
if (msg == null) return;
|
||||
|
@ -72,7 +72,7 @@ public class MediaProcessingBundle implements CommandBundle<For> {
|
||||
final var msg = ctx.message();
|
||||
if (msg == null) return;
|
||||
|
||||
final var session = sessions.get(msg.getChatId(), msg.getMessageId());
|
||||
final var session = sessions.getMediaSession(msg.getChatId(), msg.getMessageId());
|
||||
if (session == null) return;
|
||||
|
||||
final String id = ctx.argument(0);
|
||||
@ -157,7 +157,7 @@ public class MediaProcessingBundle implements CommandBundle<For> {
|
||||
final var msg = ctx.message();
|
||||
if (msg == null) return;
|
||||
|
||||
final var session = sessions.get(msg.getChatId(), msg.getMessageId());
|
||||
final var session = sessions.getMediaSession(msg.getChatId(), msg.getMessageId());
|
||||
if (session == null) return;
|
||||
|
||||
consumer.accept(ctx, session);
|
||||
|
@ -2,6 +2,7 @@ package com.annimon.ffmpegbot.commands.ffmpeg;
|
||||
|
||||
import com.annimon.ffmpegbot.parameters.Parameter;
|
||||
import com.annimon.ffmpegbot.session.MediaSession;
|
||||
import com.annimon.ffmpegbot.session.YtDlpSession;
|
||||
import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
|
||||
import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;
|
||||
|
||||
@ -11,9 +12,9 @@ import java.util.List;
|
||||
import static com.annimon.ffmpegbot.commands.ffmpeg.CallbackQueryCommands.*;
|
||||
|
||||
public class MediaProcessingKeyboard {
|
||||
public static InlineKeyboardMarkup createKeyboard(MediaSession mediaSession) {
|
||||
public static InlineKeyboardMarkup createKeyboard(MediaSession session) {
|
||||
final var keyboard = new ArrayList<List<InlineKeyboardButton>>();
|
||||
for (Parameter<?> param : mediaSession.getParams()) {
|
||||
for (Parameter<?> param : session.getParams()) {
|
||||
final String paramId = param.getId();
|
||||
keyboard.add(List.of(
|
||||
inlineKeyboardButton("<", callbackData(PREV, paramId)),
|
||||
@ -25,6 +26,12 @@ public class MediaProcessingKeyboard {
|
||||
return new InlineKeyboardMarkup(keyboard);
|
||||
}
|
||||
|
||||
public static InlineKeyboardMarkup createKeyboard(YtDlpSession session) {
|
||||
final var keyboard = new ArrayList<List<InlineKeyboardButton>>();
|
||||
keyboard.add(List.of(inlineKeyboardButton("Start", callbackData(YTDLP_START))));
|
||||
return new InlineKeyboardMarkup(keyboard);
|
||||
}
|
||||
|
||||
private static InlineKeyboardButton inlineKeyboardButton(String text, String callbackData) {
|
||||
final var button = new InlineKeyboardButton();
|
||||
button.setText(text);
|
||||
|
@ -23,6 +23,12 @@ public class YtDlpCommandBuilder {
|
||||
final var other = "best";
|
||||
commands.add(String.join("/", List.of(mp4, any, other)));
|
||||
}
|
||||
// Trim
|
||||
if (!session.getInputParams().isEmpty()) {
|
||||
commands.addAll(List.of("--external-downloader", "ffmpeg"));
|
||||
final String ffmpegArgs = String.join(" ", session.getInputParams().asFFmpegCommands());
|
||||
commands.addAll(List.of("--external-downloader-args", "ffmpeg_i:" + ffmpegArgs));
|
||||
}
|
||||
// Url
|
||||
commands.add(session.getUrl());
|
||||
// Output
|
||||
|
@ -1,13 +1,16 @@
|
||||
package com.annimon.ffmpegbot.commands.ytdlp;
|
||||
|
||||
import com.annimon.ffmpegbot.Permissions;
|
||||
import com.annimon.ffmpegbot.commands.ffmpeg.CallbackQueryCommands;
|
||||
import com.annimon.ffmpegbot.file.FilePath;
|
||||
import com.annimon.ffmpegbot.session.*;
|
||||
import com.annimon.tgbotsmodule.api.methods.Methods;
|
||||
import com.annimon.tgbotsmodule.commands.CommandBundle;
|
||||
import com.annimon.tgbotsmodule.commands.CommandRegistry;
|
||||
import com.annimon.tgbotsmodule.commands.SimpleCallbackQueryCommand;
|
||||
import com.annimon.tgbotsmodule.commands.SimpleRegexCommand;
|
||||
import com.annimon.tgbotsmodule.commands.authority.For;
|
||||
import com.annimon.tgbotsmodule.commands.context.CallbackQueryContext;
|
||||
import com.annimon.tgbotsmodule.commands.context.RegexMessageContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -17,13 +20,26 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.annimon.ffmpegbot.commands.ffmpeg.MediaProcessingKeyboard.createKeyboard;
|
||||
|
||||
public class YtDlpCommandBundle implements CommandBundle<For> {
|
||||
|
||||
private final Sessions sessions;
|
||||
|
||||
public YtDlpCommandBundle(Sessions sessions) {
|
||||
this.sessions = sessions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(@NotNull CommandRegistry commands) {
|
||||
commands.register(new SimpleRegexCommand(
|
||||
Pattern.compile("/dl (https?://[^ ]+) ?(audio|\\d+)?p?"),
|
||||
Permissions.ALLOWED_USERS,
|
||||
this::download));
|
||||
commands.register(new SimpleCallbackQueryCommand(
|
||||
CallbackQueryCommands.YTDLP_START,
|
||||
Permissions.ALLOWED_USERS,
|
||||
this::ytDlpStart));
|
||||
}
|
||||
|
||||
private void download(@NotNull RegexMessageContext ctx) {
|
||||
@ -31,25 +47,48 @@ public class YtDlpCommandBundle implements CommandBundle<For> {
|
||||
final String downloadOption = Optional.ofNullable(ctx.group(2))
|
||||
.filter(Predicate.not(String::isBlank))
|
||||
.orElse("720");
|
||||
final var fileType = downloadOption.equals("audio") ? FileType.AUDIO : FileType.VIDEO;
|
||||
final var ytDlpSession = new YtDlpSession(url, downloadOption, fileType);
|
||||
final var filename = FilePath.generateFilename(url, FilePath.defaultFilename(fileType));
|
||||
ytDlpSession.setOutputFilename(filename);
|
||||
|
||||
Methods.sendChatAction(ctx.chatId(), Resolver.resolveAction(fileType)).callAsync(ctx.sender);
|
||||
CompletableFuture.runAsync(() -> new YtDlpTask().process(ytDlpSession))
|
||||
final var message = ctx.message();
|
||||
final var fileType = downloadOption.equals("audio") ? FileType.AUDIO : FileType.VIDEO;
|
||||
final var filename = FilePath.generateFilename(url, FilePath.defaultFilename(fileType));
|
||||
|
||||
final var session = new YtDlpSession(url, downloadOption, fileType);
|
||||
session.setChatId(message.getChatId());
|
||||
session.setOutputFilename(filename);
|
||||
|
||||
final var result = ctx.replyToMessage(session.toString())
|
||||
.enableHtml()
|
||||
.call(ctx.sender);
|
||||
session.setMessageId(result.getMessageId());
|
||||
sessions.put(session);
|
||||
|
||||
Methods.editMessageReplyMarkup(result.getChatId(), result.getMessageId())
|
||||
.setReplyMarkup(createKeyboard(session))
|
||||
.call(ctx.sender);
|
||||
}
|
||||
|
||||
private void ytDlpStart(CallbackQueryContext ctx) {
|
||||
final var msg = ctx.message();
|
||||
if (msg == null) return;
|
||||
|
||||
final var session = sessions.getYtDlpSession(msg.getChatId(), msg.getMessageId());
|
||||
if (session == null) return;
|
||||
|
||||
Methods.sendChatAction(msg.getChatId(), Resolver.resolveAction(session.getFileType()))
|
||||
.callAsync(ctx.sender);
|
||||
CompletableFuture.runAsync(() -> new YtDlpTask().process(session))
|
||||
.thenRunAsync(() -> {
|
||||
final File outputFile = FilePath.outputFile(ytDlpSession.getOutputFilename());
|
||||
final File outputFile = FilePath.outputFile(session.getOutputFilename());
|
||||
if (!outputFile.exists()) {
|
||||
throw new RuntimeException("No file to send. Check your command settings.");
|
||||
}
|
||||
Resolver.resolveMethod(fileType)
|
||||
.setChatId(ctx.chatId())
|
||||
Resolver.resolveMethod(session.getFileType())
|
||||
.setChatId(session.getChatId())
|
||||
.setFile(outputFile)
|
||||
.call(ctx.sender);
|
||||
})
|
||||
.exceptionallyAsync(throwable -> {
|
||||
ctx.replyToMessage("Failed due to " + throwable.getMessage())
|
||||
ctx.answerAsAlert("Failed due to " + throwable.getMessage())
|
||||
.callAsync(ctx.sender);
|
||||
return null;
|
||||
});
|
||||
|
@ -1,6 +1,9 @@
|
||||
package com.annimon.ffmpegbot.parameters;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class InputParameters {
|
||||
private String startPosition = "";
|
||||
@ -33,6 +36,27 @@ public class InputParameters {
|
||||
this.duration = "";
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return Stream.of(startPosition, endPosition, duration).allMatch(String::isEmpty);
|
||||
}
|
||||
|
||||
public List<String> asFFmpegCommands() {
|
||||
final var commands = new ArrayList<String>();
|
||||
if (!startPosition.isEmpty()) {
|
||||
commands.add("-ss");
|
||||
commands.add(startPosition);
|
||||
}
|
||||
if (!endPosition.isEmpty()) {
|
||||
commands.add("-to");
|
||||
commands.add(endPosition);
|
||||
}
|
||||
if (!duration.isEmpty()) {
|
||||
commands.add("-t");
|
||||
commands.add(duration);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
public StringJoiner describe() {
|
||||
final var joiner = new StringJoiner("\n");
|
||||
if (!startPosition.isEmpty()) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.annimon.ffmpegbot.session;
|
||||
|
||||
import com.annimon.ffmpegbot.parameters.InputParameters;
|
||||
import com.annimon.ffmpegbot.parameters.Parameter;
|
||||
|
||||
import java.io.File;
|
||||
@ -9,10 +8,7 @@ import java.util.StringJoiner;
|
||||
|
||||
import static com.annimon.ffmpegbot.TextUtils.*;
|
||||
|
||||
public class MediaSession {
|
||||
// Session key
|
||||
private long chatId;
|
||||
private int messageId;
|
||||
public final class MediaSession extends Session {
|
||||
// Media info
|
||||
private FileType fileType;
|
||||
private String fileId;
|
||||
@ -22,7 +18,6 @@ public class MediaSession {
|
||||
private String dimensions;
|
||||
// Parameters
|
||||
private List<Parameter<?>> params;
|
||||
private final InputParameters inputParams = new InputParameters();
|
||||
// Files
|
||||
private File inputFile;
|
||||
private File outputFile;
|
||||
@ -38,22 +33,6 @@ public class MediaSession {
|
||||
this.setOriginalFilename(fileInfo.filename());
|
||||
}
|
||||
|
||||
public long getChatId() {
|
||||
return chatId;
|
||||
}
|
||||
|
||||
public void setChatId(long chatId) {
|
||||
this.chatId = chatId;
|
||||
}
|
||||
|
||||
public int getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public void setMessageId(int messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
public FileType getFileType() {
|
||||
return fileType;
|
||||
}
|
||||
@ -94,10 +73,6 @@ public class MediaSession {
|
||||
return params;
|
||||
}
|
||||
|
||||
public InputParameters getInputParams() {
|
||||
return inputParams;
|
||||
}
|
||||
|
||||
public void setParams(List<Parameter<?>> params) {
|
||||
this.params = params;
|
||||
}
|
||||
@ -126,6 +101,7 @@ public class MediaSession {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringJoiner describe() {
|
||||
final var joiner = new StringJoiner("\n");
|
||||
joiner.add("Type: <code>%s</code>".formatted(fileType));
|
||||
|
35
src/main/java/com/annimon/ffmpegbot/session/Session.java
Normal file
35
src/main/java/com/annimon/ffmpegbot/session/Session.java
Normal file
@ -0,0 +1,35 @@
|
||||
package com.annimon.ffmpegbot.session;
|
||||
|
||||
import com.annimon.ffmpegbot.parameters.InputParameters;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public abstract sealed class Session permits MediaSession, YtDlpSession {
|
||||
// Session key
|
||||
protected long chatId;
|
||||
protected int messageId;
|
||||
// Parameters
|
||||
protected final InputParameters inputParams = new InputParameters();
|
||||
|
||||
public long getChatId() {
|
||||
return chatId;
|
||||
}
|
||||
|
||||
public void setChatId(long chatId) {
|
||||
this.chatId = chatId;
|
||||
}
|
||||
|
||||
public int getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public void setMessageId(int messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
public InputParameters getInputParams() {
|
||||
return inputParams;
|
||||
}
|
||||
|
||||
abstract StringJoiner describe();
|
||||
}
|
@ -4,21 +4,29 @@ import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class Sessions {
|
||||
private final Map<String, MediaSession> sessions;
|
||||
private final Map<String, Session> sessions;
|
||||
|
||||
public Sessions() {
|
||||
sessions = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public MediaSession get(long chatId, long messageId) {
|
||||
public Session get(long chatId, long messageId) {
|
||||
return sessions.get(mapKey(chatId, messageId));
|
||||
}
|
||||
|
||||
public void put(MediaSession mediaSession) {
|
||||
public MediaSession getMediaSession(long chatId, long messageId) {
|
||||
return (MediaSession) get(chatId, messageId);
|
||||
}
|
||||
|
||||
public YtDlpSession getYtDlpSession(long chatId, long messageId) {
|
||||
return (YtDlpSession) get(chatId, messageId);
|
||||
}
|
||||
|
||||
public void put(Session mediaSession) {
|
||||
sessions.put(mapKey(mediaSession.getChatId(), mediaSession.getMessageId()), mediaSession);
|
||||
}
|
||||
|
||||
public void put(long chatId, long messageId, MediaSession mediaSession) {
|
||||
public void put(long chatId, long messageId, Session mediaSession) {
|
||||
sessions.put(mapKey(chatId, messageId), mediaSession);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package com.annimon.ffmpegbot.session;
|
||||
|
||||
public class YtDlpSession {
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public final class YtDlpSession extends Session {
|
||||
private final String url;
|
||||
private final String downloadOption;
|
||||
private final FileType fileType;
|
||||
@ -31,4 +33,18 @@ public class YtDlpSession {
|
||||
public void setOutputFilename(String outputFilename) {
|
||||
this.outputFilename = outputFilename;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringJoiner describe() {
|
||||
final var joiner = new StringJoiner("\n");
|
||||
joiner.add("URL: <code>%s</code>".formatted(url));
|
||||
joiner.add("Type: <code>%s</code>".formatted(fileType));
|
||||
joiner.merge(inputParams.describe());
|
||||
return joiner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return describe().toString();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user