diff --git a/build.gradle b/build.gradle index 07ec1aa..4697214 100644 --- a/build.gradle +++ b/build.gradle @@ -30,10 +30,16 @@ repositories { ignoreGradleMetadataRedirection() } } + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + maven { url 'https://maven.aliyun.com/repository/public' } maven { url 'https://jitpack.io' } mavenCentral() } +configurations.all { + resolutionStrategy.cacheChangingModulesFor 0, 'seconds' +} + dependencies { testImplementation platform('org.junit:junit-bom:5.10.0') testImplementation 'org.junit.jupiter:junit-jupiter' @@ -123,6 +129,22 @@ dependencies { implementation 'org.openjfx:javafx-graphics:21' implementation 'me.friwi:jcefmaven:122.1.10' + + implementation 'com.alphacephei:vosk:0.3.45' // Java API + implementation 'net.java.dev.jna:jna:5.12.1' // 本地库加载 + + // 高性能音频处理 + implementation 'org.apache.commons:commons-math3:3.6.1' + implementation 'com.google.guava:guava:31.1-jre' + + // 中文拼音处理 + implementation 'com.belerweb:pinyin4j:2.5.1' + + // 音频I/O + implementation 'commons-io:commons-io:2.18.0' + implementation 'jflac:jflac:1.3' // FLAC支持 + + implementation 'com.github.axet:TarsosDSP:2.4' } // 分离依赖项到 libs 目录 diff --git a/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java b/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java index da6560f..757a2d0 100644 --- a/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java +++ b/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java @@ -3,17 +3,29 @@ package com.axis.innovators.box.browser; import com.axis.innovators.box.tools.FolderCreator; import org.cef.*; import org.cef.browser.*; +import org.cef.callback.CefContextMenuParams; +import org.cef.callback.CefMenuModel; import org.cef.callback.CefQueryCallback; import org.cef.handler.*; +import org.cef.network.CefRequest; + import javax.swing.*; import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.*; import java.io.File; +import java.io.IOException; import java.net.MalformedURLException; -import java.util.Arrays; -import java.util.UUID; +import java.net.URI; import java.util.function.Consumer; +import static org.cef.callback.CefMenuModel.MenuId.MENU_ID_USER_FIRST; + +/** + * @author tzdwindows 7 + */ public class BrowserWindow extends JFrame { private final String windowId; private CefApp cefApp; @@ -25,6 +37,7 @@ public class BrowserWindow extends JFrame { private WindowOperationHandler operationHandler; private static Thread cefThread; private CefMessageRouter msgRouter; + public static class Builder { private String windowId; private String title = "JCEF Window"; @@ -32,31 +45,70 @@ public class BrowserWindow extends JFrame { private WindowOperationHandler operationHandler; private String htmlPath; private Image icon; + private boolean resizable = true; // 默认允许调整大小 + private boolean maximizable = true; // 默认允许最大化 + private boolean minimizable = true; // 默认允许最小化 + + + public Builder resizable(boolean resizable) { + this.resizable = resizable; + return this; + } + + public Builder maximizable(boolean maximizable) { + this.maximizable = maximizable; + return this; + } + + public Builder minimizable(boolean minimizable) { + this.minimizable = minimizable; + return this; + } public Builder(String windowId) { this.windowId = windowId; } + /** + * 设置浏览器窗口标题 + * @param title 标题 + */ public Builder title(String title) { this.title = title; return this; } + /** + * 设置浏览器窗口大小 + * @param width 宽度 + * @param height 高度 + */ public Builder size(int width, int height) { this.size = new Dimension(width, height); return this; } + /** + * 设置浏览器触发事件 + * @param handler 事件处理器 + */ public Builder operationHandler(WindowOperationHandler handler) { this.operationHandler = handler; return this; } + /** + * 设置浏览器图标 + * @param icon 图标 + */ public Builder icon(Image icon) { this.icon = icon; return this; } + /** + * 设置HTML路径 + */ public BrowserWindow build() { if (this.htmlPath == null || this.htmlPath.isEmpty()) { throw new IllegalArgumentException("HTML paths cannot be empty"); @@ -68,6 +120,10 @@ public class BrowserWindow extends JFrame { return new BrowserWindow(this); } + /** + * 设置HTML路径 + * @param path HTML路径 + */ public Builder htmlPath(String path) { this.htmlPath = path; return this; @@ -79,9 +135,12 @@ public class BrowserWindow extends JFrame { this.htmlPath = builder.htmlPath; this.operationHandler = builder.operationHandler; + // 设置图标(如果存在) if (builder.icon != null) { setIconImage(builder.icon); } + + // 初始化浏览器组件 try { this.browserComponent = initializeCef(builder); if (operationHandler != null) { @@ -89,11 +148,11 @@ public class BrowserWindow extends JFrame { } } catch (Exception e) { JOptionPane.showMessageDialog(this, "初始化失败: " + e.getMessage()); - //System.exit(1); throw new RuntimeException(e); } } + private Component initializeCef(Builder builder) throws MalformedURLException { if (!isInitialized && CefApp.getState() != CefApp.CefAppState.INITIALIZED) { try { @@ -150,14 +209,16 @@ public class BrowserWindow extends JFrame { int line ) { // 格式化输出到 Java 控制台 - String log = String.format( - "[Browser Console] %s %s (Line %d) -> %s", - getLogLevelSymbol(level), - source, - line, - message - ); - System.out.println(log); + //if (level != CefSettings.LogSeverity.LOGSEVERITY_WARNING) { + String log = String.format( + "[Browser Console] %s %s (Line %d) -> %s", + getLogLevelSymbol(level), + source, + line, + message + ); + System.out.println(log); + //} return false; } @@ -176,6 +237,112 @@ public class BrowserWindow extends JFrame { } }); + client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() { + @Override + public boolean onBeforePopup(CefBrowser browser, CefFrame frame, + String targetUrl, String targetFrameName) { + try { + Desktop.getDesktop().browse(new URI(targetUrl)); + } catch (Exception e) { + System.out.println("Failed to open external browser: " + e.getMessage()); + } + return true; // 拦截弹窗 + } + }); + + client.addRequestHandler(new CefRequestHandlerAdapter() { + @Override + public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, + CefRequest request, boolean userGesture, boolean isRedirect) { + if (userGesture) { + try { + Desktop.getDesktop().browse(new URI(request.getURL())); + return true; // 取消内置浏览器导航 + } catch (Exception e) { + System.out.println("Failed to open external browser: " + e.getMessage()); + } + } + return false; + } + }); + + client.addContextMenuHandler(new CefContextMenuHandlerAdapter() { + @Override + public void onBeforeContextMenu(CefBrowser browser, CefFrame frame, + CefContextMenuParams params, CefMenuModel model) { + model.clear(); + if (!params.getSelectionText().isEmpty() || params.isEditable()) { + model.addItem(MENU_ID_USER_FIRST, "复制"); + } + + if (params.isEditable()) { + model.addItem(MENU_ID_USER_FIRST + 1, "粘贴"); + model.addItem(MENU_ID_USER_FIRST + 2, "粘贴纯文本"); + } + } + + @Override + public boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, + CefContextMenuParams params, int commandId, int eventFlags) { + if (commandId == MENU_ID_USER_FIRST) { + if (params.isEditable()) { + browser.executeJavaScript("document.execCommand('copy');", browser.getURL(), 0); + } else { + browser.executeJavaScript( + "window.getSelection().toString();", + browser.getURL(), + 0 + ); + } + return true; + } else if (commandId == MENU_ID_USER_FIRST + 1) { + pasteContent(browser, false); + return true; + } else if (commandId == MENU_ID_USER_FIRST + 2) { + pasteContent(browser, true); + return true; + } + + return false; + } + + /** + * 处理粘贴操作 + * @param plainText 是否去除格式(纯文本模式) + */ + private void pasteContent(CefBrowser browser, boolean plainText) { + try { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) { + String text = (String) clipboard.getData(DataFlavor.stringFlavor); + + if (plainText) { + text = text.replaceAll("<[^>]+>", ""); + } + + String escapedText = text + .replace("\\", "\\\\") + .replace("'", "\\'") + .replace("\n", "\\n") + .replace("\r", "\\r"); + + String script = String.format( + "if (document.activeElement) {\n" + + " document.activeElement.value += '%s';\n" + // 简单追加文本 + " document.dispatchEvent(new Event('input', { bubbles: true }));\n" + // 触发输入事件 + "}", + escapedText + ); + browser.executeJavaScript(script, browser.getURL(), 0); + } + } catch (UnsupportedFlavorException | IOException e) { + e.printStackTrace(); + } + } + }); + + + // 3. 拦截所有新窗口(关键修复点!) //client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() { // @Override @@ -205,7 +372,7 @@ public class BrowserWindow extends JFrame { // 6. 配置窗口布局(确保只添加一次) SwingUtilities.invokeLater(() -> { - getContentPane().removeAll(); // 清空已有组件 + getContentPane().removeAll(); getContentPane().setLayout(new BorderLayout()); // 透明拖拽层(仅顶部可拖拽) @@ -243,7 +410,7 @@ public class BrowserWindow extends JFrame { // 7. 窗口属性设置 setTitle(builder.title); - setSize(builder.size); // 直接设置尺寸,避免pack()计算错误 + setSize(builder.size); setLocationRelativeTo(null); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); @@ -251,7 +418,7 @@ public class BrowserWindow extends JFrame { addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { - browser.close(true); // 强制关闭浏览器 + browser.close(true); client.dispose(); cefApp.dispose(); isInitialized = false; diff --git a/src/main/java/com/axis/innovators/box/browser/BrowserWindowJDialog.java b/src/main/java/com/axis/innovators/box/browser/BrowserWindowJDialog.java new file mode 100644 index 0000000..66552aa --- /dev/null +++ b/src/main/java/com/axis/innovators/box/browser/BrowserWindowJDialog.java @@ -0,0 +1,532 @@ +package com.axis.innovators.box.browser; + +import com.axis.innovators.box.tools.FolderCreator; +import org.cef.*; +import org.cef.browser.*; +import org.cef.callback.CefContextMenuParams; +import org.cef.callback.CefMenuModel; +import org.cef.callback.CefQueryCallback; +import org.cef.handler.*; +import org.cef.network.CefRequest; + +import javax.swing.*; +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.*; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.util.function.Consumer; + +import static org.cef.callback.CefMenuModel.MenuId.MENU_ID_USER_FIRST; + +/** + * @author tzdwindows 7 + */ +public class BrowserWindowJDialog extends JDialog { + private final String windowId; + private CefApp cefApp; + private CefClient client; + private CefBrowser browser; + private final Component browserComponent; + private final String htmlPath; + //private boolean isInitialized = false; + private WindowOperationHandler operationHandler; + private static Thread cefThread; + private CefMessageRouter msgRouter; + + public static class Builder { + private String windowId; + private String title = "JCEF Window"; + private Dimension size = new Dimension(800, 600); + private WindowOperationHandler operationHandler; + private String htmlPath; + private Image icon; + private JFrame parentFrame; + private boolean resizable = true; // 默认允许调整大小 + private boolean maximizable = true; // 默认允许最大化 + private boolean minimizable = true; // 默认允许最小化 + + + public Builder resizable(boolean resizable) { + this.resizable = resizable; + return this; + } + + public Builder maximizable(boolean maximizable) { + this.maximizable = maximizable; + return this; + } + + public Builder minimizable(boolean minimizable) { + this.minimizable = minimizable; + return this; + } + + public Builder(String windowId) { + this.windowId = windowId; + } + + /** + * 设置浏览器窗口标题 + * @param title 标题 + */ + public Builder title(String title) { + this.title = title; + return this; + } + + /** + * 设置浏览器窗口大小 + * @param width 宽度 + * @param height 高度 + */ + public Builder size(int width, int height) { + this.size = new Dimension(width, height); + return this; + } + + /** + * 设置浏览器窗口父窗口 + * @param parent 父窗口 + */ + public Builder parentFrame(JFrame parent) { + this.parentFrame = parent; + return this; + } + + + /** + * 设置浏览器触发事件 + * @param handler 事件处理器 + */ + public Builder operationHandler(WindowOperationHandler handler) { + this.operationHandler = handler; + return this; + } + + /** + * 设置浏览器图标 + * @param icon 图标 + */ + public Builder icon(Image icon) { + this.icon = icon; + return this; + } + + /** + * 设置HTML路径 + */ + public BrowserWindowJDialog build() { + if (this.htmlPath == null || this.htmlPath.isEmpty()) { + throw new IllegalArgumentException("HTML paths cannot be empty"); + } + File htmlFile = new File(this.htmlPath); + if (!htmlFile.exists()) { + throw new RuntimeException("The HTML file does not exist: " + htmlFile.getAbsolutePath()); + } + return new BrowserWindowJDialog(this); + } + + /** + * 设置HTML路径 + * @param path HTML路径 + */ + public Builder htmlPath(String path) { + this.htmlPath = path; + return this; + } + } + + private BrowserWindowJDialog(Builder builder) { + // 根据父窗口是否存在,设置是否为模态对话框 + super(builder.parentFrame, builder.title, builder.parentFrame != null); + this.windowId = builder.windowId; + this.htmlPath = builder.htmlPath; + this.operationHandler = builder.operationHandler; + + // 设置图标(如果存在) + if (builder.icon != null) { + setIconImage(builder.icon); + } + + // 初始化浏览器组件 + try { + this.browserComponent = initializeCef(builder); + if (operationHandler != null) { + setupMessageHandlers(operationHandler); + } + } catch (Exception e) { + JOptionPane.showMessageDialog(this, "初始化失败: " + e.getMessage()); + throw new RuntimeException(e); + } + } + private static boolean isInitialized = false; + private Component initializeCef(Builder builder) throws MalformedURLException { + if (!isInitialized) { + isInitialized = true; + try { + this.cefApp = CefAppManager.getInstance(); + CefAppManager.incrementBrowserCount(); + client = cefApp.createClient(); + + client.addDisplayHandler(new CefDisplayHandler (){ + @Override + public void onAddressChange(CefBrowser browser, CefFrame frame, String url) { + + } + + @Override + public void onTitleChange(CefBrowser browser, String title) { + + } + + @Override + public void OnFullscreenModeChange(CefBrowser browser, boolean fullscreen) { + + } + + @Override + public boolean onTooltip(CefBrowser browser, String text) { + return false; + } + + @Override + public void onStatusMessage(CefBrowser browser, String value) { + + } + + @Override + public boolean onConsoleMessage( + CefBrowser browser, + CefSettings.LogSeverity level, + String message, + String source, + int line + ) { + // 格式化输出到 Java 控制台 + //if (level != CefSettings.LogSeverity.LOGSEVERITY_WARNING) { + String log = String.format( + "[Browser Console] %s %s (Line %d) -> %s", + getLogLevelSymbol(level), + source, + line, + message + ); + System.out.println(log); + //} + return false; + } + + @Override + public boolean onCursorChange(CefBrowser browser, int cursorType) { + return false; + } + + private String getLogLevelSymbol(CefSettings.LogSeverity level) { + switch (level) { + case LOGSEVERITY_ERROR: return "⛔"; + case LOGSEVERITY_WARNING: return "⚠️"; + case LOGSEVERITY_DEFAULT: return "🐞"; + default: return "ℹ️"; + } + } + }); + + client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() { + @Override + public boolean onBeforePopup(CefBrowser browser, CefFrame frame, + String targetUrl, String targetFrameName) { + try { + Desktop.getDesktop().browse(new URI(targetUrl)); + } catch (Exception e) { + System.out.println("Failed to open external browser: " + e.getMessage()); + } + return true; // 拦截弹窗 + } + }); + + client.addRequestHandler(new CefRequestHandlerAdapter() { + @Override + public boolean onBeforeBrowse(CefBrowser browser, CefFrame frame, + CefRequest request, boolean userGesture, boolean isRedirect) { + if (userGesture) { + try { + Desktop.getDesktop().browse(new URI(request.getURL())); + return true; // 取消内置浏览器导航 + } catch (Exception e) { + System.out.println("Failed to open external browser: " + e.getMessage()); + } + } + return false; + } + }); + + client.addContextMenuHandler(new CefContextMenuHandlerAdapter() { + @Override + public void onBeforeContextMenu(CefBrowser browser, CefFrame frame, + CefContextMenuParams params, CefMenuModel model) { + model.clear(); + if (!params.getSelectionText().isEmpty() || params.isEditable()) { + model.addItem(MENU_ID_USER_FIRST, "复制"); + } + + if (params.isEditable()) { + model.addItem(MENU_ID_USER_FIRST + 1, "粘贴"); + model.addItem(MENU_ID_USER_FIRST + 2, "粘贴纯文本"); + } + } + + @Override + public boolean onContextMenuCommand(CefBrowser browser, CefFrame frame, + CefContextMenuParams params, int commandId, int eventFlags) { + if (commandId == MENU_ID_USER_FIRST) { + if (params.isEditable()) { + browser.executeJavaScript("document.execCommand('copy');", browser.getURL(), 0); + } else { + browser.executeJavaScript( + "window.getSelection().toString();", + browser.getURL(), + 0 + ); + } + return true; + } else if (commandId == MENU_ID_USER_FIRST + 1) { + pasteContent(browser, false); + return true; + } else if (commandId == MENU_ID_USER_FIRST + 2) { + pasteContent(browser, true); + return true; + } + + return false; + } + + /** + * 处理粘贴操作 + * @param plainText 是否去除格式(纯文本模式) + */ + private void pasteContent(CefBrowser browser, boolean plainText) { + try { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) { + String text = (String) clipboard.getData(DataFlavor.stringFlavor); + + if (plainText) { + text = text.replaceAll("<[^>]+>", ""); + } + + String escapedText = text + .replace("\\", "\\\\") + .replace("'", "\\'") + .replace("\n", "\\n") + .replace("\r", "\\r"); + + String script = String.format( + "if (document.activeElement) {\n" + + " document.activeElement.value += '%s';\n" + // 简单追加文本 + " document.dispatchEvent(new Event('input', { bubbles: true }));\n" + // 触发输入事件 + "}", + escapedText + ); + browser.executeJavaScript(script, browser.getURL(), 0); + } + } catch (UnsupportedFlavorException | IOException e) { + e.printStackTrace(); + } + } + }); + + + + // 3. 拦截所有新窗口(关键修复点!) + //client.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() { + // @Override + // public boolean onBeforePopup(CefBrowser browser, + // CefFrame frame, String target_url, String target_frame_name) { + // return true; // 返回true表示拦截弹窗 + // } + //}); + + + Thread.currentThread().setName("BrowserRenderThread"); + + // 4. 加载HTML + String fileUrl = new File(htmlPath).toURI().toURL().toString(); + System.out.println("Loading HTML from: " + fileUrl); + + // 5. 创建浏览器组件(直接添加到内容面板) + browser = client.createBrowser(fileUrl, false, false); + + Component browserComponent = browser.getUIComponent(); + browser.executeJavaScript("console.log('Java -> HTML 消息测试')",null,2); + + CefMessageRouter.CefMessageRouterConfig config = new CefMessageRouter.CefMessageRouterConfig(); + config.jsQueryFunction = "javaQuery";// 定义方法 + config.jsCancelFunction = "javaQueryCancel";// 定义取消方法 + + + // 6. 配置窗口布局(确保只添加一次) + SwingUtilities.invokeLater(() -> { + getContentPane().removeAll(); + getContentPane().setLayout(new BorderLayout()); + + // 透明拖拽层(仅顶部可拖拽) + JPanel dragPanel = new JPanel(new BorderLayout()); + dragPanel.setOpaque(false); + + JPanel titleBar = new JPanel(); + titleBar.setOpaque(false); + titleBar.setPreferredSize(new Dimension(builder.size.width, 20)); + final Point[] dragStart = new Point[1]; + titleBar.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + dragStart[0] = e.getPoint(); + } + + @Override + public void mouseReleased(MouseEvent e) { + dragStart[0] = null; + } + }); + titleBar.addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + if (dragStart[0] != null) { + Point curr = e.getLocationOnScreen(); + setLocation(curr.x - dragStart[0].x, curr.y - dragStart[0].y); + } + } + }); + + dragPanel.add(titleBar, BorderLayout.NORTH); + getContentPane().add(dragPanel, BorderLayout.CENTER); + getContentPane().add(browserComponent, BorderLayout.CENTER); + + // 7. 窗口属性设置 + setTitle(builder.title); + setSize(builder.size); + setLocationRelativeTo(builder.parentFrame); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + // 8. 资源释放 + addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + browser.close(true); + client.dispose(); + } + }); + + setVisible(true); + + }); + return browserComponent; + } catch (Exception e) { + e.printStackTrace(); + JOptionPane.showMessageDialog(null, "初始化失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); + } + } else { + isInitialized = false; + SwingUtilities.invokeLater(() -> { + dispose(); + }); + } + return null; + } + + public static void printStackTrace() { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (int i = 2; i < stackTrace.length; i++) { + StackTraceElement element = stackTrace[i]; + System.out.println(element.getClassName() + "." + element.getMethodName() + + "(" + (element.getFileName() != null ? element.getFileName() : "Unknown Source") + + ":" + element.getLineNumber() + ")"); + } + } + + @Override + public void setVisible(boolean b) { + super.setVisible(b); + } + + public Component getBrowserComponent() { + return browserComponent; + } + + private void setupMessageHandlers(WindowOperationHandler handler) { + if (client != null) { + msgRouter = CefMessageRouter.create(); + msgRouter.addHandler(new CefMessageRouterHandlerAdapter() { + @Override + public boolean onQuery(CefBrowser browser, + CefFrame frame, + long queryId, + String request, + boolean persistent, + CefQueryCallback callback) { + if (request.startsWith("system:")) { + String[] parts = request.split(":"); + String operation = parts.length >= 2 ? parts[1] : null; + String targetWindow = parts.length > 2 ? parts[2] : null; + handler.handleOperation( + new WindowOperation(operation, targetWindow, callback) // [!code ++] + ); + return true; + } + if (request.startsWith("java-response:")) { + String[] parts = request.split(":"); + String requestId = parts[1]; + String responseData = parts.length > 2 ? parts[2] : ""; + Consumer handler = WindowRegistry.getInstance().getCallback(requestId); + if (handler != null) { + handler.accept(responseData); + callback.success(""); + } else { + callback.failure(-1, "无效的请求ID"); + } + return true; + } + return false; + } + }, true); + client.addMessageRouter(msgRouter); + } + } + + public String getWindowId() { + return windowId; + } + + /** + * 获取消息路由器 + * @return 消息路由器 + */ + public CefMessageRouter getMsgRouter() { + return msgRouter; + } + + /** + * 获取浏览器对象 + * @return 浏览器对象 + */ + public CefBrowser getBrowser() { + return browser; + } + + public void closeWindow() { + SwingUtilities.invokeLater(() -> { + if (browser != null) { + browser.close(true); + } + dispose(); + cefApp.dispose(); + WindowRegistry.getInstance().unregisterWindow(windowId); + }); + } +} + diff --git a/src/main/java/com/axis/innovators/box/browser/CefAppManager.java b/src/main/java/com/axis/innovators/box/browser/CefAppManager.java new file mode 100644 index 0000000..5bc7dd0 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/browser/CefAppManager.java @@ -0,0 +1,64 @@ +package com.axis.innovators.box.browser; + +import com.axis.innovators.box.tools.FolderCreator; +import org.cef.CefApp; +import org.cef.CefSettings; + +import java.io.File; + +public class CefAppManager { + private static CefApp cefApp; + private static int browserCount = 0; + private static boolean isInitialized = false; + + static { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + disposeCefApp(); + })); + } + public static synchronized CefApp getInstance() throws Exception { + // 关键修改:仅在第一次初始化时设置参数 + if (cefApp == null && !isInitialized) { + CefSettings settings = new CefSettings(); + settings.windowless_rendering_enabled = false; + settings.javascript_flags = "--expose-gc"; + settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_VERBOSE; + + String subprocessPath = FolderCreator.getLibraryFolder() + "/jcef/lib/win64/jcef_helper.exe"; + validateSubprocessPath(subprocessPath); + settings.browser_subprocess_path = subprocessPath; + + cefApp = CefApp.getInstance(settings); + isInitialized = true; + } else if (cefApp == null) { + // 后续调用使用无参数版本 + cefApp = CefApp.getInstance(); + } + return cefApp; + } + + private static void validateSubprocessPath(String path) { + File exeFile = new File(path); + if (!exeFile.exists()) { + throw new IllegalStateException("jcef_helper.exe not found at: " + path); + } + } + + public static synchronized void incrementBrowserCount() { + browserCount++; + } + + public static synchronized void decrementBrowserCount() { + if (--browserCount <= 0) { + disposeCefApp(); + } + } + + private static void disposeCefApp() { + if (cefApp != null) { + cefApp.dispose(); + cefApp = null; + isInitialized = false; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/browser/DeepSeek - 探索未至之境.html b/src/main/java/com/axis/innovators/box/browser/DeepSeek - 探索未至之境.html deleted file mode 100644 index b97d636..0000000 --- a/src/main/java/com/axis/innovators/box/browser/DeepSeek - 探索未至之境.html +++ /dev/null @@ -1,688 +0,0 @@ - - - - - - DeepSeek - 智能助手 - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
-
-
- - -
-
-
-
- - - - \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/browser/MainApplication.java b/src/main/java/com/axis/innovators/box/browser/MainApplication.java index 7c4414e..2a4fe7a 100644 --- a/src/main/java/com/axis/innovators/box/browser/MainApplication.java +++ b/src/main/java/com/axis/innovators/box/browser/MainApplication.java @@ -8,14 +8,18 @@ import org.cef.handler.CefMessageRouterHandlerAdapter; import org.tzd.lm.LM; import javax.swing.*; +import java.io.InputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; /** * 这是一个简单的示例程序,用于展示如何使用JCEF来创建一个简单的浏览器窗口。 + * @author tzdwindows 7 */ public class MainApplication { private static final ExecutorService executor = Executors.newCachedThreadPool(); @@ -27,12 +31,63 @@ public class MainApplication { modelHandle = LM.llamaLoadModelFromFile(LM.DEEP_SEEK); ctxHandle = LM.createContext(modelHandle); - AtomicReference window = new AtomicReference<>(); + AtomicReference window = new AtomicReference<>(); SwingUtilities.invokeLater(() -> { - WindowRegistry.getInstance().createNewWindow("main", builder -> - window.set(builder.title("Axis Innovators Box") + WindowRegistry.getInstance().createNewChildWindow("main", builder -> + window.set(builder.title("Axis Innovators Box AI 工具箱") + .icon(new ImageIcon(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("icons/logo.png"))).getImage()) .size(1280, 720) - .htmlPath("C:\\Users\\Administrator\\MCreatorWorkspaces\\AxisInnovatorsBox\\src\\main\\java\\com\\axis\\innovators\\box\\browser\\DeepSeek - 探索未至之境.html") + .htmlPath(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("javascript/AIaToolbox.html")).getFile()) + .operationHandler(createOperationHandler()) + .build()) + ); + + CefMessageRouter msgRouter = window.get().getMsgRouter(); + if (msgRouter != null) { + msgRouter.addHandler(new CefMessageRouterHandlerAdapter() { + @Override + public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, + String request, boolean persistent, CefQueryCallback callback) { + // 处理浏览器请求 + handleBrowserQuery(browser, request, callback); + return true; + } + + @Override + public void onQueryCanceled(CefBrowser browser, CefFrame frame, long queryId) { + // 处理请求取消 + } + }, true); + } + }); + + // 关闭钩子 + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + LM.llamaFreeContext(ctxHandle); + LM.llamaFreeModel(modelHandle); + executor.shutdown(); + })); + } + + + /** + * 弹出AI窗口 + * @param parent 父窗口 + */ + public static void popupAIWindow + (JFrame parent) { + LM.loadLibrary(LM.CUDA); + modelHandle = LM.llamaLoadModelFromFile(LM.DEEP_SEEK); + ctxHandle = LM.createContext(modelHandle); + + AtomicReference window = new AtomicReference<>(); + SwingUtilities.invokeLater(() -> { + WindowRegistry.getInstance().createNewChildWindow("main", builder -> + window.set(builder.title("Axis Innovators Box AI 工具箱") + .parentFrame(parent) + .icon(new ImageIcon(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("icons/logo.png"))).getImage()) + .size(1280, 720) + .htmlPath(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("javascript/AIaToolbox_dark.html")).getFile()) .operationHandler(createOperationHandler()) .build()) ); @@ -78,13 +133,9 @@ public class MainApplication { if ("ai-inference".equals(operation)) { executor.execute(() -> { - //String system; if (isSystem) { - //system = ; isSystem = false; - } //else { - //system = null; - //} + } List messageList = new java.util.ArrayList<>(List.of()); // 修改后的推理回调处理 String jsCode = String.format( @@ -96,27 +147,10 @@ public class MainApplication { "推理内容" ); browser.executeJavaScript(jsCode, null, 0); - LM.inference(modelHandle, ctxHandle, 0.6f, prompt + "\n", - """ - # 角色设定 - 你是一个严格遵循规则的AI助手, - 当遇到简单的问题时你可以直接回答(回答先添加),在遇到复杂问题时请你思考后再给出答案,并且需满足以下要求: - - ## 核心指令 - 1. **强制推理标记**:无论是否推理,回答**必须**以``开头,推理结束时闭合``。 - 2. **推理流程**: - - 使用固定句式(如“好的,用户现在需要...”)启动分析 - 3. **回答规范**: - - 使用Markdown标题、列表、加粗突出重点 - - LaTeX公式严格包裹在`$$...$$`中(示例:`$$E=mc^2$$`) - - 不同可能性答案用列表呈现 - - ## 违规惩罚 - - 若未包含``标签,需在回答开头添加 - """, + LM.inference(modelHandle, ctxHandle, 0.6f, prompt + "\n","", new LM.MessageCallback() { private boolean thinkingClosed = false; - +// @Override public void onMessage(String message) { messageList.add(message); @@ -128,7 +162,7 @@ public class MainApplication { .replace("\"", "\\\"") .replace("\n", "\\n") .replace("\r", "\\r"); - +// if (messageList.contains("") && !thinkingClosed) { String endJs = String.format( "if (typeof updateResponse === 'function') {" + @@ -139,8 +173,10 @@ public class MainApplication { browser.executeJavaScript(endJs, null, 0); thinkingClosed = true; } - +// // 实时更新内容 + System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8)); +// String jsCode = String.format( "if (typeof updateResponse === 'function') {" + " updateResponse('%s', '%s');" + @@ -152,6 +188,17 @@ public class MainApplication { } },isSystem); messageList.clear(); + //jsCode = String.format( + // "if (typeof updateResponse === 'function') {" + + // " updateResponse('%s', '%s');" + + // "}", + // requestId, "嗯,用户问的是“请直接告诉我傅里叶变换公式”。首先,我需要回忆一下傅里叶变换的基本知识。傅里叶变换是将一个时间域的信号转换为频率域的信号,它在工程和科学研究中有着广泛的应用。\n\n接下来,我要确定傅里叶变换的数学表达式。标准形式应该是$$F(\\omega) = \\int_{-\\infty}^{\\infty} f(t) e^{-i\\omega t} dt$$。这里,$f(t)$是原函数,$e^{-i\\omega t}$是指数函数,$\\omega$是频率变量。\n\n然后,我需要考虑是否有其他形式的傅里叶变换,比如离散形式或逆变换。通常,离散傅里叶变换(DFT)使用$$X[k] = \\sum_{n=0}^{N-1} x[n] e^{-i2\\pi kn/N}$$来表示,而逆变换则是$$x[n] = \\frac{1}{N} \\sum_{k=0}^{N-1} X[k] e^{i2\\pi kn/N}$$。不过,用户的问题比较直接,可能只关注基本的连续形式。\n\n最后,我要确保回答准确无误,并且按照用户的格式要求使用标准的 LaTeX æ式来呈现。\n\n\n傅里叶变换的基本公式是:$$F(\\omega) = \\int_{-\\infty}^{\\infty} f(t) e^{-i\\omega t} dt$$".replace("\\", "\\\\") + // .replace("'", "\\'") + // .replace("\"", "\\\"") + // .replace("\n", "\\n") + // .replace("\r", "\\r") + //); + //browser.executeJavaScript(jsCode, null, 0); callback.success("COMPLETED:" + requestId); }); } diff --git a/src/main/java/com/axis/innovators/box/browser/WindowOperation.java b/src/main/java/com/axis/innovators/box/browser/WindowOperation.java index 886aeb6..4dc0829 100644 --- a/src/main/java/com/axis/innovators/box/browser/WindowOperation.java +++ b/src/main/java/com/axis/innovators/box/browser/WindowOperation.java @@ -5,26 +5,5 @@ import org.cef.callback.CefQueryCallback; /** * @author tzdwindows 7 */ -public class WindowOperation { - private final String type; - private final String targetWindow; - private final CefQueryCallback callback; - - public WindowOperation(String type, String targetWindow, CefQueryCallback callback) { - this.type = type; - this.targetWindow = targetWindow; - this.callback = callback; - } - - public String getType() { - return type; - } - - public String getTargetWindow() { - return targetWindow; - } - - public CefQueryCallback getCallback() { - return callback; - } +public record WindowOperation(String type, String targetWindow, CefQueryCallback callback) { } diff --git a/src/main/java/com/axis/innovators/box/browser/WindowOperationHandler.java b/src/main/java/com/axis/innovators/box/browser/WindowOperationHandler.java index bee4c12..ac7d3b8 100644 --- a/src/main/java/com/axis/innovators/box/browser/WindowOperationHandler.java +++ b/src/main/java/com/axis/innovators/box/browser/WindowOperationHandler.java @@ -83,12 +83,12 @@ public class WindowOperationHandler { } public void handleOperation(WindowOperation operation) { - Consumer handler = operations.get(operation.getType()); + Consumer handler = operations.get(operation.type()); if (handler != null) { - handler.accept(operation.getTargetWindow()); - operation.getCallback().success("操作成功: " + operation.getType()); + handler.accept(operation.targetWindow()); + operation.callback().success("操作成功: " + operation.type()); } else { - operation.getCallback().failure(-1, "未定义的操作: " + operation.getType()); + operation.callback().failure(-1, "未定义的操作: " + operation.type()); } } } \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/browser/WindowRegistry.java b/src/main/java/com/axis/innovators/box/browser/WindowRegistry.java index dfce264..cd9ffff 100644 --- a/src/main/java/com/axis/innovators/box/browser/WindowRegistry.java +++ b/src/main/java/com/axis/innovators/box/browser/WindowRegistry.java @@ -8,6 +8,8 @@ public class WindowRegistry { private static WindowRegistry instance; private final ConcurrentMap windows = new ConcurrentHashMap<>(); + private final ConcurrentMap childWindows = + new ConcurrentHashMap<>(); private final Map> callbacks = new ConcurrentHashMap<>(); private WindowRegistry() {} @@ -23,6 +25,10 @@ public class WindowRegistry { windows.put(window.getWindowId(), window); } + public void registerChildWindow(BrowserWindowJDialog window) { + childWindows.put(window.getWindowId(), window); + } + public void registerCallback(String requestId, Consumer handler) { callbacks.put(requestId, handler); } @@ -42,10 +48,27 @@ public class WindowRegistry { return windows.get(windowId); } + /** + * 创建一个新的窗口 + * @param windowId 窗口ID + * @param config 窗口配置 + */ public void createNewWindow(String windowId, Consumer config) { BrowserWindow.Builder builder = new BrowserWindow.Builder(windowId); config.accept(builder); BrowserWindow window = builder.build(); registerWindow(window); } + + /** + * 创建一个新的子窗口 + * @param windowId 窗口ID + * @param config 窗口配置 + */ + public void createNewChildWindow(String windowId, Consumer config) { + BrowserWindowJDialog.Builder builder = new BrowserWindowJDialog.Builder(windowId); + config.accept(builder); + BrowserWindowJDialog window = builder.build(); + registerChildWindow(window); + } } diff --git a/src/main/java/com/axis/innovators/box/browser/main.html b/src/main/java/com/axis/innovators/box/browser/main.html deleted file mode 100644 index b923f71..0000000 --- a/src/main/java/com/axis/innovators/box/browser/main.html +++ /dev/null @@ -1,13 +0,0 @@ - \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/register/RegistrationTool.java b/src/main/java/com/axis/innovators/box/register/RegistrationTool.java index bfcd1bb..b638631 100644 --- a/src/main/java/com/axis/innovators/box/register/RegistrationTool.java +++ b/src/main/java/com/axis/innovators/box/register/RegistrationTool.java @@ -1,6 +1,7 @@ package com.axis.innovators.box.register; import com.axis.innovators.box.AxisInnovatorsBox; +import com.axis.innovators.box.browser.MainApplication; import com.axis.innovators.box.gui.FridaWindow; import com.axis.innovators.box.gui.LocalWindow; import com.axis.innovators.box.gui.MainWindow; @@ -65,8 +66,10 @@ public class RegistrationTool { } Window owner = SwingUtilities.windowForComponent((Component) e.getSource()); - LocalWindow dialog = new LocalWindow(owner); - main.popupWindow(dialog); + // 这是被抛弃的界面,在后面的版本可能会删除 + //LocalWindow dialog = new LocalWindow(owner); + //main.popupWindow(dialog); + MainApplication.popupAIWindow((JFrame)owner); } })); diff --git a/src/main/java/com/axis/innovators/box/speech/HighAccuracySpeechRecognition.java b/src/main/java/com/axis/innovators/box/speech/HighAccuracySpeechRecognition.java new file mode 100644 index 0000000..4fe4b93 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/speech/HighAccuracySpeechRecognition.java @@ -0,0 +1,390 @@ +package com.axis.innovators.box.speech; + +import be.tarsos.dsp.AudioDispatcher; +import be.tarsos.dsp.AudioEvent; +import be.tarsos.dsp.io.TarsosDSPAudioFormat; +import be.tarsos.dsp.io.jvm.AudioDispatcherFactory; +import be.tarsos.dsp.io.jvm.JVMAudioInputStream; +import be.tarsos.dsp.io.jvm.WaveformWriter; +import com.google.gson.*; +import org.vosk.*; +import org.apache.commons.math3.util.Precision; +import net.sourceforge.pinyin4j.PinyinHelper; +import javax.sound.sampled.*; +import java.io.*; +import java.lang.reflect.Field; +import java.nio.channels.FileChannel; +import java.nio.file.*; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class HighAccuracySpeechRecognition { + private final Model model; + private final String outputPath; + private final int sampleRate = 44100; + private final TarsosDSPAudioFormat audioFormat = new TarsosDSPAudioFormat( + 44100, + 16, + 1, + true, + false + ); + + static { + LibVosk.setLogLevel(LogLevel.WARNINGS); + } + + public HighAccuracySpeechRecognition(String modelPath, String outputDir) throws IOException { + this.model = new Model(modelPath); + + this.outputPath = outputDir; + Files.createDirectories(Paths.get(outputDir)); + } + + public void processAudio(Path audioFile) throws Exception { + Path convertedFile = null; + try { + convertedFile = convertToPCM(audioFile); + try (Recognizer recognizer = new Recognizer(model, sampleRate); + AudioInputStream ais = AudioSystem.getAudioInputStream(convertedFile.toFile())) { + + byte[] buffer = new byte[4096]; + List wordList = new ArrayList<>(); + + while (ais.read(buffer) >= 0) { + if (recognizer.acceptWaveForm(buffer, buffer.length)) { + parseResult(recognizer.getResult(), wordList); + } + } + + parseResult(recognizer.getFinalResult(), wordList); + segmentAudio(convertedFile, wordList); + } + } finally { + // 增强的文件删除逻辑 + if (convertedFile != null) { + deleteWithRetry(convertedFile, 5, 100); + } + } + } + + // 新增重试删除方法 + private void deleteWithRetry(Path path, int maxRetries, long intervalMillis) throws IOException { + int retryCount = 0; + while (Files.exists(path)) { + try { + Files.delete(path); + break; + } catch (IOException ex) { + if (++retryCount >= maxRetries) { + throw ex; + } + try { + // 强制释放文件句柄 + System.gc(); + Thread.sleep(intervalMillis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("删除中断", e); + } + } + } + } + + private void parseResult(String json, List wordList) { + if (json == null || json.isEmpty()) return; + + System.out.println("Processing result: " + json); + try { + JsonObject result = JsonParser.parseString(json).getAsJsonObject(); + + if (result.has("text")) { + String fullText = result.get("text").getAsString(); + processRawText(fullText, wordList); + } else if (result.has("result") && result.get("result").isJsonArray()) { + JsonArray words = result.getAsJsonArray("result"); + + words.forEach(w -> { + if (w.isJsonObject()) { + JsonObject word = w.getAsJsonObject(); + if (result.has("result") && result.get("result").isJsonArray()) { + wordList.add(new WordInfo( + word.get("word").getAsString(), + Precision.round(word.get("start").getAsDouble(), 3), + Precision.round(word.get("end").getAsDouble(), 3), + (float) word.get("conf").getAsDouble() + )); + } + } + }); + } else { + System.err.println("Warning: Invalid recognition result - " + json); + } + } catch (JsonSyntaxException e) { + throw new AudioProcessingException("Invalid JSON result: " + json, e); + } + } + + private void processRawText(String rawText, List wordList) { + // 使用正则表达式过滤无效字符 + String cleanedText = rawText.replaceAll("[^\\u4e00-\\u9fa5a-zA-Z0-9]", " ") + .replaceAll("\\s+", " ") + .trim(); + + if (!cleanedText.isEmpty()) { + // 对无法切分的时间信息进行估算(示例逻辑) + double estimatedDuration = cleanedText.length() * 0.3; // 假设每个字0.3秒 + wordList.add(new WordInfo( + cleanedText, + 0.0, + estimatedDuration, + 0.5f // 默认置信度 + )); + } + } + + private void segmentAudio(Path audioFile, List words) throws Exception { + // 确保每个片段独立处理 + for (WordInfo word : words) { + AudioDispatcher dispatcher = AudioDispatcherFactory.fromFile( + audioFile.toFile(), + 4096, + 0 + ); + + // 为每个片段创建独立处理器 + SegmentWriter writer = new SegmentWriter( + Paths.get(outputPath, generateShortName(word.text) + ".wav"), + (int)(word.start * sampleRate), + (int)((word.end - word.start) * sampleRate), + word.confidence + ); + + dispatcher.addAudioProcessor(writer); + dispatcher.run(); // 每个片段独立运行 + } + } + + private static String generateShortName(String text) { + String clean = text.replaceAll("[^a-zA-Z0-9]", ""); + return clean.substring(0, Math.min(clean.length(), 12)) + + "_" + System.currentTimeMillis() % 10000; + } + + private Path convertToPCM(Path input) throws Exception { + Path output = Files.createTempFile("converted", ".wav"); + ProcessBuilder pb = new ProcessBuilder( + "G:\\ffmpeg-N-102781-g05f9b3a0a5-win64-gpl\\bin\\ffmpeg.exe", "-y", + "-i", input.toString(), + "-acodec", "pcm_s16le", + "-ac", "1", + "-ar", String.valueOf(sampleRate), + output.toString() + ); + pb.redirectErrorStream(true); + Process process = pb.start(); + if (process.waitFor() != 0) { + throw new IOException("FFmpeg conversion failed"); + } + return output; + } + + private String convertToCleanPinyin(String chinese) { + StringBuilder sb = new StringBuilder(); + for (char c : chinese.toCharArray()) { + String[] pinyins = PinyinHelper.toHanyuPinyinStringArray(c); + if (pinyins != null && pinyins.length > 0) { + sb.append(pinyins[0].replaceAll("[^a-zA-Z]", "")); + } + } + return sb.toString().toLowerCase(); + } + + private static class WordInfo { + final String text; + final double start; + final double end; + final float confidence; + + WordInfo(String text, double start, double end, float confidence) { + this.text = text; + this.start = start; + this.end = end; + this.confidence = confidence; + } + } + + static class AudioFadeUtil { + public static void applyFade(Path audioPath, int fadeInMs, int fadeOutMs) throws Exception { + AudioInputStream ais = AudioSystem.getAudioInputStream(audioPath.toFile()); + AudioFormat format = ais.getFormat(); + byte[] bytes = ais.readAllBytes(); + + // 转换到short数组处理 + short[] samples = new short[bytes.length / 2]; + for (int i = 0; i < samples.length; i++) { + samples[i] = (short) ((bytes[2*i+1] << 8) | (bytes[2*i] & 0xFF)); + } + + // 淡入处理 + int fadeInSamples = (int) (format.getSampleRate() * fadeInMs / 1000); + fadeInSamples = Math.min(fadeInSamples, samples.length); + for (int i = 0; i < fadeInSamples; i++) { + double factor = (double) i / fadeInSamples; + samples[i] = (short) (samples[i] * factor); + } + + // 淡出处理 + int fadeOutSamples = (int) (format.getSampleRate() * fadeOutMs / 1000); + fadeOutSamples = Math.min(fadeOutSamples, samples.length); + for (int i = samples.length - fadeOutSamples; i < samples.length; i++) { + double factor = 1.0 - (double) (i - (samples.length - fadeOutSamples)) / fadeOutSamples; + samples[i] = (short) (samples[i] * factor); + } + + // 转换回byte数组 + byte[] processedBytes = new byte[bytes.length]; + for (int i = 0; i < samples.length; i++) { + processedBytes[2*i] = (byte) (samples[i] & 0xFF); + processedBytes[2*i+1] = (byte) ((samples[i] >> 8) & 0xFF); + } + + try (AudioInputStream newAis = new AudioInputStream( + new ByteArrayInputStream(processedBytes), + format, + processedBytes.length / format.getFrameSize())) { + AudioSystem.write(newAis, AudioFileFormat.Type.WAVE, audioPath.toFile()); + } + } + } + + // 修改后的SegmentWriter实现 + private class SegmentWriter extends WaveformWriter { + private final Path finalOutputPath; + private final int targetBytes; + private int writtenBytes = 0; + private final float gain; + private final File tempRawFile; + + SegmentWriter(Path output, int startFrame, int frameCount, float confidence) { + // 使用独立控制的临时文件 + super(createTempFormat(), generateTempPath(output)); + this.finalOutputPath = output; + this.gain = Math.min(1.0f, confidence * 2); + this.targetBytes = frameCount * 2; + this.tempRawFile = createManagedTempFile(output); + + // 初始化文件通道 + initFileChannel(startFrame); + } + + private static TarsosDSPAudioFormat createTempFormat() { + return new TarsosDSPAudioFormat( + 44100, + 16, + 1, + true, + false + ); + } + + private static String generateTempPath(Path output) { + return output.getParent().toString() + File.separator + + "tmp_" + System.nanoTime() + ".raw"; + } + + private File createManagedTempFile(Path output) { + try { + File tempFile = File.createTempFile("seg_", ".raw", output.getParent().toFile()); + tempFile.deleteOnExit(); + return tempFile; + } catch (IOException e) { + throw new AudioProcessingException("无法创建临时文件", e); + } + } + + private void initFileChannel(int startFrame) { + try (RandomAccessFile raf = new RandomAccessFile(tempRawFile, "rw")) { + FileChannel channel = raf.getChannel(); + channel.position(startFrame * 2L); + } catch (IOException e) { + throw new AudioProcessingException("文件初始化失败", e); + } + } + + @Override + public boolean process(AudioEvent audioEvent) { + byte[] buffer = audioEvent.getByteBuffer().clone(); + applyGain(buffer); + writeToTemp(buffer); + return writtenBytes < targetBytes; + } + + private void applyGain(byte[] buffer) { + for (int i = 0; i < buffer.length; i += 2) { + short sample = (short) ((buffer[i+1] << 8) | (buffer[i] & 0xFF)); + sample = (short) (sample * gain); + buffer[i] = (byte) (sample & 0xFF); + buffer[i+1] = (byte) ((sample >> 8) & 0xFF); + } + } + + private void writeToTemp(byte[] buffer) { + try (FileOutputStream fos = new FileOutputStream(tempRawFile, true)) { + fos.write(buffer); + writtenBytes += buffer.length; + } catch (IOException e) { + throw new AudioProcessingException("写入临时文件失败", e); + } + } + + @Override + public void processingFinished() { + try { + convertToWav(); + applyAudioEffects(); + } finally { + cleanupResources(); + } + } + + private void convertToWav() { + try (AudioInputStream rawStream = new AudioInputStream( + new FileInputStream(tempRawFile), + JVMAudioInputStream.toAudioFormat(audioFormat), + tempRawFile.length() / audioFormat.getFrameSize())) { + + AudioSystem.write(rawStream, + AudioFileFormat.Type.WAVE, + finalOutputPath.toFile()); + } catch (IOException e) { + throw new AudioProcessingException("WAV转换失败", e); + } + } + + private void applyAudioEffects() { + try { + AudioFadeUtil.applyFade(finalOutputPath, 50, 50); + } catch (Exception e) { + System.err.println("音频效果处理失败: " + e.getMessage()); + } + } + + private void cleanupResources() { + try { + if (tempRawFile.exists() && !tempRawFile.delete()) { + tempRawFile.deleteOnExit(); + } + } catch (SecurityException e) { + System.err.println("临时文件清理失败: " + tempRawFile.getAbsolutePath()); + } + } + } + + public static class AudioProcessingException extends RuntimeException { + public AudioProcessingException(String message, Throwable cause) { + super(message, cause); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/axis/innovators/box/speech/Main.java b/src/main/java/com/axis/innovators/box/speech/Main.java new file mode 100644 index 0000000..fbc12b4 --- /dev/null +++ b/src/main/java/com/axis/innovators/box/speech/Main.java @@ -0,0 +1,15 @@ +package com.axis.innovators.box.speech; + +import java.nio.file.Paths; + +public class Main { + + public static void main(String[] args) throws Exception { + HighAccuracySpeechRecognition recognizer = new HighAccuracySpeechRecognition( + "C:\\Users\\Administrator\\Desktop\\声音识别模型\\vosk-model-cn-0.22", + "output" + ); + + recognizer.processAudio(Paths.get("G:\\鬼畜素材\\工作间\\哪吒-嗵嗵\\哪吒-嗵嗵1.wav")); + } +} diff --git a/src/main/java/org/tzd/lm/LM.java b/src/main/java/org/tzd/lm/LM.java index 0ba03b8..cd86957 100644 --- a/src/main/java/org/tzd/lm/LM.java +++ b/src/main/java/org/tzd/lm/LM.java @@ -136,22 +136,6 @@ public class LM { String prompt, String system, MessageCallback messageCallback, boolean isContinue){ - //if (isContinue){ - // return inference(modelHandle, - // ctxHandle, - // temperature, - // 0.1f, - // 100, - // 0.9f, - // 0, - // 64, - // 1.1f, - // 0.0f, - // 0.0f, - // system + "用户:" + prompt + "\n请继续回答:", - // messageCallback - // ); - //} return inference(modelHandle, ctxHandle, temperature, @@ -163,7 +147,7 @@ public class LM { 1.1f, 0.0f, 0.0f, - "{问题}" + prompt, + prompt, messageCallback ); }