diff --git a/lib/converter/ffmpeg_windows.dart b/lib/converter/ffmpeg_windows.dart new file mode 100644 index 0000000..c03de5b --- /dev/null +++ b/lib/converter/ffmpeg_windows.dart @@ -0,0 +1,71 @@ +import 'package:ffmpeg_kit_extended_flutter/ffmpeg_kit_extended_flutter.dart'; +import 'package:flutter/foundation.dart'; +import 'package:universal_io/io.dart'; +import 'package:whisper_ggml_plus/whisper_ggml_plus.dart'; + +/// FFmpeg-based implementation of [WhisperAudioConverter] for Windows. +class FFmpegWindowsConverter implements WhisperAudioConverter { + /// Internal private constructor. + FFmpegWindowsConverter._(); + + /// Registers this FFmpeg converter to the [WhisperController]. + static void register() { + WhisperController.registerAudioConverter(FFmpegWindowsConverter._()); + } + + @override + Future convert(File input) async { + // Generate output path by appending .wav to the original filename + final String outputPath = '${input.path}.wav'; + final File audioOutput = File(outputPath); + + // Clean up if the output file already exists from a previous failed run + if (await audioOutput.exists()) { + await audioOutput.delete(); + } + + // FFmpeg arguments optimized for Whisper.cpp: + // -ar 16000 : 16kHz sampling rate + // -ac 1 : Mono channel + // -c:a pcm_s16le : 16-bit little-endian PCM (WAV) + final List arguments = [ + '-y', + '-i', + '"${input.path}"', + '-ar', + '16000', + '-ac', + '1', + '-c:a', + 'pcm_s16le', + '"$outputPath"', + ]; + + debugPrint( + '⚙️ [WHISPER FFMPEG WINDOWS] Starting conversion: ${input.path} -> $outputPath', + ); + + // Execute FFmpeg command + final FFmpegSession session = FFmpegKit.execute(arguments.join(' ')); + final int returnCode = session.getReturnCode(); + + if (returnCode == 0) { + debugPrint('[WHISPER FFMPEG WINDOWS] Conversion successful'); + return audioOutput; + } else if (returnCode == 255) { + debugPrint('[WHISPER FFMPEG WINDOWS] Conversion canceled by user'); + } else { + debugPrint( + '[WHISPER FFMPEG WINDOWS] Conversion failed with returnCode $returnCode', + ); + final String? logs = session.getOutput(); + if (logs != null) { + debugPrint('--- FFmpeg Logs ---'); + debugPrint(logs); + debugPrint('-------------------'); + } + } + + return null; + } +} diff --git a/lib/main.dart b/lib/main.dart index 8093951..93ea1fc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:caption_trans/converter/ffmpeg_macos.dart'; +import 'package:caption_trans/converter/ffmpeg_windows.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -19,6 +20,8 @@ void main() async { // so it works within the macOS sandbox. if (Platform.isMacOS) { FFmpegMacOsConverter.register(); + } else if (Platform.isWindows) { + FFmpegWindowsConverter.register(); } runApp(CaptionTransApp(settingsService: settingsService)); diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..1859a62 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) ffmpeg_kit_extended_flutter_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FfmpegKitExtendedFlutterPlugin"); + ffmpeg_kit_extended_flutter_plugin_register_with_registrar(ffmpeg_kit_extended_flutter_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index eaef5de..68d484c 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + ffmpeg_kit_extended_flutter ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/pubspec.lock b/pubspec.lock index 7ebe970..61895b5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" url: "https://pub.flutter-io.cn" source: hosted - version: "91.0.0" + version: "93.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b url: "https://pub.flutter-io.cn" source: hosted - version: "8.4.1" + version: "10.0.1" args: dependency: transitive description: @@ -153,6 +153,15 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" + ffmpeg_kit_extended_flutter: + dependency: "direct main" + description: + path: flutter + ref: HEAD + resolved-ref: efab14f66152feeaa528bfd7d8b7c5fa4f1043c0 + url: "https://github.com/cddqssc/ffmpeg-kit-extended.git" + source: git + version: "0.2.0" ffmpeg_kit_flutter_new_min: dependency: "direct main" description: @@ -797,9 +806,11 @@ packages: whisper_ggml_plus: dependency: "direct main" description: - path: whisper_ggml_plus - relative: true - source: path + path: "." + ref: HEAD + resolved-ref: b57159c40d3356e51b32279c6cab12a9cb65cde3 + url: "https://github.com/cddqssc/whisper_ggml_plus.git" + source: git version: "1.4.1" win32: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index feca0a9..1bc2074 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,10 +15,13 @@ dependencies: # Whisper / Audio whisper_ggml_plus: - path: whisper_ggml_plus + git: + url: https://github.com/cddqssc/whisper_ggml_plus.git ffmpeg_kit_flutter_new_min: ^3.1.0 - - universal_io: ^2.3.1 + ffmpeg_kit_extended_flutter: + git: + url: https://github.com/cddqssc/ffmpeg-kit-extended.git + path: flutter # State Management flutter_bloc: ^8.1.5 @@ -37,6 +40,12 @@ dependencies: cupertino_icons: ^1.0.8 openai_dart: ^1.4.0 + universal_io: ^2.3.1 + +ffmpeg_kit_extended_config: + linux: "https://github.com/akashskypatel/ffmpeg-kit-builders/releases/download/v0.8.2-linux/bundle-base-linux-x86_64-shared-small-gpl.zip" + windows: "https://github.com/akashskypatel/ffmpeg-kit-builders/releases/download/v0.8.2-windows/bundle-base-windows-x86_64-shared-small-gpl.zip" + dev_dependencies: flutter_test: sdk: flutter diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 50c008f..781a73a 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -2,41 +2,27 @@ cmake_minimum_required(VERSION 3.14) project(caption_trans LANGUAGES CXX) -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. set(BINARY_NAME "caption_trans") -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. cmake_policy(VERSION 3.14...3.25) -# Define build configuration option. get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(IS_MULTICONFIG) - set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" - CACHE STRING "" FORCE) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" CACHE STRING "" FORCE) else() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") + set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Profile" "Release") endif() endif() -# Define settings for the Profile build mode. + set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") -# Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") @@ -45,64 +31,31 @@ function(APPLY_STANDARD_SETTINGS TARGET) target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() -# Flutter library and tool build rules. set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) -# Application build; see runner/CMakeLists.txt. add_subdirectory("runner") - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. include(flutter/generated_plugins.cmake) - # === Installation === -# Support files are copied into place next to the executable, so that it can -# run in place. This is done instead of making a separate bundle (as on Linux) -# so that building and running from within Visual Studio will work. -set(BUILD_BUNDLE_DIR "$") -# Make the "install" step default, as it's required to run. -set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - +set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "..." FORCE) set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime) +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) if(PLUGIN_BUNDLED_LIBRARIES) - install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() -# Copy the native assets provided by the build.dart from all packages. set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) +install(DIRECTORY "${NATIVE_ASSETS_DIR}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) +install(CODE "file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")" COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) -# Install the AOT library on non-Debug builds only. -install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - CONFIGURATIONS Profile;Release - COMPONENT Runtime) +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" CONFIGURATIONS Profile;Release COMPONENT Runtime) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..4055549 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FfmpegKitExtendedFlutterPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FfmpegKitExtendedFlutterPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index d0cdc01..ec1064b 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + ffmpeg_kit_extended_flutter ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt index 394917c..7fe6e83 100644 --- a/windows/runner/CMakeLists.txt +++ b/windows/runner/CMakeLists.txt @@ -1,11 +1,6 @@ cmake_minimum_required(VERSION 3.14) project(runner LANGUAGES CXX) -# Define the application target. To change its name, change BINARY_NAME in the -# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer -# work. -# -# Any new source files that you add to the application should be added here. add_executable(${BINARY_NAME} WIN32 "flutter_window.cpp" "main.cpp" @@ -16,25 +11,32 @@ add_executable(${BINARY_NAME} WIN32 "runner.exe.manifest" ) -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. apply_standard_settings(${BINARY_NAME}) -# Add preprocessor definitions for the build version. target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") -# Disable Windows macros that collide with C++ standard library functions. target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") -# Add dependency libraries and include directories. Add any application-specific -# dependencies here. target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") -# Run the Flutter tool portions of the build. This must not be removed. add_dependencies(${BINARY_NAME} flutter_assemble) + +# Unified build-time dependency copy script +add_custom_command(TARGET ${BINARY_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} + -D "BINARY_DIR=${CMAKE_BINARY_DIR}" + -D "SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}" + -D "OUTPUT_DIR=$" + -D "FLUTTER_LIBRARY=${FLUTTER_LIBRARY}" + -D "FLUTTER_ICU_DATA_FILE=${FLUTTER_ICU_DATA_FILE}" + -D "PROJECT_BUILD_DIR=${PROJECT_BUILD_DIR}" + -D "BUILD_TYPE=$" + -P "${CMAKE_CURRENT_SOURCE_DIR}/copy_dependencies.cmake" + VERBATIM +) diff --git a/windows/runner/copy_dependencies.cmake b/windows/runner/copy_dependencies.cmake new file mode 100644 index 0000000..321db15 --- /dev/null +++ b/windows/runner/copy_dependencies.cmake @@ -0,0 +1,50 @@ +# Robust build-time dependency copy script + +message(STATUS "DEBUG: Running copy_dependencies.cmake") +message(STATUS "DEBUG: BINARY_DIR: ${BINARY_DIR}") +message(STATUS "DEBUG: OUTPUT_DIR: ${OUTPUT_DIR}") + +# 1. Copy Core Flutter DLLs +if(EXISTS "${FLUTTER_LIBRARY}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FLUTTER_LIBRARY}" "${OUTPUT_DIR}") +endif() + +if(EXISTS "${FLUTTER_ICU_DATA_FILE}") + execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory "${OUTPUT_DIR}/data") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FLUTTER_ICU_DATA_FILE}" "${OUTPUT_DIR}/data") +endif() + +# 1.1 Copy AOT Data (app.so) for Release/Profile +if(NOT BUILD_TYPE STREQUAL "Debug") + file(GLOB_RECURSE app_so "${BINARY_DIR}/*/app.so") + if(app_so) + list(GET app_so 0 first_app_so) + message(STATUS "DEBUG: Found app.so at ${first_app_so}") + execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory "${OUTPUT_DIR}/data") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${first_app_so}" "${OUTPUT_DIR}/data") + endif() +endif() + +# 2. Copy Assets +if(EXISTS "${PROJECT_BUILD_DIR}/flutter_assets") + execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory "${OUTPUT_DIR}/data/flutter_assets") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_BUILD_DIR}/flutter_assets" "${OUTPUT_DIR}/data/flutter_assets") +endif() + +# 3. Copy Plugin DLLs (Search and find) +set(DLL_NAMES + "whisper_ggml_plus.dll" + "ffmpeg_kit_extended_flutter_plugin.dll" + "libffmpegkit.dll" +) + +foreach(dll ${DLL_NAMES}) + file(GLOB_RECURSE found_dll "${BINARY_DIR}/*/${dll}") + if(found_dll) + list(GET found_dll 0 first_found) # Take the first one found + message(STATUS "DEBUG: Found ${dll} at ${first_found}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${first_found}" "${OUTPUT_DIR}") + else() + message(STATUS "DEBUG: Could not find ${dll} in ${BINARY_DIR}") + endif() +endforeach() diff --git a/windows/runner/copy_libffmpegkit.cmake b/windows/runner/copy_libffmpegkit.cmake new file mode 100644 index 0000000..1417970 --- /dev/null +++ b/windows/runner/copy_libffmpegkit.cmake @@ -0,0 +1,16 @@ +set(SEARCH_PATHS + "${CMAKE_BINARY_DIR}/plugins/ffmpeg_kit_extended_flutter/libffmpegkit.dll" + "${CMAKE_BINARY_DIR}/install/libffmpegkit.dll" + "${CMAKE_BINARY_DIR}/runner/Debug/libffmpegkit.dll" +) + +foreach(path ${SEARCH_PATHS}) + if(EXISTS "${path}") + message(STATUS "DEBUG: Found libffmpegkit.dll at ${path}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${path}" + "${CMAKE_BINARY_DIR}/runner/${BUILD_TYPE}" + ) + return() + endif() +endforeach()