├── readme_assets ├── demo.gif ├── main2.png └── runserver.png ├── windows ├── runner │ ├── resources │ │ └── app_icon.ico │ ├── resource.h │ ├── utils.h │ ├── runner.exe.manifest │ ├── flutter_window.h │ ├── main.cpp │ ├── CMakeLists.txt │ ├── utils.cpp │ ├── flutter_window.cpp │ ├── Runner.rc │ ├── win32_window.h │ └── win32_window.cpp ├── .gitignore ├── flutter │ ├── generated_plugin_registrant.h │ ├── generated_plugins.cmake │ ├── generated_plugin_registrant.cc │ └── CMakeLists.txt └── CMakeLists.txt ├── runServer.bat ├── lib ├── gif │ ├── git_frame.dart │ ├── gif_controller.dart │ └── gif_view.dart ├── measure_size.dart ├── global.dart ├── toolbar.dart ├── field.dart ├── l10n │ ├── intl_zh_CN.arb │ └── intl_en.arb ├── my_divider_painter.dart ├── options.dart ├── video_view.dart ├── generated │ ├── intl │ │ ├── messages_all.dart │ │ ├── messages_zh_CN.dart │ │ └── messages_en.dart │ └── l10n.dart ├── util.dart ├── window_frame.dart ├── settings.dart ├── data.dart ├── frame_view.dart ├── main.dart ├── file_preview.dart ├── status_bar.dart ├── server.dart ├── settings_view.dart ├── options_menu_button.dart ├── file_select_view.dart └── face_swap_list.dart ├── .gitignore ├── LICENSE ├── test └── widget_test.dart ├── .vscode ├── launch.json └── settings.json ├── analysis_options.yaml ├── .metadata ├── README_zh.md ├── README.md ├── pubspec.yaml └── server.py /readme_assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorobject/faceswap/HEAD/readme_assets/demo.gif -------------------------------------------------------------------------------- /readme_assets/main2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorobject/faceswap/HEAD/readme_assets/main2.png -------------------------------------------------------------------------------- /readme_assets/runserver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorobject/faceswap/HEAD/readme_assets/runserver.png -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vectorobject/faceswap/HEAD/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /runServer.bat: -------------------------------------------------------------------------------- 1 | chcp 65001>nul 2 | call C:\ProgramData\miniconda3\Scripts\activate.bat C:\ProgramData\miniconda3 3 | call conda activate roop 4 | pushd G:\github\roop\roop_github\ 5 | python -u server.py %1 6 | pause 7 | popd 8 | echo errorlevel:%errorlevel% 9 | exit -------------------------------------------------------------------------------- /lib/gif/git_frame.dart: -------------------------------------------------------------------------------- 1 | ///Code modification from https://github.com/RafaelBarbosatec/gif_view 2 | 3 | import 'package:flutter/rendering.dart'; 4 | 5 | class GifFrame { 6 | final ImageInfo imageInfo; 7 | final Duration duration; 8 | 9 | GifFrame(this.imageInfo, this.duration); 10 | } 11 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | /output 46 | /images 47 | /run.bat 48 | -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /lib/measure_size.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | 4 | typedef void OnWidgetSizeChange(Size? size); 5 | 6 | class MeasureSizeRenderObject extends RenderProxyBox { 7 | Size? oldSize; 8 | final OnWidgetSizeChange onChange; 9 | 10 | MeasureSizeRenderObject(this.onChange); 11 | 12 | @override 13 | void performLayout() { 14 | super.performLayout(); 15 | 16 | Size? newSize = child?.size; 17 | if (oldSize == newSize) return; 18 | oldSize = newSize; 19 | WidgetsBinding.instance.addPostFrameCallback((_) { 20 | onChange(newSize); 21 | }); 22 | } 23 | } 24 | 25 | class MeasureSize extends SingleChildRenderObjectWidget { 26 | final OnWidgetSizeChange onChange; 27 | 28 | const MeasureSize({ 29 | required Widget child, 30 | required this.onChange, 31 | Key? key, 32 | }) : super(key: key, child: child); 33 | 34 | @override 35 | RenderObject createRenderObject(BuildContext context) { 36 | return MeasureSizeRenderObject(onChange); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_windows 7 | flutter_meedu_videoplayer 8 | fullscreen_window 9 | media_kit_libs_windows_video 10 | media_kit_video 11 | screen_brightness_windows 12 | screen_retriever 13 | window_manager 14 | ) 15 | 16 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 17 | media_kit_native_event_loop 18 | ) 19 | 20 | set(PLUGIN_BUNDLED_LIBRARIES) 21 | 22 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 24 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 27 | endforeach(plugin) 28 | 29 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 30 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 31 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 32 | endforeach(ffi_plugin) 33 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 vectorobject 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/global.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:flutter_meedu_videoplayer/meedu_player.dart'; 3 | import 'package:path/path.dart' as path; 4 | 5 | import 'options.dart'; 6 | import 'server.dart'; 7 | import 'settings.dart'; 8 | 9 | class Global { 10 | static var imagesPath = 11 | Directory(path.join(Directory.current.parent.path, "images")); 12 | static var resultDir = 13 | Directory(path.join(Directory.current.parent.path, "output")); 14 | static var tempDir = 15 | Directory(path.join(Directory.current.parent.path, "temp")); 16 | static var optionsFile = 17 | File(path.join(Directory.current.parent.path, "options.json")); 18 | static var settingsFile = 19 | File(path.join(Directory.current.parent.path, "settings.json")); 20 | static var _isInited = false; 21 | static Future init() async { 22 | if (_isInited) { 23 | return; 24 | } 25 | _isInited = true; 26 | initMeeduPlayer(); 27 | if (!(await Global.imagesPath.exists())) { 28 | await Global.imagesPath.create(); 29 | } 30 | await Settings.init(); 31 | Options.read(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/toolbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Toolbar extends StatefulWidget { 4 | final List children; 5 | const Toolbar({super.key, required this.children}); 6 | 7 | @override 8 | State createState() => _ToolbarState(); 9 | } 10 | 11 | class _ToolbarState extends State { 12 | @override 13 | Widget build(BuildContext context) { 14 | var theme = Theme.of(context); 15 | return IconButtonTheme( 16 | data: IconButtonThemeData( 17 | style: theme.iconButtonTheme.style?.copyWith( 18 | iconSize: MaterialStatePropertyAll(20), 19 | padding: MaterialStatePropertyAll(EdgeInsets.zero))), 20 | child: Container( 21 | alignment: Alignment.centerLeft, 22 | padding: const EdgeInsets.symmetric(horizontal: 10), 23 | height: 25, 24 | decoration: BoxDecoration( 25 | border: Border( 26 | bottom: BorderSide(color: theme.colorScheme.outlineVariant)), 27 | //color: theme.colorScheme.primaryContainer, 28 | ), 29 | child: Row(children: widget.children))); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:faceswap/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class RangedIntField extends Field { 4 | int min; 5 | int max; 6 | RangedIntField(super.fieldName, super.value, {this.min = 0, this.max = 100}); 7 | @override 8 | set value(int newValue) { 9 | if (newValue > max) { 10 | newValue = max; 11 | } else if (newValue < min) { 12 | newValue = min; 13 | } 14 | super.value = newValue; 15 | } 16 | } 17 | 18 | class ChoicesField extends Field { 19 | List choices; 20 | T defaultValue; 21 | ChoicesField(String field, this.defaultValue, this.choices) 22 | : super(field, defaultValue); 23 | @override 24 | set value(T newValue) { 25 | if (!choices.contains(newValue)) { 26 | newValue = defaultValue; 27 | } 28 | super.value = newValue; 29 | } 30 | } 31 | 32 | class Field extends ValueNotifier { 33 | String fieldName; 34 | Field(this.fieldName, super.value); 35 | 36 | bool readFromObj(Map? obj) { 37 | if (obj != null && obj.containsKey(fieldName)) { 38 | try { 39 | value = obj[fieldName]; 40 | return true; 41 | } catch (err) {} 42 | } 43 | return false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/l10n/intl_zh_CN.arb: -------------------------------------------------------------------------------- 1 | { 2 | "file_filter_type_all":"全部" 3 | ,"file_filter_type_img":"图片" 4 | ,"file_filter_type_gif_video":"GIF和视频" 5 | ,"add_folder":"添加文件夹" 6 | ,"parent_folder":"上层文件夹" 7 | ,"reveal_in_file_explorer":"在资源管理器中显示" 8 | ,"refresh":"刷新" 9 | ,"detect_faces":"识别人脸" 10 | ,"video":"视频" 11 | ,"executing":"执行中" 12 | ,"try_to_read_service_port_from_file":"尝试从文件读取服务端口:{path}" 13 | ,"using_port_read_from_file":"使用从文件读取到的端口:{port}" 14 | ,"connection_failed_and_restart":"连接失败,重启服务中" 15 | ,"log":"日志" 16 | ,"generating":"生成中" 17 | ,"generate":"生成" 18 | ,"settings":"设置" 19 | ,"face_enhance":"增强" 20 | ,"close":"关闭" 21 | ,"source":"源" 22 | ,"target":"目标" 23 | ,"result":"结果" 24 | ,"delete":"删除" 25 | ,"options":"选项" 26 | ,"keep_fps":"保持FPS" 27 | ,"audio":"音频" 28 | ,"keep_frames":"保留序列帧文件夹" 29 | ,"min_similarity":"视频脸部最小相似度:" 30 | ,"output_video_encoder":"视频编码:" 31 | ,"output_video_quality":"视频编码质量:" 32 | ,"temp_frame_format":"序列帧格式:" 33 | ,"temp_frame_quality":"序列帧质量:" 34 | ,"set_as_default":"设为默认" 35 | ,"provider_desc":"拖动标签调整顺序,越往左优先级越高。" 36 | ,"execution_providers":"执行提供程序:" 37 | ,"execution_threads":"执行线程数:" 38 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "(Windows) 启动", 10 | "type": "cppvsdbg", 11 | "request": "launch", 12 | "program": "${workspaceFolder}/build/windows/runner/Debug/faceswap.exe", 13 | "args": [], 14 | "stopAtEntry": false, 15 | "cwd": "${workspaceFolder}", 16 | "environment": [], 17 | }, 18 | { 19 | "name": "faceswap", 20 | "request": "launch", 21 | "type": "dart", 22 | "program": "lib/main.dart", 23 | "justMyCode":false, 24 | "purpose": ["debug-in-terminal"] 25 | }, 26 | { 27 | "name": "faceswap (profile mode)", 28 | "request": "launch", 29 | "type": "dart", 30 | "flutterMode": "profile", 31 | "program": "lib/main.dart" 32 | }, 33 | { 34 | "name": "faceswap (release mode)", 35 | "request": "launch", 36 | "type": "dart", 37 | "flutterMode": "release", 38 | "program": "lib/main.dart" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /lib/my_divider_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:multi_split_view/multi_split_view.dart'; 3 | 4 | class MyDividerPainter extends DividerPainter { 5 | MyDividerPainter( 6 | {super.backgroundColor, 7 | super.highlightedBackgroundColor, 8 | super.animationEnabled = DividerPainter.defaultAnimationEnabled, 9 | super.animationDuration = DividerPainter.defaultAnimationDuration}); 10 | 11 | @override 12 | void paint( 13 | {required Axis dividerAxis, 14 | required bool resizable, 15 | required bool highlighted, 16 | required Canvas canvas, 17 | required Size dividerSize, 18 | required Map animatedValues}) { 19 | Color? color = backgroundColor; 20 | if (animationEnabled && 21 | animatedValues.containsKey(DividerPainter.backgroundKey)) { 22 | color = animatedValues[DividerPainter.backgroundKey]; 23 | } else if (highlighted && highlightedBackgroundColor != null) { 24 | color = highlightedBackgroundColor; 25 | } 26 | 27 | if (color != null) { 28 | var paint = Paint() 29 | ..style = PaintingStyle.fill 30 | ..color = color 31 | ..isAntiAlias = true; 32 | canvas.drawRRect( 33 | RRect.fromLTRBR(1, 0, dividerSize.width - 1, dividerSize.height, 34 | const Radius.circular(10)), 35 | paint); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.CreateAndShow(L"faceswap", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /lib/l10n/intl_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "file_filter_type_all":"All" 3 | ,"file_filter_type_img":"Image" 4 | ,"file_filter_type_gif_video":"GIF and Video" 5 | ,"add_folder":"Add Folder" 6 | ,"parent_folder":"Parent Folder" 7 | ,"reveal_in_file_explorer":"Show in File Explorer" 8 | ,"refresh":"Refresh" 9 | ,"detect_faces":"Detect Faces" 10 | ,"video":"Video" 11 | ,"executing":"Executing" 12 | ,"try_to_read_service_port_from_file":"Try to read service port from file:{path}" 13 | ,"using_port_read_from_file":"Using port read from file:{port}" 14 | ,"connection_failed_and_restart":"Connection failed, restarting server" 15 | ,"log":"Log" 16 | ,"generating":"Generating" 17 | ,"generate":"Generate" 18 | ,"settings":"Settings" 19 | ,"face_enhance":"Enhance" 20 | ,"close":"Close" 21 | ,"source":"Source" 22 | ,"target":"Target" 23 | ,"result":"Result" 24 | ,"delete":"Delete" 25 | ,"options":"Options" 26 | ,"keep_fps":"Keep FPS" 27 | ,"audio":"Audio" 28 | ,"keep_frames":"Keep frames folder" 29 | ,"min_similarity":"Video facial minimum similarity:" 30 | ,"output_video_encoder":"Video Encoder:" 31 | ,"output_video_quality":"Video Quality:" 32 | ,"temp_frame_format":"Frame Format:" 33 | ,"temp_frame_quality":"Frame Quality:" 34 | ,"set_as_default":"Set as Default" 35 | ,"provider_desc":"Drag the labels to adjust the order, with higher priority as they move to the left." 36 | ,"execution_providers":"Execution Providers:" 37 | ,"execution_threads":"Execution Threads:" 38 | } -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | void RegisterPlugins(flutter::PluginRegistry* registry) { 19 | FileSelectorWindowsRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 21 | FlutterMeeduVideoplayerPluginCApiRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("FlutterMeeduVideoplayerPluginCApi")); 23 | FullscreenWindowPluginCApiRegisterWithRegistrar( 24 | registry->GetRegistrarForPlugin("FullscreenWindowPluginCApi")); 25 | MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( 26 | registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi")); 27 | MediaKitVideoPluginCApiRegisterWithRegistrar( 28 | registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi")); 29 | ScreenBrightnessWindowsPluginRegisterWithRegistrar( 30 | registry->GetRegistrarForPlugin("ScreenBrightnessWindowsPlugin")); 31 | ScreenRetrieverPluginRegisterWithRegistrar( 32 | registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); 33 | WindowManagerPluginRegisterWithRegistrar( 34 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 35 | } 36 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 17 | base_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 18 | - platform: android 19 | create_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 20 | base_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 21 | - platform: ios 22 | create_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 23 | base_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 24 | - platform: linux 25 | create_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 26 | base_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 27 | - platform: macos 28 | create_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 29 | base_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 30 | - platform: web 31 | create_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 32 | base_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 33 | - platform: windows 34 | create_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 35 | base_revision: 6fb746fc3656e30e676592ec675a64e3a28bbe88 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /lib/options.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:faceswap/status_bar.dart'; 4 | 5 | import 'field.dart'; 6 | import 'global.dart'; 7 | 8 | class Options { 9 | static Field keepFPS = Field("keepFPS", true); 10 | static Field audio = Field("audio", true); 11 | static Field keepFrames = Field("keepFrames", false); 12 | static RangedIntField minSimilarity = RangedIntField("minSimilarity", 30); 13 | static ChoicesField tempFrameFormat = 14 | ChoicesField("tempFrameFormat", "PNG", ["PNG", "JPG"]); 15 | static RangedIntField tempFrameQuality = 16 | RangedIntField("tempFrameQuality", 0); 17 | static ChoicesField outputVideoEncoder = ChoicesField( 18 | "outputVideoEncoder", 19 | "libx264", 20 | ['libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc']); 21 | static RangedIntField outputVideoQuality = 22 | RangedIntField("outputVideoQuality", 35); 23 | static final List _all = [ 24 | keepFPS, 25 | audio, 26 | keepFrames, 27 | minSimilarity, 28 | tempFrameFormat, 29 | tempFrameQuality, 30 | outputVideoEncoder, 31 | outputVideoQuality 32 | ]; 33 | static read() async { 34 | if (await Global.optionsFile.exists()) { 35 | var str = await Global.optionsFile.readAsString(); 36 | Map obj; 37 | try { 38 | obj = jsonDecode(str); 39 | } catch (err) { 40 | StatusBar.appendOutput( 41 | "Read ${Global.optionsFile.path} err:$err", true); 42 | return; 43 | } 44 | for (var item in _all) { 45 | item.readFromObj(obj); 46 | } 47 | } 48 | } 49 | 50 | static save() async { 51 | var obj = {}; 52 | for (var item in _all) { 53 | obj[item.fieldName] = item.value; 54 | } 55 | await Global.optionsFile.writeAsString(jsonEncode(obj)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 37 | 38 | # Run the Flutter tool portions of the build. This must not be removed. 39 | add_dependencies(${BINARY_NAME} flutter_assemble) 40 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | ### [English](README.md)|[中文] 2 | 3 | 4 | ## **一个给[roop](https://github.com/s0md3v/roop)做的UI** 5 | 6 | 7 | ### **特点:** 8 | 9 | #### 1、支持图像、gif、视频 10 | 11 | #### 2、支持换指定的脸 12 | 13 | ![](https://github.com/vectorobject/faceswap/blob/main/readme_assets/main2.png?raw=true) 14 | 15 | 16 | 17 | ### **首次运行步骤:** 18 | 19 | 20 | #### 1、确保能正常运行[roop项目](https://github.com/s0md3v/roop)(版本:1.3.2) 21 | 22 | #### 2、新建一个文件夹,例如:`E:\ff` (以下全部用此路径作为示例) 23 | 24 | #### 3、下载[faceswap release版](https://github.com/vectorobject/faceswap/releases),并将其解压缩到文件夹里: `E:\ff\` 25 | 26 | ##### 此时得到的目录结构如下: 27 | 28 | ``` 29 | ---E:\ff\ 30 | ------faceswap\ 31 | ---------faceswap.exe 32 | ------runServer.bat 33 | ------server.py 34 | ``` 35 | 36 | #### 4、将`server.py`移动到roop项目的根目录下 37 | 38 | #### 5、请根据自己的环境修改`runServer.bat` 39 | 40 | ##### 例如我使用的是miniconda,安装路径是 `G:\miniconda3\`,那么内容如下: 41 | 42 | ```bat 43 | chcp 65001>nul 44 | call G:\miniconda3\Scripts\activate.bat G:\miniconda3 45 | call conda activate roop 46 | pushd D:\roop\roop 47 | python -u server.py %1 48 | ``` 49 | 50 | ##### 如果你使用其他的方法也可以,只要能正常运行`server.py`就行 51 | 52 | #### 6.运行`E:\ff\faceswap\faceswap.exe` 53 | 54 | ##### 如果正常,启动后会弹出一个命令窗口,如下所示: 55 | 56 | ![](https://github.com/vectorobject/faceswap/blob/main/readme_assets/runserver.png?raw=true) 57 | 58 | ##### 如果显示其他错误,请根据提示检查配置是否正确 59 | 60 | 61 | 62 | 63 | ### **使用方法:** 64 | ![](https://github.com/vectorobject/faceswap/blob/main/readme_assets/demo.gif?raw=true) 65 | 66 | #### 1、在 `E:\ff\images\` 中放入你喜欢的图片 67 | 68 | #### 2、双击选择源图片和目标图片(或GIF、视频) 69 | 70 | #### 3、分别点击【识别人脸】,等待人脸被标记出来 71 | 72 | ##### 如果是GIF或者视频,点击【识别人脸】时会在当前时间点截图后再标记,可以从多个时间点提取人脸 73 | 74 | #### 4、双击需要交换的人脸,它们会被添加到最右边的列表中 75 | 76 | #### 5、拖动调整列表中人脸的顺序 77 | 78 | #### 6、点击生成 79 | 80 | 81 | 82 | 83 | ### **调试python脚本:** 84 | 85 | #### 1、新建一个文件:`E:\ff\server_port.txt`,文件内容是一个端口号(53499) 86 | 87 | #### 2、IDE里以调试模式运行`server.py` 88 | 89 | #### 3、运行`E:\ff\faceswap\faceswap.exe` 90 | 91 | 92 | ### **其它** 93 | 94 | #### Flutter SDK 版本:3.10.4 -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | std::string utf8_string; 52 | if (target_length == 0 || target_length > utf8_string.max_size()) { 53 | return utf8_string; 54 | } 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.html": "html", 4 | "any": "cpp", 5 | "array": "cpp", 6 | "atomic": "cpp", 7 | "bit": "cpp", 8 | "*.tcc": "cpp", 9 | "cctype": "cpp", 10 | "chrono": "cpp", 11 | "clocale": "cpp", 12 | "cmath": "cpp", 13 | "compare": "cpp", 14 | "concepts": "cpp", 15 | "condition_variable": "cpp", 16 | "cstdarg": "cpp", 17 | "cstddef": "cpp", 18 | "cstdint": "cpp", 19 | "cstdio": "cpp", 20 | "cstdlib": "cpp", 21 | "cstring": "cpp", 22 | "ctime": "cpp", 23 | "cwchar": "cpp", 24 | "cwctype": "cpp", 25 | "deque": "cpp", 26 | "map": "cpp", 27 | "set": "cpp", 28 | "unordered_map": "cpp", 29 | "vector": "cpp", 30 | "exception": "cpp", 31 | "algorithm": "cpp", 32 | "functional": "cpp", 33 | "iterator": "cpp", 34 | "memory": "cpp", 35 | "memory_resource": "cpp", 36 | "numeric": "cpp", 37 | "optional": "cpp", 38 | "random": "cpp", 39 | "ratio": "cpp", 40 | "string": "cpp", 41 | "string_view": "cpp", 42 | "system_error": "cpp", 43 | "tuple": "cpp", 44 | "type_traits": "cpp", 45 | "utility": "cpp", 46 | "initializer_list": "cpp", 47 | "iosfwd": "cpp", 48 | "iostream": "cpp", 49 | "istream": "cpp", 50 | "limits": "cpp", 51 | "mutex": "cpp", 52 | "new": "cpp", 53 | "ostream": "cpp", 54 | "ranges": "cpp", 55 | "stdexcept": "cpp", 56 | "stop_token": "cpp", 57 | "streambuf": "cpp", 58 | "thread": "cpp", 59 | "cfenv": "cpp", 60 | "cinttypes": "cpp", 61 | "typeinfo": "cpp", 62 | "variant": "cpp" 63 | }, 64 | "code-runner.executorMap":{ 65 | "cpp":"chcp 65001 " 66 | }, 67 | "dart.flutterSdkPaths": ["G:\\flutter\\fluttersdk"], 68 | "dart.flutterSdkPath": "G:\\flutter\\fluttersdk\\3.10.4", 69 | } -------------------------------------------------------------------------------- /lib/video_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_meedu_videoplayer/meedu_player.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | 7 | class VideoView extends StatefulWidget { 8 | late final String url; 9 | final MeeduPlayerController? controller; 10 | 11 | VideoView({super.key, required File file, this.controller}) { 12 | url = file.path; 13 | } 14 | 15 | @override 16 | State createState() => _VideoViewState(); 17 | } 18 | 19 | class _VideoViewState extends State { 20 | late MeeduPlayerController _controller; 21 | 22 | @override 23 | void initState() { 24 | _init(); 25 | super.initState(); 26 | } 27 | 28 | Future _init() async { 29 | if (widget.controller == null) { 30 | _controller = MeeduPlayerController(controlsStyle: ControlsStyle.primary); 31 | } else { 32 | _controller = widget.controller!; 33 | } 34 | _controller.enabledButtons = const EnabledButtons(videoFit: false); 35 | var prefs = await SharedPreferences.getInstance(); 36 | if (!prefs.containsKey('fit')) { 37 | _controller.videoFit.value = BoxFit.contain; 38 | _controller.setUserPreferenceForFit(); 39 | } 40 | 41 | _setDataSource(widget.url); 42 | } 43 | 44 | _setDataSource(String url) async { 45 | if (_controller.videoPlayerController?.dataSource != widget.url) { 46 | await _controller.setDataSource( 47 | DataSource( 48 | source: url, 49 | type: DataSourceType.network, 50 | ), 51 | autoplay: true, 52 | ); 53 | } 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | _setDataSource(widget.url); 59 | Widget child = MeeduVideoPlayer( 60 | controller: _controller, 61 | ); 62 | child = TextButtonTheme( 63 | data: const TextButtonThemeData( 64 | style: ButtonStyle( 65 | backgroundColor: MaterialStatePropertyAll(Colors.transparent), 66 | padding: MaterialStatePropertyAll(EdgeInsets.zero))), 67 | child: child, 68 | ); 69 | return child; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that looks up messages for specific locales by 3 | // delegating to the appropriate library. 4 | 5 | // Ignore issues from commonly used lints in this file. 6 | // ignore_for_file:implementation_imports, file_names, unnecessary_new 7 | // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering 8 | // ignore_for_file:argument_type_not_assignable, invalid_assignment 9 | // ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases 10 | // ignore_for_file:comment_references 11 | 12 | import 'dart:async'; 13 | 14 | import 'package:flutter/foundation.dart'; 15 | import 'package:intl/intl.dart'; 16 | import 'package:intl/message_lookup_by_library.dart'; 17 | import 'package:intl/src/intl_helpers.dart'; 18 | 19 | import 'messages_en.dart' as messages_en; 20 | import 'messages_zh_CN.dart' as messages_zh_cn; 21 | 22 | typedef Future LibraryLoader(); 23 | Map _deferredLibraries = { 24 | 'en': () => new SynchronousFuture(null), 25 | 'zh_CN': () => new SynchronousFuture(null), 26 | }; 27 | 28 | MessageLookupByLibrary? _findExact(String localeName) { 29 | switch (localeName) { 30 | case 'en': 31 | return messages_en.messages; 32 | case 'zh_CN': 33 | return messages_zh_cn.messages; 34 | default: 35 | return null; 36 | } 37 | } 38 | 39 | /// User programs should call this before using [localeName] for messages. 40 | Future initializeMessages(String localeName) { 41 | var availableLocale = Intl.verifiedLocale( 42 | localeName, (locale) => _deferredLibraries[locale] != null, 43 | onFailure: (_) => null); 44 | if (availableLocale == null) { 45 | return new SynchronousFuture(false); 46 | } 47 | var lib = _deferredLibraries[availableLocale]; 48 | lib == null ? new SynchronousFuture(false) : lib(); 49 | initializeInternalMessageLookup(() => new CompositeMessageLookup()); 50 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 51 | return new SynchronousFuture(true); 52 | } 53 | 54 | bool _messagesExistFor(String locale) { 55 | try { 56 | return _findExact(locale) != null; 57 | } catch (e) { 58 | return false; 59 | } 60 | } 61 | 62 | MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { 63 | var actualLocale = 64 | Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); 65 | if (actualLocale == null) return null; 66 | return _findExact(actualLocale); 67 | } 68 | -------------------------------------------------------------------------------- /lib/util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:crc32_checksum/crc32_checksum.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:path/path.dart' as pathlib; 6 | 7 | extension FileExt on File { 8 | bool get isGifOrVideo { 9 | return Util.isGifOrVideo(path); 10 | } 11 | 12 | bool get isGif { 13 | return Util.isGif(path); 14 | } 15 | 16 | bool get isVideo { 17 | return Util.isVideo(path); 18 | } 19 | 20 | bool get isImg { 21 | return Util.isImg(path); 22 | } 23 | 24 | bool get isImgOrGif { 25 | return Util.isImgOrGif(path); 26 | } 27 | 28 | String get ext { 29 | return Util.getExt(path); 30 | } 31 | } 32 | 33 | class Util { 34 | static const imageFormats = [ 35 | "png", 36 | "jpg", 37 | "jpeg", 38 | "webp", 39 | "bmp", 40 | ]; 41 | 42 | static const videoFormats = [ 43 | "mp4", 44 | "mov", 45 | "avi", 46 | "mkv", 47 | "webm", 48 | "flv", 49 | "wmv", 50 | "ogv", 51 | "mpg", 52 | "mpeg", 53 | "3gp", 54 | "rm", 55 | "dv" 56 | ]; 57 | 58 | static String getExt(String path) { 59 | var t = pathlib.split(path); 60 | var tArr = t.last.split("."); 61 | if (tArr.length < 2) { 62 | return ""; 63 | } 64 | return tArr.last.toLowerCase(); 65 | } 66 | 67 | static bool isImgOrGif(String path) { 68 | var t = getExt(path); 69 | return "gif" == t || imageFormats.contains(t); 70 | } 71 | 72 | static bool isGifOrVideo(String path) { 73 | var t = getExt(path); 74 | return "gif" == t || videoFormats.contains(t); 75 | } 76 | 77 | static bool isGif(String path) { 78 | return "gif" == getExt(path); 79 | } 80 | 81 | static bool isVideo(String path) { 82 | return videoFormats.contains(getExt(path)); 83 | } 84 | 85 | static bool isImg(String path) { 86 | return imageFormats.contains(getExt(path)); 87 | } 88 | 89 | static Future getFileCrc32(File file) async { 90 | var str = await file.readAsBytes(); 91 | return Crc32.calculate(str).toString(); 92 | } 93 | 94 | static Future revealInExplorer(Directory dir, 95 | [File? selectFile]) async { 96 | try { 97 | String? selectPath; 98 | if (selectFile != null && selectFile.path.contains(dir.path)) { 99 | selectPath = selectFile.path.replaceAll('/', '\\\\'); 100 | } 101 | await Process.start("explorer.exe", 102 | selectPath != null ? ["/select,", selectPath] : [dir.path], 103 | runInShell: true); 104 | } catch (err) { 105 | debugPrint("$err"); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### [English]|[中文](README_zh.md) 2 | 3 | 4 | ## **A UI for [roop](https://github.com/s0md3v/roop)** 5 | 6 | ### **Features:** 7 | 8 | #### 1. Support for images, gifs, videos 9 | 10 | #### 2. Allows swapping faces with specified faces 11 | 12 | ![](https://github.com/vectorobject/faceswap/blob/main/readme_assets/main2.png?raw=true) 13 | 14 | 15 | 16 | 17 | ### **First run:** 18 | 19 | 20 | 21 | 22 | #### 1. Make sure the [roop project](https://github.com/s0md3v/roop)(version: 1.3.2) can run successfully. 23 | 24 | 25 | 26 | #### 2. Create a new folder, e.g., `E:\ff` (this path will be used throughout the instructions). 27 | 28 | 29 | 30 | #### 3. Download the [faceswap release version](https://github.com/vectorobject/faceswap/releases) and extract it to the folder: `E:\ff\` 31 | 32 | ##### As follows: 33 | 34 | ``` 35 | ---E:\ff\ 36 | ------faceswap\ 37 | ---------faceswap.exe 38 | ------runServer.bat 39 | ------server.py 40 | ``` 41 | 42 | 43 | #### 4. Move the `server.py` to the root directory of the roop project 44 | 45 | 46 | 47 | #### 5. Modify the `runServer.bat` according to your environment 48 | 49 | 50 | 51 | ##### For example, if you are using minoconda installed at `G:\minoconda3\` , the content should be as follows: 52 | 53 | 54 | ```bat 55 | chcp 65001>nul 56 | call G:\miniconda3\Scripts\activate.bat G:\miniconda3 57 | call conda activate roop 58 | pushd D:\roop\roop 59 | python -u server.py %1 60 | ``` 61 | 62 | 63 | ##### You can use other methods as well as long as server.py runs correctly. 64 | 65 | 66 | 67 | #### 6. Run `E:\ff\fceswap\fceswap.exe` 68 | 69 | 70 | 71 | ##### If successful, a command prompt window will appear upon startup, as shown below: 72 | 73 | 74 | 75 | ![](https://github.com/vectorobject/faceswap/blob/main/readme_assets/runserver.png?raw=true) 76 | 77 | 78 | 79 | ##### If any other errors appear, check the configuration based on the provided instructions. 80 | 81 | 82 | 83 | 84 | 85 | 86 | ### **Usage:** 87 | 88 | ![](https://github.com/vectorobject/faceswap/blob/main/readme_assets/demo.gif?raw=true) 89 | 90 | 91 | 92 | #### 1. Place your preferred images in `E:\ff\images` 93 | 94 | 95 | #### 2. Double-click to select the source and target image (or GIF/video) 96 | 97 | 98 | #### 3. Click [Detect Faces] for each image and wait for the faces to be marked 99 | 100 | 101 | ##### For GIFs or videos, clicking [Detect Faces] will capture frames at the current time point and then mark the faces. This allows extracting faces from multiple time points. 102 | 103 | 104 | #### 4. Double-click the faces you want to swap;they will be added to the list on the right. 105 | 106 | 107 | #### 5. Drag and adjust the order of faces in the list. 108 | 109 | 110 | #### 6. Click [Generate] to create the face-swapped result. 111 | 112 | 113 | 114 | ### **Debuging Python scripts:** 115 | 116 | 117 | 118 | #### 1. Create a file: `E:\ff\server_port.txt`, and write a port number (53499) in the file. 119 | 120 | 121 | #### 2. Run `server.py` in debug mode using an IDE. 122 | 123 | 124 | #### 3. Run `E:\ff\fceswap\fceswap.exe` 125 | 126 | 127 | ### **Others** 128 | 129 | #### Flutter SDK Version:3.10.4 -------------------------------------------------------------------------------- /lib/window_frame.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:window_manager/window_manager.dart'; 6 | 7 | class WindowFrame extends StatefulWidget { 8 | const WindowFrame({ 9 | Key? key, 10 | required this.child, 11 | }) : super(key: key); 12 | 13 | /// The [child] contained by the VirtualWindowFrame. 14 | final Widget child; 15 | 16 | @override 17 | State createState() => _WindowFrameState(); 18 | } 19 | 20 | class _WindowFrameState extends State with WindowListener { 21 | bool _isFocused = true; 22 | bool _isMaximized = false; 23 | bool _isFullScreen = false; 24 | 25 | late ThemeData theme; 26 | 27 | @override 28 | void initState() { 29 | windowManager.addListener(this); 30 | super.initState(); 31 | } 32 | 33 | @override 34 | void dispose() { 35 | windowManager.removeListener(this); 36 | super.dispose(); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | theme = Theme.of(context); 42 | return Container( 43 | decoration: BoxDecoration( 44 | color: Colors.transparent, 45 | borderRadius: BorderRadius.circular( 46 | (_isMaximized || _isFullScreen) ? 0 : 10, 47 | ), 48 | border: Border.all(color: theme.primaryColor.withOpacity(0.3))), 49 | child: DragToResizeArea( 50 | resizeEdgeSize: 5, 51 | enableResizeEdges: (_isMaximized || _isFullScreen) ? [] : null, 52 | child: ClipRRect( 53 | borderRadius: BorderRadius.circular( 54 | (_isMaximized || _isFullScreen) ? 0 : 10, 55 | ), 56 | child: Column( 57 | children: [ 58 | SizedBox( 59 | height: kWindowCaptionHeight, 60 | child: WindowCaption( 61 | backgroundColor: theme.appBarTheme.backgroundColor, 62 | brightness: theme.brightness, 63 | title: Text( 64 | 'Faceswap', 65 | style: TextStyle( 66 | fontFamily: theme.textTheme.titleMedium?.fontFamily, 67 | color: theme.appBarTheme.foregroundColor, 68 | ), 69 | ), 70 | ), 71 | ), 72 | Expanded( 73 | child: widget.child, 74 | ), 75 | ], 76 | ), 77 | ), 78 | ), 79 | ); 80 | } 81 | 82 | @override 83 | void onWindowFocus() { 84 | setState(() { 85 | _isFocused = true; 86 | }); 87 | } 88 | 89 | @override 90 | void onWindowBlur() { 91 | setState(() { 92 | _isFocused = false; 93 | }); 94 | } 95 | 96 | @override 97 | void onWindowMaximize() { 98 | setState(() { 99 | _isMaximized = true; 100 | }); 101 | } 102 | 103 | @override 104 | void onWindowUnmaximize() { 105 | setState(() { 106 | _isMaximized = false; 107 | }); 108 | } 109 | 110 | @override 111 | void onWindowEnterFullScreen() { 112 | setState(() { 113 | _isFullScreen = true; 114 | }); 115 | } 116 | 117 | @override 118 | void onWindowLeaveFullScreen() { 119 | setState(() { 120 | _isFullScreen = false; 121 | }); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "faceswap" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "faceswap" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "faceswap.exe" "\0" 98 | VALUE "ProductName", "faceswap" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_zh_CN.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a zh_CN locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // Ignore issues from commonly used lints in this file. 7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new 8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering 9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases 10 | // ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes 11 | // ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes 12 | 13 | import 'package:intl/intl.dart'; 14 | import 'package:intl/message_lookup_by_library.dart'; 15 | 16 | final messages = new MessageLookup(); 17 | 18 | typedef String MessageIfAbsent(String messageStr, List args); 19 | 20 | class MessageLookup extends MessageLookupByLibrary { 21 | String get localeName => 'zh_CN'; 22 | 23 | static String m0(path) => "尝试从文件读取服务端口:${path}"; 24 | 25 | static String m1(port) => "使用从文件读取到的端口:${port}"; 26 | 27 | final messages = _notInlinedMessages(_notInlinedMessages); 28 | static Map _notInlinedMessages(_) => { 29 | "add_folder": MessageLookupByLibrary.simpleMessage("添加文件夹"), 30 | "audio": MessageLookupByLibrary.simpleMessage("音频"), 31 | "close": MessageLookupByLibrary.simpleMessage("关闭"), 32 | "connection_failed_and_restart": 33 | MessageLookupByLibrary.simpleMessage("连接失败,重启服务中"), 34 | "delete": MessageLookupByLibrary.simpleMessage("删除"), 35 | "detect_faces": MessageLookupByLibrary.simpleMessage("识别人脸"), 36 | "executing": MessageLookupByLibrary.simpleMessage("执行中"), 37 | "execution_providers": MessageLookupByLibrary.simpleMessage("执行提供程序:"), 38 | "execution_threads": MessageLookupByLibrary.simpleMessage("执行线程数:"), 39 | "face_enhance": MessageLookupByLibrary.simpleMessage("增强"), 40 | "file_filter_type_all": MessageLookupByLibrary.simpleMessage("全部"), 41 | "file_filter_type_gif_video": 42 | MessageLookupByLibrary.simpleMessage("GIF和视频"), 43 | "file_filter_type_img": MessageLookupByLibrary.simpleMessage("图片"), 44 | "generate": MessageLookupByLibrary.simpleMessage("生成"), 45 | "generating": MessageLookupByLibrary.simpleMessage("生成中"), 46 | "keep_fps": MessageLookupByLibrary.simpleMessage("保持FPS"), 47 | "keep_frames": MessageLookupByLibrary.simpleMessage("保留序列帧文件夹"), 48 | "log": MessageLookupByLibrary.simpleMessage("日志"), 49 | "min_similarity": MessageLookupByLibrary.simpleMessage("视频脸部最小相似度:"), 50 | "options": MessageLookupByLibrary.simpleMessage("选项"), 51 | "output_video_encoder": MessageLookupByLibrary.simpleMessage("视频编码:"), 52 | "output_video_quality": MessageLookupByLibrary.simpleMessage("视频编码质量:"), 53 | "parent_folder": MessageLookupByLibrary.simpleMessage("上层文件夹"), 54 | "provider_desc": 55 | MessageLookupByLibrary.simpleMessage("拖动标签调整顺序,越往左优先级越高。"), 56 | "refresh": MessageLookupByLibrary.simpleMessage("刷新"), 57 | "result": MessageLookupByLibrary.simpleMessage("结果"), 58 | "reveal_in_file_explorer": 59 | MessageLookupByLibrary.simpleMessage("在资源管理器中显示"), 60 | "set_as_default": MessageLookupByLibrary.simpleMessage("设为默认"), 61 | "settings": MessageLookupByLibrary.simpleMessage("设置"), 62 | "source": MessageLookupByLibrary.simpleMessage("源"), 63 | "target": MessageLookupByLibrary.simpleMessage("目标"), 64 | "temp_frame_format": MessageLookupByLibrary.simpleMessage("序列帧格式:"), 65 | "temp_frame_quality": MessageLookupByLibrary.simpleMessage("序列帧质量:"), 66 | "try_to_read_service_port_from_file": m0, 67 | "using_port_read_from_file": m1, 68 | "video": MessageLookupByLibrary.simpleMessage("视频") 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /lib/settings.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:faceswap/server.dart'; 3 | import 'package:faceswap/status_bar.dart'; 4 | 5 | import 'field.dart'; 6 | import 'global.dart'; 7 | 8 | class ProviderItem { 9 | bool selected; 10 | String name; 11 | ProviderItem(this.name, this.selected); 12 | toJsonObj() { 13 | return {"name": name, "selected": selected}; 14 | } 15 | } 16 | 17 | class ExecutionProvider extends Field> { 18 | List? choices; 19 | ExecutionProvider(String fieldName) : super(fieldName, []); 20 | Future init(Map? obj) async { 21 | List tempProviders = []; 22 | try { 23 | for (var item in obj?[fieldName]) { 24 | tempProviders.add(ProviderItem(item["name"], item["selected"])); 25 | } 26 | } catch (err) { 27 | print(err); 28 | } 29 | choices = await Server.getAvailableProviders(); 30 | if (tempProviders.isEmpty) { 31 | tempProviders = [ 32 | ProviderItem("CUDAExecutionProvider", true), 33 | ProviderItem("CPUExecutionProvider", true) 34 | ]; 35 | } 36 | value.clear(); 37 | for (var provider in tempProviders) { 38 | if (choicesContains(provider.name)) { 39 | value.add(provider); 40 | } 41 | } 42 | for (var provider in choices!) { 43 | if (!contains(provider)) { 44 | value.add(ProviderItem(provider, false)); 45 | } 46 | } 47 | notifyListeners(); 48 | } 49 | 50 | bool choicesContains(String providerName) { 51 | if (choices != null) { 52 | for (var item in choices!) { 53 | if (item == providerName) { 54 | return true; 55 | } 56 | } 57 | } 58 | return false; 59 | } 60 | 61 | bool contains(String providerName) { 62 | for (var item in value) { 63 | if (item.name == providerName) { 64 | return true; 65 | } 66 | } 67 | return false; 68 | } 69 | 70 | toJsonObj() { 71 | var arr = []; 72 | for (var item in value) { 73 | arr.add(item.toJsonObj()); 74 | } 75 | return arr; 76 | } 77 | 78 | List getSelectedNames() { 79 | List arr = []; 80 | for (var item in value) { 81 | if (item.selected) { 82 | arr.add(item.name); 83 | } 84 | } 85 | return arr; 86 | } 87 | } 88 | 89 | class Settings { 90 | static ExecutionProvider executionProvider = 91 | ExecutionProvider("execution-provider"); 92 | static RangedIntField executionThreads = 93 | RangedIntField("execution-threads", 1, min: 1, max: 50); 94 | static Field darkTheme = Field("dark-theme", true); 95 | static final List _all = [ 96 | executionProvider, 97 | executionThreads, 98 | darkTheme 99 | ]; 100 | 101 | static init() async { 102 | Map? obj; 103 | if (await Global.settingsFile.exists()) { 104 | var str = await Global.settingsFile.readAsString(); 105 | try { 106 | obj = jsonDecode(str); 107 | } catch (err) { 108 | StatusBar.appendOutput( 109 | "Read ${Global.settingsFile.path} err:$err", true); 110 | } 111 | } 112 | darkTheme.readFromObj(obj); 113 | var hasThreads = executionThreads.readFromObj(obj); 114 | executionProvider.init(obj).then((_) { 115 | if (!hasThreads) { 116 | if (executionProvider.value.first.name == "CUDAExecutionProvider") { 117 | executionThreads.value = 8; 118 | } 119 | } 120 | }); 121 | } 122 | 123 | static Future save() async { 124 | var obj = {}; 125 | for (var item in _all) { 126 | if (item is ExecutionProvider) { 127 | obj[item.fieldName] = item.toJsonObj(); 128 | } else { 129 | obj[item.fieldName] = item.value; 130 | } 131 | } 132 | await Global.settingsFile.writeAsString(jsonEncode(obj)); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # === Flutter Library === 14 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 15 | 16 | # Published to parent scope for install step. 17 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 18 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 19 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 20 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 21 | 22 | list(APPEND FLUTTER_LIBRARY_HEADERS 23 | "flutter_export.h" 24 | "flutter_windows.h" 25 | "flutter_messenger.h" 26 | "flutter_plugin_registrar.h" 27 | "flutter_texture_registrar.h" 28 | ) 29 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 30 | add_library(flutter INTERFACE) 31 | target_include_directories(flutter INTERFACE 32 | "${EPHEMERAL_DIR}" 33 | ) 34 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 35 | add_dependencies(flutter flutter_assemble) 36 | 37 | # === Wrapper === 38 | list(APPEND CPP_WRAPPER_SOURCES_CORE 39 | "core_implementations.cc" 40 | "standard_codec.cc" 41 | ) 42 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 43 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 44 | "plugin_registrar.cc" 45 | ) 46 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 47 | list(APPEND CPP_WRAPPER_SOURCES_APP 48 | "flutter_engine.cc" 49 | "flutter_view_controller.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 52 | 53 | # Wrapper sources needed for a plugin. 54 | add_library(flutter_wrapper_plugin STATIC 55 | ${CPP_WRAPPER_SOURCES_CORE} 56 | ${CPP_WRAPPER_SOURCES_PLUGIN} 57 | ) 58 | apply_standard_settings(flutter_wrapper_plugin) 59 | set_target_properties(flutter_wrapper_plugin PROPERTIES 60 | POSITION_INDEPENDENT_CODE ON) 61 | set_target_properties(flutter_wrapper_plugin PROPERTIES 62 | CXX_VISIBILITY_PRESET hidden) 63 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 64 | target_include_directories(flutter_wrapper_plugin PUBLIC 65 | "${WRAPPER_ROOT}/include" 66 | ) 67 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 68 | 69 | # Wrapper sources needed for the runner. 70 | add_library(flutter_wrapper_app STATIC 71 | ${CPP_WRAPPER_SOURCES_CORE} 72 | ${CPP_WRAPPER_SOURCES_APP} 73 | ) 74 | apply_standard_settings(flutter_wrapper_app) 75 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 76 | target_include_directories(flutter_wrapper_app PUBLIC 77 | "${WRAPPER_ROOT}/include" 78 | ) 79 | add_dependencies(flutter_wrapper_app flutter_assemble) 80 | 81 | # === Flutter tool backend === 82 | # _phony_ is a non-existent file to force this command to run every time, 83 | # since currently there's no way to get a full input/output list from the 84 | # flutter tool. 85 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 86 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 87 | add_custom_command( 88 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 89 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 90 | ${CPP_WRAPPER_SOURCES_APP} 91 | ${PHONY_OUTPUT} 92 | COMMAND ${CMAKE_COMMAND} -E env 93 | ${FLUTTER_TOOL_ENVIRONMENT} 94 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 95 | windows-x64 $ 96 | VERBATIM 97 | ) 98 | add_custom_target(flutter_assemble DEPENDS 99 | "${FLUTTER_LIBRARY}" 100 | ${FLUTTER_LIBRARY_HEADERS} 101 | ${CPP_WRAPPER_SOURCES_CORE} 102 | ${CPP_WRAPPER_SOURCES_PLUGIN} 103 | ${CPP_WRAPPER_SOURCES_APP} 104 | ) 105 | -------------------------------------------------------------------------------- /lib/data.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class SourceFaceLib { 7 | static ValueNotifier> faces = ValueNotifier([]); 8 | static bool has(RectData face) { 9 | if (faces.value.contains(face)) { 10 | return true; 11 | } 12 | for (var f in faces.value) { 13 | if (f.equals(face)) { 14 | return true; 15 | } 16 | } 17 | return false; 18 | } 19 | 20 | static void add(RectData face) { 21 | if (!has(face)) { 22 | faces.value.add(face); 23 | // ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member 24 | faces.notifyListeners(); 25 | } 26 | } 27 | } 28 | 29 | class SwapData { 30 | ValueNotifier> source = ValueNotifier([]); 31 | ValueNotifier> target = ValueNotifier([]); 32 | int idCount = 0; 33 | 34 | bool hasSource(RectData face) { 35 | if (source.value.contains(face)) { 36 | return true; 37 | } 38 | for (var f in source.value) { 39 | if (f.equals(face)) { 40 | return true; 41 | } 42 | } 43 | return false; 44 | } 45 | 46 | bool hasTarget(RectData face) { 47 | if (target.value.contains(face)) { 48 | return true; 49 | } 50 | for (var f in target.value) { 51 | if (f.equals(face)) { 52 | return true; 53 | } 54 | } 55 | return false; 56 | } 57 | 58 | void addSource(RectData face) { 59 | if (source.value.contains(face)) { 60 | face = face.copy(); 61 | } 62 | face.id = idCount++; 63 | source.value.add(face); 64 | source.notifyListeners(); 65 | } 66 | 67 | void addTarget(RectData face) { 68 | if (!hasTarget(face)) { 69 | face.id = idCount++; 70 | target.value.add(face); 71 | target.notifyListeners(); 72 | } 73 | } 74 | 75 | void clearTargets() { 76 | target.value.clear(); 77 | target.notifyListeners(); 78 | } 79 | } 80 | 81 | class TargetData { 82 | List> frames = []; 83 | 84 | int add(FrameData frame) { 85 | frames.add(ValueNotifier(frame)); 86 | return frames.length - 1; 87 | } 88 | 89 | int getFramePositionIndex(String framePosition) { 90 | for (var i = 0; i < frames.length; i++) { 91 | if (frames[i].value.framePosition == framePosition) { 92 | return i; 93 | } 94 | } 95 | return -1; 96 | } 97 | } 98 | 99 | class FrameData { 100 | File file; 101 | String? framePosition; 102 | File? frameFile; 103 | int? width; 104 | int? height; 105 | 106 | get fileForShow => frameFile ?? file; 107 | 108 | final List _rects = []; 109 | List get rects => _rects; 110 | 111 | FrameData(this.file); 112 | 113 | _addRect(RectData rect) { 114 | rect.parent = this; 115 | _rects.add(rect); 116 | } 117 | 118 | FrameData fromServerData(dynamic serverData) { 119 | if (serverData != null) { 120 | width = serverData["width"]; 121 | height = serverData["height"]; 122 | var faces = serverData["faces"]; 123 | _rects.clear(); 124 | for (var face in faces) { 125 | _addRect(RectData(Rect.fromLTRB(face[0], face[1], face[2], face[3]))); 126 | } 127 | } 128 | return this; 129 | } 130 | } 131 | 132 | class RectData { 133 | int? id; 134 | Rect rect; 135 | late FrameData parent; 136 | bool selected = false; 137 | bool enhance = false; 138 | 139 | RectData(this.rect); 140 | int get index => parent.rects.indexOf(this); 141 | 142 | bool equals(RectData t) { 143 | return parent.file.path == t.parent.file.path && 144 | index == t.index && 145 | parent.framePosition == t.parent.framePosition; 146 | } 147 | 148 | RectData copy() { 149 | var t = RectData(rect); 150 | t.parent = parent; 151 | t.selected = selected; 152 | t.enhance = enhance; 153 | return t; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/frame_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/gestures.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | import 'data.dart'; 7 | import 'file_preview.dart'; 8 | 9 | class FrameView extends StatefulWidget { 10 | final ValueNotifier frameData; 11 | final void Function(RectData face) onFaceDoubleTap; 12 | const FrameView( 13 | {super.key, required this.frameData, required this.onFaceDoubleTap}); 14 | 15 | @override 16 | State createState() => _FrameViewState(); 17 | } 18 | 19 | class _FrameViewState extends State { 20 | late ThemeData theme; 21 | @override 22 | Widget build(BuildContext context) { 23 | theme = Theme.of(context); 24 | return ValueListenableBuilder( 25 | valueListenable: widget.frameData, 26 | builder: (context, value, child) { 27 | return FilePreview( 28 | file: value?.fileForShow, 29 | imgBuilder: (imgFile) { 30 | if (value == null) { 31 | return const SizedBox(); 32 | } else { 33 | return LayoutBuilder( 34 | builder: (context, constraints) { 35 | List overlays = [ 36 | Image.file( 37 | imgFile, 38 | ) 39 | ]; 40 | if (value.width != null) { 41 | var scale = min(constraints.maxWidth / value.width!, 42 | constraints.maxHeight / value.height!); 43 | if (scale > 1) { 44 | scale = 1; 45 | } 46 | for (var t in value.rects) { 47 | overlays.add(Positioned( 48 | left: t.rect.left * scale, 49 | top: t.rect.top * scale, 50 | width: t.rect.width * scale, 51 | height: t.rect.height * scale, 52 | child: _buildFaceMarker(t), 53 | )); 54 | } 55 | } 56 | return Stack( 57 | children: overlays, 58 | ); 59 | }, 60 | ); 61 | } 62 | }, 63 | ); 64 | }); 65 | } 66 | 67 | Widget _buildFaceMarker(RectData t) { 68 | bool isSelected = t.selected; 69 | return Listener( 70 | onPointerDown: (event) { 71 | if (event.buttons == kPrimaryMouseButton) { 72 | if (!t.selected) { 73 | t.selected = true; 74 | setState(() {}); 75 | } 76 | } else if (event.buttons == kSecondaryMouseButton) { 77 | if (t.selected) { 78 | t.selected = false; 79 | setState(() {}); 80 | } 81 | } 82 | }, 83 | child: GestureDetector( 84 | onDoubleTap: () { 85 | widget.onFaceDoubleTap(t); 86 | }, 87 | child: Container( 88 | alignment: Alignment.topLeft, 89 | decoration: BoxDecoration( 90 | border: Border.all( 91 | width: isSelected ? 7 : 5, 92 | color: isSelected 93 | ? Colors.blue.withOpacity(0.8) 94 | : Colors.red.withOpacity(0.6), 95 | ), 96 | ), 97 | child: FittedBox( 98 | child: Container( 99 | alignment: Alignment.center, 100 | width: 30, 101 | height: 30, 102 | decoration: BoxDecoration( 103 | borderRadius: const BorderRadius.only( 104 | bottomRight: Radius.circular(10)), 105 | color: Colors.blue.withOpacity(0.5), 106 | ), 107 | child: Text( 108 | "${t.index + 1}", 109 | style: const TextStyle(fontSize: 13, color: Colors.white), 110 | )), 111 | ), 112 | ))); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(faceswap LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "faceswap") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(SET CMP0063 NEW) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | # Generated plugin build rules, which manage building the plugins and adding 56 | # them to the application. 57 | include(flutter/generated_plugins.cmake) 58 | 59 | 60 | # === Installation === 61 | # Support files are copied into place next to the executable, so that it can 62 | # run in place. This is done instead of making a separate bundle (as on Linux) 63 | # so that building and running from within Visual Studio will work. 64 | set(BUILD_BUNDLE_DIR "$") 65 | # Make the "install" step default, as it's required to run. 66 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 67 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 68 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 69 | endif() 70 | 71 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 72 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 73 | 74 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 75 | COMPONENT Runtime) 76 | 77 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 78 | COMPONENT Runtime) 79 | 80 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 81 | COMPONENT Runtime) 82 | 83 | if(PLUGIN_BUNDLED_LIBRARIES) 84 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 85 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 86 | COMPONENT Runtime) 87 | endif() 88 | 89 | # Fully re-copy the assets directory on each build to avoid having stale files 90 | # from a previous install. 91 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 92 | install(CODE " 93 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 94 | " COMPONENT Runtime) 95 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 96 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 97 | 98 | # Install the AOT library on non-Debug builds only. 99 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 100 | CONFIGURATIONS Profile;Release 101 | COMPONENT Runtime) 102 | -------------------------------------------------------------------------------- /lib/generated/intl/messages_en.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that provides messages for a en locale. All the 3 | // messages from the main program should be duplicated here with the same 4 | // function name. 5 | 6 | // Ignore issues from commonly used lints in this file. 7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new 8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering 9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases 10 | // ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes 11 | // ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes 12 | 13 | import 'package:intl/intl.dart'; 14 | import 'package:intl/message_lookup_by_library.dart'; 15 | 16 | final messages = new MessageLookup(); 17 | 18 | typedef String MessageIfAbsent(String messageStr, List args); 19 | 20 | class MessageLookup extends MessageLookupByLibrary { 21 | String get localeName => 'en'; 22 | 23 | static String m0(path) => "Try to read service port from file:${path}"; 24 | 25 | static String m1(port) => "Using port read from file:${port}"; 26 | 27 | final messages = _notInlinedMessages(_notInlinedMessages); 28 | static Map _notInlinedMessages(_) => { 29 | "add_folder": MessageLookupByLibrary.simpleMessage("Add Folder"), 30 | "audio": MessageLookupByLibrary.simpleMessage("Audio"), 31 | "close": MessageLookupByLibrary.simpleMessage("Close"), 32 | "connection_failed_and_restart": MessageLookupByLibrary.simpleMessage( 33 | "Connection failed, restarting server"), 34 | "delete": MessageLookupByLibrary.simpleMessage("Delete"), 35 | "detect_faces": MessageLookupByLibrary.simpleMessage("Detect Faces"), 36 | "executing": MessageLookupByLibrary.simpleMessage("Executing"), 37 | "execution_providers": 38 | MessageLookupByLibrary.simpleMessage("Execution Providers:"), 39 | "execution_threads": 40 | MessageLookupByLibrary.simpleMessage("Execution Threads:"), 41 | "face_enhance": MessageLookupByLibrary.simpleMessage("Enhance"), 42 | "file_filter_type_all": MessageLookupByLibrary.simpleMessage("All"), 43 | "file_filter_type_gif_video": 44 | MessageLookupByLibrary.simpleMessage("GIF and Video"), 45 | "file_filter_type_img": MessageLookupByLibrary.simpleMessage("Image"), 46 | "generate": MessageLookupByLibrary.simpleMessage("Generate"), 47 | "generating": MessageLookupByLibrary.simpleMessage("Generating"), 48 | "keep_fps": MessageLookupByLibrary.simpleMessage("Keep FPS"), 49 | "keep_frames": 50 | MessageLookupByLibrary.simpleMessage("Keep frames folder"), 51 | "log": MessageLookupByLibrary.simpleMessage("Log"), 52 | "min_similarity": MessageLookupByLibrary.simpleMessage( 53 | "Video facial minimum similarity:"), 54 | "options": MessageLookupByLibrary.simpleMessage("Options"), 55 | "output_video_encoder": 56 | MessageLookupByLibrary.simpleMessage("Video Encoder:"), 57 | "output_video_quality": 58 | MessageLookupByLibrary.simpleMessage("Video Quality:"), 59 | "parent_folder": MessageLookupByLibrary.simpleMessage("Parent Folder"), 60 | "provider_desc": MessageLookupByLibrary.simpleMessage( 61 | "Drag the labels to adjust the order, with higher priority as they move to the left."), 62 | "refresh": MessageLookupByLibrary.simpleMessage("Refresh"), 63 | "result": MessageLookupByLibrary.simpleMessage("Result"), 64 | "reveal_in_file_explorer": 65 | MessageLookupByLibrary.simpleMessage("Show in File Explorer"), 66 | "set_as_default": 67 | MessageLookupByLibrary.simpleMessage("Set as Default"), 68 | "settings": MessageLookupByLibrary.simpleMessage("Settings"), 69 | "source": MessageLookupByLibrary.simpleMessage("Source"), 70 | "target": MessageLookupByLibrary.simpleMessage("Target"), 71 | "temp_frame_format": 72 | MessageLookupByLibrary.simpleMessage("Frame Format:"), 73 | "temp_frame_quality": 74 | MessageLookupByLibrary.simpleMessage("Frame Quality:"), 75 | "try_to_read_service_port_from_file": m0, 76 | "using_port_read_from_file": m1, 77 | "video": MessageLookupByLibrary.simpleMessage("Video") 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: faceswap 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | # In Windows, build-name is used as the major, minor, and patch parts 19 | # of the product and file versions while build-number is used as the build suffix. 20 | version: 1.1.0+1 21 | 22 | environment: 23 | sdk: '>=2.18.4 <3.0.0' 24 | 25 | # Dependencies specify other packages that your package needs in order to work. 26 | # To automatically upgrade your package dependencies to the latest versions 27 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 28 | # dependencies can be manually updated by changing the version numbers below to 29 | # the latest version available on pub.dev. To see which dependencies have newer 30 | # versions available, run `flutter pub outdated`. 31 | dependencies: 32 | flutter: 33 | sdk: flutter 34 | 35 | # The following adds the Cupertino Icons font to your application. 36 | # Use with the CupertinoIcons class for iOS style icons. 37 | cupertino_icons: 1.0.2 38 | multi_split_view: 2.4.0 39 | crc32_checksum: 0.0.2 40 | file_selector: 0.9.3 41 | flutter_meedu_videoplayer: 4.2.10 42 | linked_scroll_controller: 0.2.0 43 | dropdown_button2: 2.3.7 44 | flex_color_scheme: 7.2.0 45 | window_manager: 0.3.5 46 | 47 | dev_dependencies: 48 | flutter_test: 49 | sdk: flutter 50 | 51 | # The "flutter_lints" package below contains a set of recommended lints to 52 | # encourage good coding practices. The lint set provided by the package is 53 | # activated in the `analysis_options.yaml` file located at the root of your 54 | # package. See that file for information about deactivating specific lint 55 | # rules and activating additional ones. 56 | flutter_lints: ^2.0.0 57 | 58 | flutter_localizations: 59 | sdk: flutter 60 | 61 | # For information on the generic Dart part of this file, see the 62 | # following page: https://dart.dev/tools/pub/pubspec 63 | # The following section is specific to Flutter packages. 64 | flutter: 65 | 66 | # The following line ensures that the Material Icons font is 67 | # included with your application, so that you can use the icons in 68 | # the material Icons class. 69 | uses-material-design: true 70 | # To add assets to your application, add an assets section, like this: 71 | # assets: 72 | # - images/a_dot_burr.jpeg 73 | # - images/a_dot_ham.jpeg 74 | # An image asset can refer to one or more resolution-specific "variants", see 75 | # https://flutter.dev/assets-and-images/#resolution-aware 76 | # For details regarding adding assets from package dependencies, see 77 | # https://flutter.dev/assets-and-images/#from-packages 78 | # To add custom fonts to your application, add a fonts section here, 79 | # in this "flutter" section. Each entry in this list should have a 80 | # "family" key with the font family name, and a "fonts" key with a 81 | # list giving the asset and other descriptors for the font. For 82 | # example: 83 | # fonts: 84 | # - family: Schyler 85 | # fonts: 86 | # - asset: fonts/Schyler-Regular.ttf 87 | # - asset: fonts/Schyler-Italic.ttf 88 | # style: italic 89 | # - family: Trajan Pro 90 | # fonts: 91 | # - asset: fonts/TrajanPro.ttf 92 | # - asset: fonts/TrajanPro_Bold.ttf 93 | # weight: 700 94 | # 95 | # For details regarding fonts from package dependencies, 96 | # see https://flutter.dev/custom-fonts/#from-packages 97 | flutter_intl: 98 | enabled: true 99 | -------------------------------------------------------------------------------- /lib/gif/gif_controller.dart: -------------------------------------------------------------------------------- 1 | ///Code modification from https://github.com/RafaelBarbosatec/gif_view 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | import 'git_frame.dart'; 8 | 9 | enum GifStatus { loading, playing, stoped, paused, reversing } 10 | 11 | class GifController extends ChangeNotifier { 12 | List _frames = []; 13 | int currentIndex = 0; 14 | GifStatus status = GifStatus.loading; 15 | 16 | final bool autoPlay; 17 | final VoidCallback? onFinish; 18 | final VoidCallback? onStart; 19 | final ValueChanged? onFrame; 20 | 21 | bool loop; 22 | bool _inverted; 23 | 24 | Timer? _curTimer; 25 | 26 | GifController({ 27 | this.autoPlay = true, 28 | this.loop = true, 29 | bool inverted = false, 30 | this.onStart, 31 | this.onFinish, 32 | this.onFrame, 33 | }) : _inverted = inverted; 34 | 35 | void _run() { 36 | switch (status) { 37 | case GifStatus.playing: 38 | case GifStatus.reversing: 39 | _runNextFrame(); 40 | break; 41 | 42 | case GifStatus.stoped: 43 | onFinish?.call(); 44 | currentIndex = 0; 45 | break; 46 | case GifStatus.loading: 47 | case GifStatus.paused: 48 | } 49 | } 50 | 51 | void _runNextFrame() async { 52 | _curTimer?.cancel(); 53 | _curTimer = Timer(_frames[currentIndex].duration, () { 54 | if (status == GifStatus.stoped) { 55 | onFinish?.call(); 56 | currentIndex = 0; 57 | return; 58 | } 59 | if (status == GifStatus.paused) { 60 | return; 61 | } 62 | if (status == GifStatus.reversing) { 63 | if (currentIndex > 0) { 64 | currentIndex--; 65 | } else if (loop) { 66 | currentIndex = _frames.length - 1; 67 | } else { 68 | status = GifStatus.stoped; 69 | } 70 | } else { 71 | if (currentIndex < _frames.length - 1) { 72 | currentIndex++; 73 | } else if (loop) { 74 | currentIndex = 0; 75 | } else { 76 | status = GifStatus.stoped; 77 | } 78 | } 79 | 80 | onFrame?.call(currentIndex); 81 | notifyListeners(); 82 | _run(); 83 | }); 84 | } 85 | 86 | GifFrame get currentFrame => _frames[currentIndex]; 87 | int get countFrames => _frames.length; 88 | 89 | void play({bool? inverted, int? initialFrame}) { 90 | if (status == GifStatus.loading) return; 91 | _inverted = inverted ?? _inverted; 92 | 93 | if (status == GifStatus.stoped || status == GifStatus.paused) { 94 | status = _inverted ? GifStatus.reversing : GifStatus.playing; 95 | 96 | bool isValidInitialFrame = initialFrame != null && 97 | initialFrame > 0 && 98 | initialFrame < _frames.length - 1; 99 | 100 | if (isValidInitialFrame) { 101 | currentIndex = initialFrame; 102 | } else { 103 | currentIndex = status == GifStatus.reversing ? _frames.length - 1 : 0; 104 | } 105 | onStart?.call(); 106 | _run(); 107 | } else { 108 | status = _inverted ? GifStatus.reversing : GifStatus.playing; 109 | } 110 | } 111 | 112 | void stop() { 113 | status = GifStatus.stoped; 114 | notifyListeners(); 115 | } 116 | 117 | void pause() { 118 | status = GifStatus.paused; 119 | notifyListeners(); 120 | } 121 | 122 | void seek(int index) { 123 | currentIndex = index; 124 | notifyListeners(); 125 | } 126 | 127 | void configure(List frames, {bool updateFrames = false}) { 128 | _curTimer?.cancel(); 129 | _curTimer = null; 130 | _frames = frames; 131 | switch (status) { 132 | case GifStatus.loading: 133 | status = GifStatus.stoped; 134 | if (autoPlay) { 135 | play(); 136 | } 137 | notifyListeners(); 138 | break; 139 | case GifStatus.playing: 140 | status = GifStatus.stoped; 141 | play(); 142 | notifyListeners(); 143 | break; 144 | case GifStatus.stoped: 145 | notifyListeners(); 146 | break; 147 | case GifStatus.paused: 148 | notifyListeners(); 149 | break; 150 | case GifStatus.reversing: 151 | notifyListeners(); 152 | break; 153 | } 154 | } 155 | 156 | void clear() { 157 | status = GifStatus.loading; 158 | currentIndex = 0; 159 | _frames = []; 160 | _curTimer?.cancel(); 161 | _curTimer = null; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:faceswap/settings.dart'; 2 | import 'package:flex_color_scheme/flex_color_scheme.dart'; 3 | import 'package:flutter_localizations/flutter_localizations.dart'; 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:window_manager/window_manager.dart'; 7 | 8 | import 'global.dart'; 9 | import 'home_view.dart'; 10 | import 'generated/l10n.dart'; 11 | import 'window_frame.dart'; 12 | 13 | void main() async { 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | 16 | await Global.init(); 17 | 18 | await windowManager.ensureInitialized(); 19 | 20 | WindowOptions windowOptions = const WindowOptions( 21 | size: Size(900, 600), 22 | center: true, 23 | backgroundColor: Colors.transparent, 24 | skipTaskbar: false, 25 | titleBarStyle: TitleBarStyle.hidden, 26 | windowButtonVisibility: false, 27 | ); 28 | windowManager.waitUntilReadyToShow(windowOptions, () async { 29 | await windowManager.setAsFrameless(); 30 | await windowManager.show(); 31 | await windowManager.focus(); 32 | }); 33 | 34 | runApp(const MyApp()); 35 | } 36 | 37 | class MyApp extends StatelessWidget { 38 | const MyApp({super.key}); 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return ValueListenableBuilder( 43 | valueListenable: Settings.darkTheme, 44 | builder: (context, value, child) => MaterialApp( 45 | builder: (context, child) { 46 | return WindowFrame(child: child!); 47 | }, 48 | localizationsDelegates: const >[ 49 | GlobalWidgetsLocalizations.delegate, 50 | GlobalMaterialLocalizations.delegate, 51 | GlobalCupertinoLocalizations.delegate, 52 | S.delegate, 53 | ], 54 | localeResolutionCallback: (locale, supportedLocales) { 55 | if (locale != null) { 56 | for (var item in supportedLocales) { 57 | if (item.toLanguageTag() == locale.toLanguageTag()) { 58 | return item; 59 | } 60 | } 61 | for (var item in supportedLocales) { 62 | if (item.languageCode == locale.languageCode && 63 | item.countryCode == locale.countryCode) { 64 | return item; 65 | } 66 | } 67 | for (var item in supportedLocales) { 68 | if (item.languageCode == locale.languageCode) { 69 | return item; 70 | } 71 | } 72 | } 73 | return const Locale('en', ''); 74 | }, 75 | supportedLocales: S.delegate.supportedLocales, 76 | debugShowCheckedModeBanner: false, 77 | title: 'FaceSwap', 78 | themeMode: Settings.darkTheme.value ? ThemeMode.dark : ThemeMode.light, 79 | theme: FlexThemeData.light( 80 | colors: const FlexSchemeColor( 81 | primary: Color(0xff00296b), 82 | primaryContainer: Color(0xffa0c2ed), 83 | secondary: Color(0xffd26900), 84 | secondaryContainer: Color(0xffffd270), 85 | tertiary: Color(0xff5c5c95), 86 | tertiaryContainer: Color(0xffc8dbf8), 87 | appBarColor: Color(0xffc8dcf8), 88 | error: null, 89 | ), 90 | surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold, 91 | blendLevel: 7, 92 | subThemesData: const FlexSubThemesData( 93 | blendOnLevel: 10, 94 | blendOnColors: false, 95 | useTextTheme: true, 96 | drawerBackgroundSchemeColor: SchemeColor.onPrimary, 97 | menuSchemeColor: SchemeColor.primary, 98 | ), 99 | keyColors: const FlexKeyColors(), 100 | visualDensity: FlexColorScheme.comfortablePlatformDensity, 101 | useMaterial3: true, 102 | ), 103 | darkTheme: FlexThemeData.dark( 104 | colors: const FlexSchemeColor( 105 | primary: Color(0xffb1cff5), 106 | primaryContainer: Color(0xff3873ba), 107 | secondary: Color(0xffffd270), 108 | secondaryContainer: Color(0xffd26900), 109 | tertiary: Color(0xffc9cbfc), 110 | tertiaryContainer: Color(0xff535393), 111 | appBarColor: Color(0xff00102b), 112 | error: null, 113 | ), 114 | surfaceMode: FlexSurfaceMode.levelSurfacesLowScaffold, 115 | blendLevel: 13, 116 | subThemesData: const FlexSubThemesData( 117 | blendOnLevel: 20, 118 | useTextTheme: true, 119 | drawerBackgroundSchemeColor: SchemeColor.onPrimary, 120 | menuSchemeColor: SchemeColor.primary, 121 | ), 122 | keyColors: const FlexKeyColors(), 123 | visualDensity: FlexColorScheme.comfortablePlatformDensity, 124 | useMaterial3: true, 125 | ), 126 | home: const HomeView(), 127 | ), 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/file_preview.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:faceswap/measure_size.dart'; 4 | import 'package:faceswap/util.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_meedu_videoplayer/meedu_player.dart'; 7 | 8 | import 'gif/gif_controller.dart'; 9 | import 'gif/gif_view.dart'; 10 | import 'video_view.dart'; 11 | 12 | class FilePreview extends StatefulWidget { 13 | final ValueNotifier? fileNotifier; 14 | final File? file; 15 | final Widget Function(File imgFile)? imgBuilder; 16 | final MeeduPlayerController? videoController; 17 | final GifController? gifController; 18 | const FilePreview({ 19 | super.key, 20 | this.file, 21 | this.fileNotifier, 22 | this.imgBuilder, 23 | this.videoController, 24 | this.gifController, 25 | }) : assert(file != null && fileNotifier == null || file == null); 26 | 27 | @override 28 | State createState() => _FilePreviewState(); 29 | } 30 | 31 | class _FilePreviewState extends State { 32 | @override 33 | Widget build(BuildContext context) { 34 | return GestureDetector( 35 | onTap: () { 36 | if (widget.gifController != null) { 37 | var file = widget.file; 38 | file ??= widget.fileNotifier?.value; 39 | if (file == null) { 40 | return; 41 | } 42 | if (file.isGif) { 43 | if (widget.gifController!.status == GifStatus.loading) { 44 | return; 45 | } 46 | if (widget.gifController!.status == GifStatus.playing) { 47 | widget.gifController!.pause(); 48 | } else { 49 | widget.gifController! 50 | .play(initialFrame: widget.gifController!.currentIndex); 51 | } 52 | } 53 | } 54 | }, 55 | child: Stack( 56 | children: [ 57 | SizedBox.expand( 58 | child: CustomPaint( 59 | painter: BackgroundPainter(), 60 | )), 61 | if (!(widget.file == null && widget.fileNotifier == null)) 62 | Center( 63 | child: widget.file != null 64 | ? _buildContent(widget.file) 65 | : ValueListenableBuilder( 66 | valueListenable: widget.fileNotifier!, 67 | builder: (context, value, child) { 68 | return _buildContent(value); 69 | }, 70 | ), 71 | ), 72 | ], 73 | ), 74 | ); 75 | } 76 | 77 | Widget _buildContent(File? value) { 78 | if (value == null) { 79 | return const SizedBox(); 80 | } 81 | 82 | Widget child; 83 | if (value.isImgOrGif) { 84 | if (widget.gifController != null && value.isGif) { 85 | child = Stack( 86 | alignment: Alignment.center, 87 | children: [ 88 | GifView( 89 | image: FileImage(value), 90 | controller: widget.gifController!, 91 | ), 92 | AnimatedBuilder( 93 | animation: widget.gifController!, 94 | builder: (context, child) { 95 | if (widget.gifController?.status == GifStatus.paused) { 96 | return const Icon( 97 | Icons.play_circle_outline, 98 | size: 50, 99 | color: Colors.white54, 100 | ); 101 | } 102 | return const SizedBox(); 103 | }, 104 | ) 105 | ], 106 | ); 107 | } else { 108 | if (widget.imgBuilder != null) { 109 | child = widget.imgBuilder!(value); 110 | } else { 111 | child = Image.file(value); 112 | } 113 | } 114 | } else { 115 | child = VideoView( 116 | controller: widget.videoController, 117 | file: value, 118 | ); 119 | } 120 | return child; 121 | } 122 | } 123 | 124 | class BackgroundPainter extends CustomPainter { 125 | @override 126 | void paint(Canvas canvas, Size size) { 127 | double eWidth = 20; 128 | double eHeight = 20; 129 | int xCount = (size.width / eWidth).ceil(); 130 | int yCount = (size.height / eHeight).ceil(); 131 | var paint = Paint() 132 | ..isAntiAlias = true 133 | ..style = PaintingStyle.fill 134 | ..color = Colors.white; 135 | canvas.drawRect(Offset.zero & size, paint); 136 | 137 | paint.color = const Color(0xffcccccc); 138 | 139 | for (int i = 0; i < xCount; ++i) { 140 | for (int j = 0; j < yCount; ++j) { 141 | if (i % 2 == 0 && j % 2 == 0 || i % 2 != 0 && j % 2 != 0) { 142 | double dx = eWidth * i; 143 | double dy = eHeight * j; 144 | double dx2 = dx + eWidth; 145 | double dy2 = dy + eHeight; 146 | if (dx2 > size.width) { 147 | dx2 = size.width; 148 | } 149 | if (dy2 > size.height) { 150 | dy2 = size.height; 151 | } 152 | canvas.drawRect(Rect.fromLTRB(dx, dy, dx2, dy2), paint); 153 | } 154 | } 155 | } 156 | } 157 | 158 | @override 159 | bool shouldRepaint(CustomPainter oldDelegate) => true; 160 | } 161 | -------------------------------------------------------------------------------- /lib/status_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:faceswap/global.dart'; 2 | import 'package:faceswap/server.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'generated/l10n.dart'; 6 | import 'settings.dart'; 7 | import 'settings_view.dart'; 8 | 9 | class OutputMsg { 10 | bool isErr; 11 | String msg; 12 | OutputMsg(this.msg, this.isErr); 13 | } 14 | 15 | class StatusBar extends StatefulWidget { 16 | static final ValueNotifier> output = ValueNotifier([]); 17 | 18 | static appendOutput(String msg, [bool isErr = false]) { 19 | output.value.add(OutputMsg(msg, isErr)); 20 | output.notifyListeners(); 21 | } 22 | 23 | const StatusBar({super.key}); 24 | 25 | @override 26 | State createState() => _StatusBarState(); 27 | } 28 | 29 | class _StatusBarState extends State { 30 | bool isLogViewShow = false; 31 | late ThemeData theme; 32 | 33 | ScrollController logViewScrollController = ScrollController(); 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | theme = Theme.of(context); 38 | Widget child = Padding( 39 | padding: const EdgeInsets.only(bottom: 2), 40 | child: SizedBox( 41 | height: 30, 42 | child: Row( 43 | children: [ 44 | TextButton.icon( 45 | onPressed: () { 46 | setState(() { 47 | isLogViewShow = !isLogViewShow; 48 | }); 49 | }, 50 | icon: const Icon( 51 | Icons.output, 52 | ), 53 | label: Text( 54 | S.of(context).log, 55 | ), 56 | ), 57 | TextButton.icon( 58 | onPressed: () { 59 | SettingsView.show(context); 60 | }, 61 | icon: const Icon( 62 | Icons.settings, 63 | ), 64 | label: Text( 65 | S.of(context).settings, 66 | ), 67 | ), 68 | ValueListenableBuilder( 69 | valueListenable: Server.waitingCount, 70 | builder: (context, value, child) { 71 | if (value == 0) { 72 | return const SizedBox(); 73 | } 74 | return Padding( 75 | padding: const EdgeInsets.symmetric(horizontal: 10), 76 | child: Row( 77 | children: [ 78 | SizedBox( 79 | width: 15, 80 | height: 15, 81 | child: CircularProgressIndicator( 82 | color: theme.colorScheme.onSurfaceVariant, 83 | strokeWidth: 2, 84 | )), 85 | const SizedBox( 86 | width: 5, 87 | ), 88 | Text( 89 | S.of(context).executing, 90 | style: TextStyle( 91 | color: theme.colorScheme.onSurfaceVariant), 92 | ), 93 | ], 94 | ), 95 | ); 96 | }, 97 | ), 98 | const Spacer(), 99 | ListenableBuilder( 100 | listenable: Settings.darkTheme, 101 | builder: (context, child) { 102 | return IconButton( 103 | padding: EdgeInsets.zero, 104 | onPressed: () { 105 | Settings.darkTheme.value = !Settings.darkTheme.value; 106 | Settings.save(); 107 | }, 108 | icon: Icon( 109 | Settings.darkTheme.value 110 | ? Icons.nightlight_rounded 111 | : Icons.wb_sunny_rounded, 112 | size: 23, 113 | )); 114 | }, 115 | ) 116 | ], 117 | ), 118 | )); 119 | if (isLogViewShow) { 120 | //log view 121 | child = Column( 122 | children: [ 123 | Container( 124 | padding: const EdgeInsets.all(3), 125 | height: 100, 126 | color: Colors.black, 127 | child: ValueListenableBuilder( 128 | valueListenable: StatusBar.output, 129 | builder: (context, value, child) { 130 | Future.delayed(const Duration(milliseconds: 10)).then((_) { 131 | logViewScrollController 132 | .jumpTo(logViewScrollController.position.maxScrollExtent); 133 | }); 134 | return ScrollbarTheme( 135 | data: ScrollbarThemeData( 136 | thumbVisibility: const MaterialStatePropertyAll(true), 137 | thumbColor: MaterialStatePropertyAll( 138 | Colors.white.withOpacity(0.5))), 139 | child: ListView.builder( 140 | itemCount: value.length, 141 | controller: logViewScrollController, 142 | itemBuilder: (context, index) { 143 | return SelectableText( 144 | value[index].msg, 145 | style: TextStyle( 146 | fontSize: 13, 147 | color: value[index].isErr 148 | ? Colors.red 149 | : Colors.white), 150 | ); 151 | }, 152 | )); 153 | }, 154 | ), 155 | ), 156 | child 157 | ], 158 | ); 159 | } 160 | child = TextButtonTheme( 161 | data: TextButtonThemeData( 162 | style: theme.textButtonTheme.style?.copyWith( 163 | foregroundColor: MaterialStatePropertyAll( 164 | theme.colorScheme.onSurfaceVariant))), 165 | child: child, 166 | ); 167 | child = IconButtonTheme( 168 | data: IconButtonThemeData( 169 | style: theme.iconButtonTheme.style?.copyWith( 170 | foregroundColor: MaterialStatePropertyAll( 171 | theme.colorScheme.onSurfaceVariant))), 172 | child: child); 173 | return child; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | 5 | #include "resource.h" 6 | 7 | namespace { 8 | 9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 10 | 11 | // The number of Win32Window objects that currently exist. 12 | static int g_active_window_count = 0; 13 | 14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 15 | 16 | // Scale helper to convert logical scaler values to physical using passed in 17 | // scale factor 18 | int Scale(int source, double scale_factor) { 19 | return static_cast(source * scale_factor); 20 | } 21 | 22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 23 | // This API is only needed for PerMonitor V1 awareness mode. 24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 25 | HMODULE user32_module = LoadLibraryA("User32.dll"); 26 | if (!user32_module) { 27 | return; 28 | } 29 | auto enable_non_client_dpi_scaling = 30 | reinterpret_cast( 31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 32 | if (enable_non_client_dpi_scaling != nullptr) { 33 | enable_non_client_dpi_scaling(hwnd); 34 | FreeLibrary(user32_module); 35 | } 36 | } 37 | 38 | } // namespace 39 | 40 | // Manages the Win32Window's window class registration. 41 | class WindowClassRegistrar { 42 | public: 43 | ~WindowClassRegistrar() = default; 44 | 45 | // Returns the singleton registar instance. 46 | static WindowClassRegistrar* GetInstance() { 47 | if (!instance_) { 48 | instance_ = new WindowClassRegistrar(); 49 | } 50 | return instance_; 51 | } 52 | 53 | // Returns the name of the window class, registering the class if it hasn't 54 | // previously been registered. 55 | const wchar_t* GetWindowClass(); 56 | 57 | // Unregisters the window class. Should only be called if there are no 58 | // instances of the window. 59 | void UnregisterWindowClass(); 60 | 61 | private: 62 | WindowClassRegistrar() = default; 63 | 64 | static WindowClassRegistrar* instance_; 65 | 66 | bool class_registered_ = false; 67 | }; 68 | 69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 70 | 71 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 72 | if (!class_registered_) { 73 | WNDCLASS window_class{}; 74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 75 | window_class.lpszClassName = kWindowClassName; 76 | window_class.style = CS_HREDRAW | CS_VREDRAW; 77 | window_class.cbClsExtra = 0; 78 | window_class.cbWndExtra = 0; 79 | window_class.hInstance = GetModuleHandle(nullptr); 80 | window_class.hIcon = 81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 82 | window_class.hbrBackground = 0; 83 | window_class.lpszMenuName = nullptr; 84 | window_class.lpfnWndProc = Win32Window::WndProc; 85 | RegisterClass(&window_class); 86 | class_registered_ = true; 87 | } 88 | return kWindowClassName; 89 | } 90 | 91 | void WindowClassRegistrar::UnregisterWindowClass() { 92 | UnregisterClass(kWindowClassName, nullptr); 93 | class_registered_ = false; 94 | } 95 | 96 | Win32Window::Win32Window() { 97 | ++g_active_window_count; 98 | } 99 | 100 | Win32Window::~Win32Window() { 101 | --g_active_window_count; 102 | Destroy(); 103 | } 104 | 105 | bool Win32Window::CreateAndShow(const std::wstring& title, 106 | const Point& origin, 107 | const Size& size) { 108 | Destroy(); 109 | 110 | const wchar_t* window_class = 111 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 112 | 113 | const POINT target_point = {static_cast(origin.x), 114 | static_cast(origin.y)}; 115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 117 | double scale_factor = dpi / 96.0; 118 | 119 | HWND window = CreateWindow( 120 | //window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 121 | window_class, title.c_str(), 122 | WS_OVERLAPPEDWINDOW, // do not add WS_VISIBLE since the window will be shown later 123 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 124 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 125 | nullptr, nullptr, GetModuleHandle(nullptr), this); 126 | 127 | if (!window) { 128 | return false; 129 | } 130 | 131 | return OnCreate(); 132 | } 133 | 134 | // static 135 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 136 | UINT const message, 137 | WPARAM const wparam, 138 | LPARAM const lparam) noexcept { 139 | if (message == WM_NCCREATE) { 140 | auto window_struct = reinterpret_cast(lparam); 141 | SetWindowLongPtr(window, GWLP_USERDATA, 142 | reinterpret_cast(window_struct->lpCreateParams)); 143 | 144 | auto that = static_cast(window_struct->lpCreateParams); 145 | EnableFullDpiSupportIfAvailable(window); 146 | that->window_handle_ = window; 147 | } else if (Win32Window* that = GetThisFromHandle(window)) { 148 | return that->MessageHandler(window, message, wparam, lparam); 149 | } 150 | 151 | return DefWindowProc(window, message, wparam, lparam); 152 | } 153 | 154 | LRESULT 155 | Win32Window::MessageHandler(HWND hwnd, 156 | UINT const message, 157 | WPARAM const wparam, 158 | LPARAM const lparam) noexcept { 159 | switch (message) { 160 | case WM_DESTROY: 161 | window_handle_ = nullptr; 162 | Destroy(); 163 | if (quit_on_close_) { 164 | PostQuitMessage(0); 165 | } 166 | return 0; 167 | 168 | case WM_DPICHANGED: { 169 | auto newRectSize = reinterpret_cast(lparam); 170 | LONG newWidth = newRectSize->right - newRectSize->left; 171 | LONG newHeight = newRectSize->bottom - newRectSize->top; 172 | 173 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 174 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 175 | 176 | return 0; 177 | } 178 | case WM_SIZE: { 179 | RECT rect = GetClientArea(); 180 | if (child_content_ != nullptr) { 181 | // Size and position the child window. 182 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 183 | rect.bottom - rect.top, TRUE); 184 | } 185 | return 0; 186 | } 187 | 188 | case WM_ACTIVATE: 189 | if (child_content_ != nullptr) { 190 | SetFocus(child_content_); 191 | } 192 | return 0; 193 | } 194 | 195 | return DefWindowProc(window_handle_, message, wparam, lparam); 196 | } 197 | 198 | void Win32Window::Destroy() { 199 | OnDestroy(); 200 | 201 | if (window_handle_) { 202 | DestroyWindow(window_handle_); 203 | window_handle_ = nullptr; 204 | } 205 | if (g_active_window_count == 0) { 206 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 207 | } 208 | } 209 | 210 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 211 | return reinterpret_cast( 212 | GetWindowLongPtr(window, GWLP_USERDATA)); 213 | } 214 | 215 | void Win32Window::SetChildContent(HWND content) { 216 | child_content_ = content; 217 | SetParent(content, window_handle_); 218 | RECT frame = GetClientArea(); 219 | 220 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 221 | frame.bottom - frame.top, true); 222 | 223 | SetFocus(child_content_); 224 | } 225 | 226 | RECT Win32Window::GetClientArea() { 227 | RECT frame; 228 | GetClientRect(window_handle_, &frame); 229 | return frame; 230 | } 231 | 232 | HWND Win32Window::GetHandle() { 233 | return window_handle_; 234 | } 235 | 236 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 237 | quit_on_close_ = quit_on_close; 238 | } 239 | 240 | bool Win32Window::OnCreate() { 241 | // No-op; provided for subclasses. 242 | return true; 243 | } 244 | 245 | void Win32Window::OnDestroy() { 246 | // No-op; provided for subclasses. 247 | } 248 | -------------------------------------------------------------------------------- /lib/server.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | import 'dart:math'; 5 | 6 | import 'package:faceswap/settings.dart'; 7 | import 'package:faceswap/status_bar.dart'; 8 | import 'package:flutter/widgets.dart'; 9 | import 'package:path/path.dart' as path; 10 | 11 | import 'generated/l10n.dart'; 12 | import 'options.dart'; 13 | import 'options_menu_button.dart'; 14 | 15 | class Server { 16 | static final ValueNotifier _baseUrl = ValueNotifier(null); 17 | static bool _isIniting = false; 18 | static bool _isFirstInited = false; 19 | 20 | static final ValueNotifier waitingCount = ValueNotifier(0); 21 | 22 | static Process? curProcess; 23 | 24 | static init([bool isFirst = true]) async { 25 | if (isFirst) { 26 | if (_isFirstInited) { 27 | return; 28 | } 29 | _isFirstInited = true; 30 | } 31 | if (_isIniting) { 32 | return; 33 | } 34 | _isIniting = true; 35 | var file = 36 | File(path.join(Directory.current.parent.path, "server_port.txt")); 37 | StatusBar.appendOutput( 38 | S.current.try_to_read_service_port_from_file(file.path)); 39 | if (await file.exists()) { 40 | int? port = int.tryParse(await file.readAsString()); 41 | if (port != null) { 42 | StatusBar.appendOutput(S.current.using_port_read_from_file(port)); 43 | _baseUrl.value = Uri.parse("http://127.0.0.1:$port/"); 44 | } 45 | } 46 | if (_baseUrl.value == null) { 47 | var port = await _getPort(); 48 | _runServer(port); 49 | var url = Uri.parse("http://127.0.0.1:$port/"); 50 | var maxCount = 10; 51 | while (true) { 52 | HttpClient client = HttpClient(); 53 | try { 54 | await client.getUrl(url); 55 | break; 56 | } catch (err) { 57 | debugPrint("$err"); 58 | maxCount--; 59 | if (maxCount <= 0) { 60 | break; 61 | } 62 | } 63 | await Future.delayed(const Duration(milliseconds: 500)); 64 | } 65 | _baseUrl.value = url; 66 | } 67 | _isIniting = false; 68 | } 69 | 70 | static Future _getPort() async { 71 | var process = 72 | await Process.start("chcp 65001>nul&netstat -an", [], runInShell: true); 73 | List ports = []; 74 | process.stdout.listen((binaryData) { 75 | String? decodedString; 76 | try { 77 | decodedString = utf8.decode(binaryData); 78 | } catch (e) { 79 | debugPrint('Not UTF-8 encoding:$binaryData'); 80 | return; 81 | } 82 | var lines = decodedString.split("\n"); 83 | final re = RegExp(r'\t\s'); 84 | for (var line in lines) { 85 | var items = line.split(re); 86 | try { 87 | var port = int.parse(items[1].split(":").last); 88 | ports.add(port); 89 | } on RangeError { 90 | } catch (err) { 91 | debugPrint("$err"); 92 | } 93 | } 94 | }); 95 | await process.exitCode; 96 | var r = Random(); 97 | while (true) { 98 | var t = 49152 + r.nextInt(16383); 99 | if (!ports.contains(t)) { 100 | return t; 101 | } 102 | } 103 | } 104 | 105 | static _runServer([int port = 0]) async { 106 | StatusBar.appendOutput("start server on $port"); 107 | Process process = await Process.start( 108 | "start", 109 | [ 110 | path.join(Directory.current.parent.path, "runServer.bat"), 111 | port.toString(), 112 | ], 113 | workingDirectory: Directory.current.parent.path, 114 | runInShell: true, 115 | ); 116 | curProcess = process; 117 | int exitCode = await process.exitCode; 118 | debugPrint('Exit Code: $exitCode'); 119 | } 120 | 121 | static Future _waitBaseUrl() async { 122 | if (_baseUrl.value != null) { 123 | return; 124 | } 125 | var com = Completer(); 126 | late Function() onChange; 127 | onChange = () { 128 | if (_baseUrl.value != null) { 129 | _baseUrl.removeListener(onChange); 130 | if (!com.isCompleted) { 131 | com.complete(); 132 | } 133 | } 134 | }; 135 | _baseUrl.addListener(onChange); 136 | return com.future; 137 | } 138 | 139 | static String _py(dynamic obj) { 140 | if (obj is bool) { 141 | return obj ? "True" : "False"; 142 | } 143 | if (obj is int) { 144 | return "$obj"; 145 | } 146 | if (obj is double) { 147 | return "$obj"; 148 | } 149 | if (obj is String) { 150 | return "r'$obj'"; 151 | } 152 | if (obj is List) { 153 | List strArr = []; 154 | for (var i in obj) { 155 | strArr.add(_py(i)); 156 | } 157 | return '[${strArr.join(",")}]'; 158 | } 159 | if (obj is Map) { 160 | List strArr = []; 161 | for (var k in obj.keys) { 162 | strArr.add("'$k':${_py(obj[k])}"); 163 | } 164 | return '{${strArr.join(",")}}'; 165 | } 166 | throw "unknow type $obj"; 167 | } 168 | 169 | static Future _callFunc( 170 | String func, 171 | ) async { 172 | waitingCount.value++; 173 | StatusBar.appendOutput("call $func"); 174 | await _waitBaseUrl(); 175 | HttpClient client = HttpClient(); 176 | HttpClientRequest req; 177 | try { 178 | req = await client.postUrl(_baseUrl.value!); 179 | } on SocketException { 180 | _baseUrl.value = null; 181 | StatusBar.appendOutput(S.current.connection_failed_and_restart); 182 | waitingCount.value--; 183 | init(false); 184 | return _callFunc(func); 185 | } catch (err) { 186 | StatusBar.appendOutput("$err"); 187 | waitingCount.value--; 188 | return null; 189 | } 190 | var body = utf8.encode(func); 191 | req.headers.set(HttpHeaders.contentLengthHeader, body.length.toString()); 192 | req.add(body); 193 | var rsp = await req.close(); 194 | if (rsp.statusCode != HttpStatus.ok) { 195 | waitingCount.value--; 196 | return null; 197 | } 198 | String rspBody = await rsp.transform(utf8.decoder).join(); 199 | try { 200 | var obj = json.decode(rspBody); 201 | if (obj["code"] == 0) { 202 | waitingCount.value--; 203 | return obj["result"]; 204 | } 205 | } catch (err) {} 206 | waitingCount.value--; 207 | return null; 208 | } 209 | 210 | static Future swapVideo( 211 | List sourceFaceInfos, 212 | List targetFaceInfos, 213 | String targetPath, 214 | String outputPath, 215 | double minSimilarity) async { 216 | await _callFunc( 217 | 'swap_video(${_py(sourceFaceInfos)},${_py(targetFaceInfos)},${_py(targetPath)},${_py(outputPath)},${_py(minSimilarity)})'); 218 | } 219 | 220 | static Future swapImage( 221 | List sourceFaceInfos, 222 | List targetFaceInfos, 223 | String targetPath, 224 | String outputPath) async { 225 | await _callFunc( 226 | 'swap_image(${_py(sourceFaceInfos)},${_py(targetFaceInfos)},${_py(targetPath)},${_py(outputPath)})'); 227 | } 228 | 229 | static Future setArgs() async { 230 | List args = [ 231 | Settings.executionProvider.getSelectedNames(), 232 | Settings.executionThreads.value, 233 | Options.keepFPS.value, 234 | Options.keepFrames.value, 235 | !Options.audio.value, 236 | Options.tempFrameFormat.value.toLowerCase(), 237 | Options.tempFrameQuality.value, 238 | Options.outputVideoEncoder.value, 239 | Options.outputVideoQuality.value 240 | ]; 241 | await _callFunc('set_args(${args.map((e) => _py(e)).join(",")})'); 242 | } 243 | 244 | static Future getFaces(String path) async { 245 | return _callFunc('get_faces(r"$path")'); 246 | } 247 | 248 | static Future videoScreenshot( 249 | Duration duration, String input, String output) async { 250 | var r = await _callFunc( 251 | 'video_screenshot(${_py(duration.toString())},${_py(input)},${_py(output)})'); 252 | return r == "succ"; 253 | } 254 | 255 | static Future> getAvailableProviders() async { 256 | var r = await _callFunc('get_available_providers()'); 257 | List t = []; 258 | for (var i in r) { 259 | t.add(i.toString()); 260 | } 261 | return t; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /lib/gif/gif_view.dart: -------------------------------------------------------------------------------- 1 | ///Code modification from https://github.com/RafaelBarbosatec/gif_view 2 | 3 | import 'dart:async'; 4 | // ignore: unnecessary_import 5 | import 'dart:typed_data'; 6 | import 'dart:ui'; 7 | 8 | import 'package:flutter/foundation.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:http/http.dart' as http; 11 | 12 | import 'gif_controller.dart'; 13 | import 'git_frame.dart'; 14 | 15 | /// 16 | /// Created by 17 | /// 18 | /// ─▄▀─▄▀ 19 | /// ──▀──▀ 20 | /// █▀▀▀▀▀█▄ 21 | /// █░░░░░█─█ 22 | /// ▀▄▄▄▄▄▀▀ 23 | /// 24 | /// Rafaelbarbosatec 25 | /// on 23/09/21 26 | 27 | final Map> _cache = {}; 28 | 29 | class GifView extends StatefulWidget { 30 | final GifController? controller; 31 | final int? frameRate; 32 | final ImageProvider image; 33 | final double? height; 34 | final double? width; 35 | final Widget? progress; 36 | final BoxFit? fit; 37 | final Color? color; 38 | final BlendMode? colorBlendMode; 39 | final AlignmentGeometry alignment; 40 | final ImageRepeat repeat; 41 | final Rect? centerSlice; 42 | final bool matchTextDirection; 43 | final bool invertColors; 44 | final bool withOpacityAnimation; 45 | final FilterQuality filterQuality; 46 | final bool isAntiAlias; 47 | final ValueChanged? onError; 48 | final Duration? fadeDuration; 49 | 50 | GifView.network( 51 | String url, { 52 | Key? key, 53 | this.controller, 54 | this.frameRate, 55 | this.height, 56 | this.width, 57 | this.progress, 58 | this.fit, 59 | this.color, 60 | this.colorBlendMode, 61 | this.alignment = Alignment.center, 62 | this.repeat = ImageRepeat.noRepeat, 63 | this.centerSlice, 64 | this.matchTextDirection = false, 65 | this.invertColors = false, 66 | this.filterQuality = FilterQuality.low, 67 | this.isAntiAlias = false, 68 | this.withOpacityAnimation = true, 69 | this.onError, 70 | this.fadeDuration, 71 | double scale = 1.0, 72 | Map? headers, 73 | }) : image = NetworkImage(url, scale: scale, headers: headers), 74 | super(key: key); 75 | 76 | GifView.asset( 77 | String asset, { 78 | Key? key, 79 | this.controller, 80 | this.frameRate, 81 | this.height, 82 | this.width, 83 | this.progress, 84 | this.fit, 85 | this.color, 86 | this.colorBlendMode, 87 | this.alignment = Alignment.center, 88 | this.repeat = ImageRepeat.noRepeat, 89 | this.centerSlice, 90 | this.matchTextDirection = false, 91 | this.invertColors = false, 92 | this.filterQuality = FilterQuality.low, 93 | this.isAntiAlias = false, 94 | this.withOpacityAnimation = true, 95 | this.onError, 96 | this.fadeDuration, 97 | String? package, 98 | AssetBundle? bundle, 99 | }) : image = AssetImage(asset, package: package, bundle: bundle), 100 | super(key: key); 101 | 102 | GifView.memory( 103 | Uint8List bytes, { 104 | Key? key, 105 | this.controller, 106 | this.frameRate = 15, 107 | this.height, 108 | this.width, 109 | this.progress, 110 | this.fit, 111 | this.color, 112 | this.colorBlendMode, 113 | this.alignment = Alignment.center, 114 | this.repeat = ImageRepeat.noRepeat, 115 | this.centerSlice, 116 | this.matchTextDirection = false, 117 | this.invertColors = false, 118 | this.filterQuality = FilterQuality.low, 119 | this.isAntiAlias = false, 120 | this.withOpacityAnimation = true, 121 | this.onError, 122 | this.fadeDuration, 123 | double scale = 1.0, 124 | }) : image = MemoryImage(bytes, scale: scale), 125 | super(key: key); 126 | 127 | const GifView({ 128 | Key? key, 129 | required this.image, 130 | this.controller, 131 | this.frameRate, 132 | this.height, 133 | this.width, 134 | this.progress, 135 | this.fit, 136 | this.color, 137 | this.colorBlendMode, 138 | this.alignment = Alignment.center, 139 | this.repeat = ImageRepeat.noRepeat, 140 | this.centerSlice, 141 | this.matchTextDirection = false, 142 | this.invertColors = false, 143 | this.filterQuality = FilterQuality.low, 144 | this.isAntiAlias = false, 145 | this.withOpacityAnimation = true, 146 | this.onError, 147 | this.fadeDuration, 148 | }) : super(key: key); 149 | 150 | @override 151 | GifViewState createState() => GifViewState(); 152 | } 153 | 154 | class GifViewState extends State with TickerProviderStateMixin { 155 | late GifController controller; 156 | AnimationController? _animationController; 157 | 158 | @override 159 | void initState() { 160 | if (widget.withOpacityAnimation) { 161 | _animationController = AnimationController( 162 | vsync: this, 163 | duration: widget.fadeDuration ?? const Duration(milliseconds: 300), 164 | ); 165 | } 166 | controller = widget.controller ?? GifController(); 167 | controller.addListener(_listener); 168 | Future.delayed(Duration.zero, _loadImage); 169 | super.initState(); 170 | } 171 | 172 | @override 173 | void didUpdateWidget(covariant GifView oldWidget) { 174 | super.didUpdateWidget(oldWidget); 175 | if (oldWidget.image != widget.image) { 176 | _loadImage(updateFrames: true); 177 | } 178 | } 179 | 180 | @override 181 | Widget build(BuildContext context) { 182 | if (controller.status == GifStatus.loading) { 183 | return SizedBox( 184 | width: widget.width, 185 | height: widget.height, 186 | child: widget.progress, 187 | ); 188 | } 189 | return RawImage( 190 | image: controller.currentFrame.imageInfo.image, 191 | width: widget.width, 192 | height: widget.height, 193 | scale: controller.currentFrame.imageInfo.scale, 194 | fit: widget.fit, 195 | color: widget.color, 196 | colorBlendMode: widget.colorBlendMode, 197 | alignment: widget.alignment, 198 | repeat: widget.repeat, 199 | centerSlice: widget.centerSlice, 200 | matchTextDirection: widget.matchTextDirection, 201 | invertColors: widget.invertColors, 202 | filterQuality: widget.filterQuality, 203 | isAntiAlias: widget.isAntiAlias, 204 | opacity: _animationController, 205 | ); 206 | } 207 | 208 | String _getKeyImage(ImageProvider provider) { 209 | if (provider is FileImage) { 210 | return provider.file.path; 211 | } 212 | return provider is NetworkImage 213 | ? provider.url 214 | : provider is AssetImage 215 | ? provider.assetName 216 | : provider is MemoryImage 217 | ? provider.bytes.toString() 218 | : ""; 219 | } 220 | 221 | Future> _fetchGif(ImageProvider provider) async { 222 | List frameList = []; 223 | try { 224 | Uint8List? data; 225 | String key = _getKeyImage(provider); 226 | if (_cache.containsKey(key)) { 227 | frameList = _cache[key]!; 228 | return frameList; 229 | } 230 | if (provider is NetworkImage) { 231 | final Uri resolved = Uri.base.resolve(provider.url); 232 | Map headers = {}; 233 | provider.headers?.forEach((String name, String value) { 234 | headers[name] = value; 235 | }); 236 | final response = await http.get(resolved, headers: headers); 237 | data = response.bodyBytes; 238 | } else if (provider is AssetImage) { 239 | AssetBundleImageKey key = 240 | await provider.obtainKey(const ImageConfiguration()); 241 | final d = await key.bundle.load(key.name); 242 | data = d.buffer.asUint8List(); 243 | } else if (provider is FileImage) { 244 | data = await provider.file.readAsBytes(); 245 | } else if (provider is MemoryImage) { 246 | data = provider.bytes; 247 | } 248 | 249 | if (data == null) { 250 | return []; 251 | } 252 | 253 | Codec codec = await instantiateImageCodec( 254 | data, 255 | allowUpscaling: false, 256 | ); 257 | 258 | for (int i = 0; i < codec.frameCount; i++) { 259 | FrameInfo frameInfo = await codec.getNextFrame(); 260 | Duration duration = frameInfo.duration; 261 | if (widget.frameRate != null) { 262 | duration = Duration(milliseconds: (1000 / widget.frameRate!).ceil()); 263 | } 264 | frameList.add( 265 | GifFrame( 266 | ImageInfo(image: frameInfo.image), 267 | duration, 268 | ), 269 | ); 270 | } 271 | _cache.putIfAbsent(key, () => frameList); 272 | } catch (e) { 273 | if (widget.onError == null) { 274 | rethrow; 275 | } else { 276 | widget.onError?.call(e); 277 | } 278 | } 279 | return frameList; 280 | } 281 | 282 | FutureOr _loadImage({bool updateFrames = false}) async { 283 | final frames = await _fetchGif(widget.image); 284 | controller.configure(frames, updateFrames: updateFrames); 285 | _animationController?.forward(from: 0); 286 | } 287 | 288 | @override 289 | void dispose() { 290 | controller.removeListener(_listener); 291 | _animationController?.dispose(); 292 | _animationController = null; 293 | super.dispose(); 294 | } 295 | 296 | void _listener() { 297 | if (mounted) { 298 | setState(() {}); 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /lib/generated/l10n.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | import 'package:flutter/material.dart'; 3 | import 'package:intl/intl.dart'; 4 | import 'intl/messages_all.dart'; 5 | 6 | // ************************************************************************** 7 | // Generator: Flutter Intl IDE plugin 8 | // Made by Localizely 9 | // ************************************************************************** 10 | 11 | // ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars 12 | // ignore_for_file: join_return_with_assignment, prefer_final_in_for_each 13 | // ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes 14 | 15 | class S { 16 | S(); 17 | 18 | static S? _current; 19 | 20 | static S get current { 21 | assert(_current != null, 22 | 'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.'); 23 | return _current!; 24 | } 25 | 26 | static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); 27 | 28 | static Future load(Locale locale) { 29 | final name = (locale.countryCode?.isEmpty ?? false) 30 | ? locale.languageCode 31 | : locale.toString(); 32 | final localeName = Intl.canonicalizedLocale(name); 33 | return initializeMessages(localeName).then((_) { 34 | Intl.defaultLocale = localeName; 35 | final instance = S(); 36 | S._current = instance; 37 | 38 | return instance; 39 | }); 40 | } 41 | 42 | static S of(BuildContext context) { 43 | final instance = S.maybeOf(context); 44 | assert(instance != null, 45 | 'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?'); 46 | return instance!; 47 | } 48 | 49 | static S? maybeOf(BuildContext context) { 50 | return Localizations.of(context, S); 51 | } 52 | 53 | /// `All` 54 | String get file_filter_type_all { 55 | return Intl.message( 56 | 'All', 57 | name: 'file_filter_type_all', 58 | desc: '', 59 | args: [], 60 | ); 61 | } 62 | 63 | /// `Image` 64 | String get file_filter_type_img { 65 | return Intl.message( 66 | 'Image', 67 | name: 'file_filter_type_img', 68 | desc: '', 69 | args: [], 70 | ); 71 | } 72 | 73 | /// `GIF and Video` 74 | String get file_filter_type_gif_video { 75 | return Intl.message( 76 | 'GIF and Video', 77 | name: 'file_filter_type_gif_video', 78 | desc: '', 79 | args: [], 80 | ); 81 | } 82 | 83 | /// `Add Folder` 84 | String get add_folder { 85 | return Intl.message( 86 | 'Add Folder', 87 | name: 'add_folder', 88 | desc: '', 89 | args: [], 90 | ); 91 | } 92 | 93 | /// `Parent Folder` 94 | String get parent_folder { 95 | return Intl.message( 96 | 'Parent Folder', 97 | name: 'parent_folder', 98 | desc: '', 99 | args: [], 100 | ); 101 | } 102 | 103 | /// `Show in File Explorer` 104 | String get reveal_in_file_explorer { 105 | return Intl.message( 106 | 'Show in File Explorer', 107 | name: 'reveal_in_file_explorer', 108 | desc: '', 109 | args: [], 110 | ); 111 | } 112 | 113 | /// `Refresh` 114 | String get refresh { 115 | return Intl.message( 116 | 'Refresh', 117 | name: 'refresh', 118 | desc: '', 119 | args: [], 120 | ); 121 | } 122 | 123 | /// `Detect Faces` 124 | String get detect_faces { 125 | return Intl.message( 126 | 'Detect Faces', 127 | name: 'detect_faces', 128 | desc: '', 129 | args: [], 130 | ); 131 | } 132 | 133 | /// `Video` 134 | String get video { 135 | return Intl.message( 136 | 'Video', 137 | name: 'video', 138 | desc: '', 139 | args: [], 140 | ); 141 | } 142 | 143 | /// `Executing` 144 | String get executing { 145 | return Intl.message( 146 | 'Executing', 147 | name: 'executing', 148 | desc: '', 149 | args: [], 150 | ); 151 | } 152 | 153 | /// `Try to read service port from file:{path}` 154 | String try_to_read_service_port_from_file(Object path) { 155 | return Intl.message( 156 | 'Try to read service port from file:$path', 157 | name: 'try_to_read_service_port_from_file', 158 | desc: '', 159 | args: [path], 160 | ); 161 | } 162 | 163 | /// `Using port read from file:{port}` 164 | String using_port_read_from_file(Object port) { 165 | return Intl.message( 166 | 'Using port read from file:$port', 167 | name: 'using_port_read_from_file', 168 | desc: '', 169 | args: [port], 170 | ); 171 | } 172 | 173 | /// `Connection failed, restarting server` 174 | String get connection_failed_and_restart { 175 | return Intl.message( 176 | 'Connection failed, restarting server', 177 | name: 'connection_failed_and_restart', 178 | desc: '', 179 | args: [], 180 | ); 181 | } 182 | 183 | /// `Log` 184 | String get log { 185 | return Intl.message( 186 | 'Log', 187 | name: 'log', 188 | desc: '', 189 | args: [], 190 | ); 191 | } 192 | 193 | /// `Generating` 194 | String get generating { 195 | return Intl.message( 196 | 'Generating', 197 | name: 'generating', 198 | desc: '', 199 | args: [], 200 | ); 201 | } 202 | 203 | /// `Generate` 204 | String get generate { 205 | return Intl.message( 206 | 'Generate', 207 | name: 'generate', 208 | desc: '', 209 | args: [], 210 | ); 211 | } 212 | 213 | /// `Settings` 214 | String get settings { 215 | return Intl.message( 216 | 'Settings', 217 | name: 'settings', 218 | desc: '', 219 | args: [], 220 | ); 221 | } 222 | 223 | /// `Enhance` 224 | String get face_enhance { 225 | return Intl.message( 226 | 'Enhance', 227 | name: 'face_enhance', 228 | desc: '', 229 | args: [], 230 | ); 231 | } 232 | 233 | /// `Close` 234 | String get close { 235 | return Intl.message( 236 | 'Close', 237 | name: 'close', 238 | desc: '', 239 | args: [], 240 | ); 241 | } 242 | 243 | /// `Source` 244 | String get source { 245 | return Intl.message( 246 | 'Source', 247 | name: 'source', 248 | desc: '', 249 | args: [], 250 | ); 251 | } 252 | 253 | /// `Target` 254 | String get target { 255 | return Intl.message( 256 | 'Target', 257 | name: 'target', 258 | desc: '', 259 | args: [], 260 | ); 261 | } 262 | 263 | /// `Result` 264 | String get result { 265 | return Intl.message( 266 | 'Result', 267 | name: 'result', 268 | desc: '', 269 | args: [], 270 | ); 271 | } 272 | 273 | /// `Delete` 274 | String get delete { 275 | return Intl.message( 276 | 'Delete', 277 | name: 'delete', 278 | desc: '', 279 | args: [], 280 | ); 281 | } 282 | 283 | /// `Options` 284 | String get options { 285 | return Intl.message( 286 | 'Options', 287 | name: 'options', 288 | desc: '', 289 | args: [], 290 | ); 291 | } 292 | 293 | /// `Keep FPS` 294 | String get keep_fps { 295 | return Intl.message( 296 | 'Keep FPS', 297 | name: 'keep_fps', 298 | desc: '', 299 | args: [], 300 | ); 301 | } 302 | 303 | /// `Audio` 304 | String get audio { 305 | return Intl.message( 306 | 'Audio', 307 | name: 'audio', 308 | desc: '', 309 | args: [], 310 | ); 311 | } 312 | 313 | /// `Keep frames folder` 314 | String get keep_frames { 315 | return Intl.message( 316 | 'Keep frames folder', 317 | name: 'keep_frames', 318 | desc: '', 319 | args: [], 320 | ); 321 | } 322 | 323 | /// `Video facial minimum similarity:` 324 | String get min_similarity { 325 | return Intl.message( 326 | 'Video facial minimum similarity:', 327 | name: 'min_similarity', 328 | desc: '', 329 | args: [], 330 | ); 331 | } 332 | 333 | /// `Video Encoder:` 334 | String get output_video_encoder { 335 | return Intl.message( 336 | 'Video Encoder:', 337 | name: 'output_video_encoder', 338 | desc: '', 339 | args: [], 340 | ); 341 | } 342 | 343 | /// `Video Quality:` 344 | String get output_video_quality { 345 | return Intl.message( 346 | 'Video Quality:', 347 | name: 'output_video_quality', 348 | desc: '', 349 | args: [], 350 | ); 351 | } 352 | 353 | /// `Frame Format:` 354 | String get temp_frame_format { 355 | return Intl.message( 356 | 'Frame Format:', 357 | name: 'temp_frame_format', 358 | desc: '', 359 | args: [], 360 | ); 361 | } 362 | 363 | /// `Frame Quality:` 364 | String get temp_frame_quality { 365 | return Intl.message( 366 | 'Frame Quality:', 367 | name: 'temp_frame_quality', 368 | desc: '', 369 | args: [], 370 | ); 371 | } 372 | 373 | /// `Set as Default` 374 | String get set_as_default { 375 | return Intl.message( 376 | 'Set as Default', 377 | name: 'set_as_default', 378 | desc: '', 379 | args: [], 380 | ); 381 | } 382 | 383 | /// `Drag the labels to adjust the order, with higher priority as they move to the left.` 384 | String get provider_desc { 385 | return Intl.message( 386 | 'Drag the labels to adjust the order, with higher priority as they move to the left.', 387 | name: 'provider_desc', 388 | desc: '', 389 | args: [], 390 | ); 391 | } 392 | 393 | /// `Execution Providers:` 394 | String get execution_providers { 395 | return Intl.message( 396 | 'Execution Providers:', 397 | name: 'execution_providers', 398 | desc: '', 399 | args: [], 400 | ); 401 | } 402 | 403 | /// `Execution Threads:` 404 | String get execution_threads { 405 | return Intl.message( 406 | 'Execution Threads:', 407 | name: 'execution_threads', 408 | desc: '', 409 | args: [], 410 | ); 411 | } 412 | } 413 | 414 | class AppLocalizationDelegate extends LocalizationsDelegate { 415 | const AppLocalizationDelegate(); 416 | 417 | List get supportedLocales { 418 | return const [ 419 | Locale.fromSubtags(languageCode: 'en'), 420 | Locale.fromSubtags(languageCode: 'zh', countryCode: 'CN'), 421 | ]; 422 | } 423 | 424 | @override 425 | bool isSupported(Locale locale) => _isSupported(locale); 426 | @override 427 | Future load(Locale locale) => S.load(locale); 428 | @override 429 | bool shouldReload(AppLocalizationDelegate old) => false; 430 | 431 | bool _isSupported(Locale locale) { 432 | for (var supportedLocale in supportedLocales) { 433 | if (supportedLocale.languageCode == locale.languageCode) { 434 | return true; 435 | } 436 | } 437 | return false; 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /lib/settings_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:dropdown_button2/dropdown_button2.dart'; 2 | import 'package:faceswap/field.dart'; 3 | import 'package:faceswap/settings.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | import 'generated/l10n.dart'; 7 | 8 | class SettingsView extends StatefulWidget { 9 | static show(BuildContext context) { 10 | showDialog( 11 | context: context, 12 | builder: (context) => const Center(child: SettingsView()), 13 | ).then((_) { 14 | Settings.save(); 15 | }); 16 | } 17 | 18 | const SettingsView({super.key}); 19 | 20 | @override 21 | State createState() => _SettingsViewState(); 22 | } 23 | 24 | class _SettingsViewState extends State { 25 | late ThemeData theme; 26 | 27 | var _selectedIndex = 0; 28 | 29 | Widget _item({required String title, String? desc, required Widget child}) { 30 | return Column( 31 | crossAxisAlignment: CrossAxisAlignment.start, 32 | children: [ 33 | Text( 34 | title, 35 | style: TextStyle( 36 | fontSize: 18, color: theme.colorScheme.onSecondaryContainer), 37 | ), 38 | const SizedBox( 39 | height: 10, 40 | ), 41 | if (desc != null) 42 | Text(desc, 43 | style: TextStyle( 44 | fontSize: 12, 45 | color: 46 | theme.colorScheme.onSecondaryContainer.withOpacity(0.8))), 47 | const SizedBox(height: 2), 48 | Padding( 49 | padding: const EdgeInsets.symmetric(horizontal: 5), 50 | child: child, 51 | ), 52 | const SizedBox( 53 | height: 30, 54 | ), 55 | ], 56 | ); 57 | } 58 | 59 | Widget _dropdownlist(String selectedItem, List items, 60 | void Function(String? value) onChange) { 61 | return DropdownButtonHideUnderline( 62 | child: DropdownButton2( 63 | items: [ 64 | for (var item in items) 65 | DropdownMenuItem( 66 | value: item, 67 | child: Text( 68 | item, 69 | ), 70 | ) 71 | ], 72 | value: selectedItem, 73 | onChanged: onChange, 74 | buttonStyleData: const ButtonStyleData( 75 | height: 35, 76 | width: 160, 77 | padding: EdgeInsets.only(left: 14, right: 14), 78 | ), 79 | dropdownStyleData: DropdownStyleData( 80 | decoration: BoxDecoration( 81 | borderRadius: BorderRadius.circular(5), 82 | ), 83 | elevation: 3, 84 | ), 85 | menuItemStyleData: const MenuItemStyleData( 86 | height: 35, 87 | ), 88 | ), 89 | ); 90 | } 91 | 92 | Widget _slider(RangedIntField value) { 93 | return Row( 94 | children: [ 95 | const SizedBox( 96 | width: 5, 97 | ), 98 | Text(value.min.toString()), 99 | const SizedBox( 100 | width: 2, 101 | ), 102 | Expanded( 103 | child: Slider( 104 | min: value.min.toDouble(), 105 | max: value.max.toDouble(), 106 | onChanged: (t) { 107 | value.value = t.floor(); 108 | }, 109 | value: value.value.toDouble(), 110 | )), 111 | const SizedBox( 112 | width: 2, 113 | ), 114 | Text(value.max.toString()), 115 | const SizedBox( 116 | width: 5, 117 | ), 118 | ], 119 | ); 120 | } 121 | 122 | @override 123 | Widget build(BuildContext context) { 124 | theme = Theme.of(context); 125 | const tabs = ["ROOP"]; 126 | return ClipRRect( 127 | borderRadius: BorderRadius.circular(6), 128 | child: Material( 129 | color: theme.colorScheme.secondaryContainer, 130 | child: SizedBox( 131 | width: 600, 132 | height: 500, 133 | child: Row( 134 | children: [ 135 | Container( 136 | width: 100, 137 | color: theme.colorScheme.surface, 138 | child: ListView( 139 | children: [ 140 | for (var i = 0; i < tabs.length; i++) 141 | Material( 142 | color: Colors.transparent, 143 | child: ListTile( 144 | title: Text(tabs[i]), 145 | selected: _selectedIndex == i, 146 | selectedColor: theme.colorScheme.primary, 147 | textColor: theme.colorScheme.onSurface 148 | .withOpacity(0.64), 149 | titleTextStyle: theme.textTheme.titleMedium, 150 | selectedTileColor: 151 | theme.colorScheme.secondaryContainer, 152 | onTap: _selectedIndex == i 153 | ? null 154 | : () { 155 | setState(() { 156 | _selectedIndex = i; 157 | }); 158 | }, 159 | )), 160 | ], 161 | ), 162 | ), 163 | Expanded( 164 | child: Column( 165 | crossAxisAlignment: CrossAxisAlignment.start, 166 | children: [ 167 | Expanded( 168 | child: Container( 169 | padding: const EdgeInsets.only( 170 | left: 20, right: 20, top: 20), 171 | child: _buildContent(), 172 | )), 173 | Container( 174 | padding: const EdgeInsets.all(10), 175 | alignment: Alignment.bottomRight, 176 | child: FilledButton( 177 | onPressed: () { 178 | Navigator.of(context).pop(); 179 | }, 180 | child: Text(S.of(context).close))), 181 | ]), 182 | ) 183 | ], 184 | ), 185 | ))); 186 | } 187 | 188 | Widget _buildProvider() { 189 | var provider = Settings.executionProvider.value; 190 | return SizedBox( 191 | height: 40, 192 | child: provider.isEmpty 193 | ? null 194 | : ReorderableListView( 195 | scrollDirection: Axis.horizontal, 196 | proxyDecorator: (child, index, animation) { 197 | return Material( 198 | color: Colors.transparent, 199 | child: Stack(children: [ 200 | Container( 201 | margin: const EdgeInsets.all(5), 202 | decoration: BoxDecoration( 203 | border: Border.all( 204 | width: 3, color: theme.colorScheme.outline), 205 | borderRadius: BorderRadius.circular(10), 206 | color: theme.colorScheme.secondaryContainer), 207 | ), 208 | child 209 | ])); 210 | }, 211 | buildDefaultDragHandles: false, 212 | onReorder: (int oldIndex, int newIndex) { 213 | if (newIndex > oldIndex) { 214 | newIndex--; 215 | } 216 | var old = provider.removeAt(oldIndex); 217 | provider.insert(newIndex, old); 218 | Settings.executionProvider.notifyListeners(); 219 | }, 220 | children: [ 221 | for (var i = 0; i < provider.length; i++) 222 | ReorderableDragStartListener( 223 | index: i, 224 | key: ValueKey(provider[i].name), 225 | child: Container( 226 | margin: const EdgeInsets.all(5), 227 | decoration: BoxDecoration( 228 | border: 229 | Border.all(color: theme.colorScheme.outline), 230 | borderRadius: BorderRadius.circular(10), 231 | ), 232 | alignment: Alignment.center, 233 | width: 100, 234 | child: Row(children: [ 235 | Checkbox( 236 | value: provider[i].selected, 237 | onChanged: (v) { 238 | provider[i].selected = v!; 239 | Settings.executionProvider.notifyListeners(); 240 | }), 241 | Text( 242 | provider[i] 243 | .name 244 | .replaceAll("ExecutionProvider", ""), 245 | strutStyle: const StrutStyle( 246 | forceStrutHeight: true, 247 | height: 1.2, 248 | ), 249 | style: TextStyle( 250 | fontSize: 15, 251 | color: 252 | theme.colorScheme.onSecondaryContainer), 253 | ) 254 | ]), 255 | )) 256 | ], 257 | )); 258 | } 259 | 260 | Widget _buildContent() { 261 | switch (_selectedIndex) { 262 | case 0: 263 | return Column( 264 | crossAxisAlignment: CrossAxisAlignment.start, 265 | children: [ 266 | ListenableBuilder( 267 | listenable: Settings.executionProvider, 268 | builder: (context, child) { 269 | return _item( 270 | title: S.of(context).execution_providers, 271 | desc: Settings.executionProvider.value.length <= 1 272 | ? null 273 | : S.of(context).provider_desc, 274 | child: _buildProvider(), 275 | ); 276 | }, 277 | ), 278 | ListenableBuilder( 279 | listenable: Settings.executionThreads, 280 | builder: (context, child) { 281 | return _item( 282 | title: 283 | "${S.of(context).execution_threads} ${Settings.executionThreads.value}", 284 | child: _slider(Settings.executionThreads), 285 | ); 286 | }), 287 | ], 288 | ); 289 | } 290 | return const SizedBox(); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /lib/options_menu_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:dropdown_button2/dropdown_button2.dart'; 2 | import 'package:faceswap/generated/l10n.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'field.dart'; 6 | import 'options.dart'; 7 | 8 | class OptionsMenuButton extends StatefulWidget { 9 | const OptionsMenuButton({super.key}); 10 | 11 | @override 12 | State createState() => _OptionsMenuButtonState(); 13 | } 14 | 15 | class _OptionsMenuButtonState extends State { 16 | @override 17 | Widget build(BuildContext context) { 18 | return TextButton.icon( 19 | onPressed: () { 20 | final RenderBox button = context.findRenderObject()! as RenderBox; 21 | final RenderBox overlay = Navigator.of(context) 22 | .overlay! 23 | .context 24 | .findRenderObject()! as RenderBox; 25 | final Offset position = 26 | button.localToGlobal(const Offset(-60, 0), ancestor: overlay); 27 | Navigator.push(context, 28 | OptionsPopupRoute(position: position, child: const OptionsMenu())); 29 | }, 30 | icon: const Icon(Icons.list), 31 | label: Text(S.of(context).options), 32 | ); 33 | } 34 | } 35 | 36 | class OptionsMenu extends StatefulWidget { 37 | const OptionsMenu({super.key}); 38 | 39 | @override 40 | State createState() => _OptionsMenuState(); 41 | } 42 | 43 | class _OptionsMenuState extends State { 44 | late ThemeData theme; 45 | Widget _buildCheckItem(String label, ValueNotifier value) { 46 | return SizedBox( 47 | width: double.infinity, 48 | height: 45, 49 | child: InkWell( 50 | onTap: () { 51 | value.value = !value.value; 52 | }, 53 | child: Row( 54 | mainAxisSize: MainAxisSize.min, 55 | children: [ 56 | ValueListenableBuilder( 57 | valueListenable: value, 58 | builder: (context, v, child) { 59 | return IgnorePointer( 60 | child: Checkbox( 61 | onChanged: (_) {}, 62 | value: v, 63 | ), 64 | ); 65 | }, 66 | ), 67 | Text( 68 | label, 69 | style: DefaultTextStyle.of(context) 70 | .style 71 | .copyWith(color: theme.colorScheme.onSurface), 72 | ) 73 | ], 74 | ), 75 | )); 76 | } 77 | 78 | Widget _buildSliderItem(String label, RangedIntField value) { 79 | return Container( 80 | margin: const EdgeInsets.all(3), 81 | decoration: BoxDecoration( 82 | border: Border.all( 83 | color: theme.colorScheme.outlineVariant.withOpacity(0.5)), 84 | borderRadius: BorderRadius.circular(10)), 85 | child: ValueListenableBuilder( 86 | valueListenable: value, 87 | builder: (context, v, child) { 88 | return Column( 89 | crossAxisAlignment: CrossAxisAlignment.start, 90 | children: [ 91 | Padding( 92 | padding: const EdgeInsets.only(left: 7, right: 7, top: 7), 93 | child: Text( 94 | "$label $v", 95 | style: DefaultTextStyle.of(context) 96 | .style 97 | .copyWith(color: theme.colorScheme.onSurface), 98 | ), 99 | ), 100 | Slider( 101 | min: value.min.toDouble(), 102 | max: value.max.toDouble(), 103 | onChanged: (t) { 104 | value.value = t.floor(); 105 | }, 106 | value: v.toDouble(), 107 | ) 108 | ], 109 | ); 110 | }, 111 | ), 112 | ); 113 | } 114 | 115 | Widget _buildDropdownListItem(String label, ChoicesField value) { 116 | return ValueListenableBuilder( 117 | valueListenable: value, 118 | builder: (context, v, child) { 119 | return Row( 120 | crossAxisAlignment: CrossAxisAlignment.start, 121 | children: [ 122 | Padding( 123 | padding: const EdgeInsets.only(left: 10, right: 10, top: 10), 124 | child: Text( 125 | label, 126 | style: DefaultTextStyle.of(context) 127 | .style 128 | .copyWith(color: theme.colorScheme.onSurface), 129 | ), 130 | ), 131 | Expanded( 132 | child: DropdownButtonHideUnderline( 133 | child: DropdownButton2( 134 | items: [ 135 | for (var item in value.choices) 136 | DropdownMenuItem( 137 | value: item, 138 | child: Text( 139 | item, 140 | ), 141 | ) 142 | ], 143 | value: v, 144 | onChanged: (t) { 145 | if (t != null) { 146 | value.value = t; 147 | } 148 | }, 149 | buttonStyleData: ButtonStyleData( 150 | decoration: BoxDecoration( 151 | borderRadius: BorderRadius.circular(10), 152 | ), 153 | height: 35, 154 | ), 155 | dropdownStyleData: DropdownStyleData( 156 | decoration: BoxDecoration( 157 | border: Border.all( 158 | color: 159 | theme.colorScheme.outlineVariant.withOpacity(0.5)), 160 | borderRadius: BorderRadius.circular(10), 161 | ), 162 | elevation: 3, 163 | ), 164 | menuItemStyleData: const MenuItemStyleData( 165 | height: 35, 166 | ), 167 | ), 168 | )), 169 | const SizedBox(width: 6) 170 | ], 171 | ); 172 | }, 173 | ); 174 | } 175 | 176 | @override 177 | Widget build(BuildContext context) { 178 | theme = Theme.of(context); 179 | return Ink( 180 | decoration: BoxDecoration( 181 | border: Border.all(color: theme.colorScheme.outlineVariant), 182 | borderRadius: BorderRadius.circular(10), 183 | color: theme.colorScheme.surface, 184 | ), 185 | child: Container( 186 | padding: const EdgeInsets.symmetric(vertical: 3), 187 | width: 300, 188 | child: Column( 189 | mainAxisSize: MainAxisSize.min, 190 | crossAxisAlignment: CrossAxisAlignment.start, 191 | children: [ 192 | _buildCheckItem(S.of(context).keep_fps, Options.keepFPS), 193 | _buildCheckItem(S.of(context).audio, Options.audio), 194 | _buildCheckItem(S.of(context).keep_frames, Options.keepFrames), 195 | _buildSliderItem( 196 | S.of(context).min_similarity, Options.minSimilarity), 197 | _buildDropdownListItem(S.of(context).output_video_encoder, 198 | Options.outputVideoEncoder), 199 | _buildSliderItem(S.of(context).output_video_quality, 200 | Options.outputVideoQuality), 201 | _buildDropdownListItem( 202 | S.of(context).temp_frame_format, Options.tempFrameFormat), 203 | _buildSliderItem( 204 | S.of(context).temp_frame_quality, Options.tempFrameQuality), 205 | Align( 206 | alignment: Alignment.centerRight, 207 | child: TextButton( 208 | onPressed: () { 209 | Options.save(); 210 | Navigator.of(context).pop(); 211 | }, 212 | child: Text(S.of(context).set_as_default)), 213 | ) 214 | ], 215 | )), 216 | ); 217 | } 218 | } 219 | 220 | class OptionsPopupRoute extends PopupRoute { 221 | final _duration = const Duration(milliseconds: 100); 222 | Offset position; 223 | Widget child; 224 | OptionsPopupRoute({required this.child, required this.position}); 225 | 226 | @override 227 | Animation createAnimation() { 228 | return CurvedAnimation( 229 | parent: super.createAnimation(), 230 | curve: Curves.linear, 231 | reverseCurve: const Interval(0.0, 2 / 3), 232 | ); 233 | } 234 | 235 | @override 236 | Color? get barrierColor => null; 237 | 238 | @override 239 | bool get barrierDismissible => true; 240 | 241 | @override 242 | String? get barrierLabel => null; 243 | 244 | @override 245 | Widget buildPage(BuildContext context, Animation animation, 246 | Animation secondaryAnimation) { 247 | return OptionsPopup(route: this, position: position, child: child); 248 | } 249 | 250 | @override 251 | Duration get transitionDuration => _duration; 252 | } 253 | 254 | class OptionsPopup extends StatelessWidget { 255 | final Widget child; 256 | final Offset position; 257 | final OptionsPopupRoute route; 258 | const OptionsPopup( 259 | {super.key, 260 | required this.child, 261 | required this.position, 262 | required this.route}); 263 | 264 | @override 265 | Widget build(BuildContext context) { 266 | final CurveTween opacity = 267 | CurveTween(curve: const Interval(0.0, 1.0 / 3.0)); 268 | final CurveTween height = CurveTween(curve: const Interval(0.0, 1)); 269 | Widget t = Material(elevation: 6, color: Colors.transparent, child: child); 270 | return CustomSingleChildLayout( 271 | delegate: _PopupMenuRouteLayout(position), 272 | child: AnimatedBuilder( 273 | animation: route.animation!, 274 | builder: (BuildContext context, Widget? child) { 275 | return FadeTransition( 276 | opacity: opacity.animate(route.animation!), 277 | child: Align( 278 | widthFactor: 1, 279 | heightFactor: 0.9 + 0.1 * height.evaluate(route.animation!), 280 | child: t, 281 | )); 282 | }), 283 | ); 284 | } 285 | } 286 | 287 | class _PopupMenuRouteLayout extends SingleChildLayoutDelegate { 288 | final Offset position; 289 | _PopupMenuRouteLayout( 290 | this.position, 291 | ); 292 | 293 | @override 294 | BoxConstraints getConstraintsForChild(BoxConstraints constraints) { 295 | return BoxConstraints.loose(constraints.biggest).deflate( 296 | const EdgeInsets.all(10), 297 | ); 298 | } 299 | 300 | @override 301 | Offset getPositionForChild(Size size, Size childSize) { 302 | double padding = 10; 303 | double x = position.dx; 304 | double y = position.dy - childSize.height; 305 | var screen = Rect.fromLTWH(0, 0, size.width, size.height); 306 | if (x < screen.left + padding) { 307 | x = screen.left + padding; 308 | } else if (x + childSize.width > screen.right - padding) { 309 | x = screen.right - childSize.width - padding; 310 | } 311 | if (y < screen.top + padding) { 312 | y = padding; 313 | } else if (y + childSize.height > screen.bottom - padding) { 314 | y = screen.bottom - childSize.height - padding; 315 | } 316 | return Offset(x, y); 317 | } 318 | 319 | @override 320 | bool shouldRelayout(_PopupMenuRouteLayout oldDelegate) { 321 | return false; 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /lib/file_select_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:faceswap/toolbar.dart'; 5 | import 'package:faceswap/util.dart'; 6 | import 'package:file_selector/file_selector.dart'; 7 | import 'package:flex_color_scheme/flex_color_scheme.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:path/path.dart' as path; 10 | 11 | import 'generated/l10n.dart'; 12 | 13 | class FileSelectView extends StatefulWidget { 14 | final String dir; 15 | final ValueNotifier selectedFile; 16 | final bool onlyImg; 17 | const FileSelectView({ 18 | super.key, 19 | required this.dir, 20 | required this.selectedFile, 21 | this.onlyImg = false, 22 | }); 23 | 24 | @override 25 | State createState() => FileSelectViewState(); 26 | } 27 | 28 | enum FileType { all, img, gifAndVideo } 29 | 30 | extension FileTypeEx on FileType { 31 | String getDesc(BuildContext context) { 32 | switch (this) { 33 | case FileType.all: 34 | return S.of(context).file_filter_type_all; 35 | case FileType.img: 36 | return S.of(context).file_filter_type_img; 37 | case FileType.gifAndVideo: 38 | return S.of(context).file_filter_type_gif_video; 39 | } 40 | } 41 | 42 | IconData get icon { 43 | switch (this) { 44 | case FileType.all: 45 | return Icons.filter_alt_off_outlined; 46 | case FileType.img: 47 | return Icons.image_outlined; 48 | case FileType.gifAndVideo: 49 | return Icons.videocam_outlined; 50 | } 51 | } 52 | 53 | FileType get next { 54 | return FileType.values[(index + 1) % FileType.values.length]; 55 | } 56 | } 57 | 58 | class FileSelectViewState extends State { 59 | late Directory dir; 60 | List files = []; 61 | FileSystemEntity? tempSelectedFile; 62 | late ThemeData theme; 63 | 64 | StreamSubscription? watchListener; 65 | StreamSubscription? listListener; 66 | 67 | FileType type = FileType.all; 68 | @override 69 | void initState() { 70 | setDir(widget.dir); 71 | super.initState(); 72 | } 73 | 74 | @override 75 | void dispose() { 76 | watchListener?.cancel(); 77 | listListener?.cancel(); 78 | super.dispose(); 79 | } 80 | 81 | setDir(path) { 82 | dir = path is Directory ? path : Directory(path); 83 | watchListener?.cancel(); 84 | watchListener = dir.watch().listen((event) { 85 | refreshDir(); 86 | }); 87 | refreshDir(); 88 | } 89 | 90 | refreshDir() { 91 | files.clear(); 92 | /*if (dir.path != Uri.file(widget.dir).toFilePath()) { 93 | files.add(null); 94 | }*/ 95 | listListener?.cancel(); 96 | listListener = dir.list().listen((f) { 97 | if (f is File) { 98 | if ((widget.onlyImg || type == FileType.img) && !f.isImg || 99 | type == FileType.gifAndVideo && !f.isGifOrVideo) { 100 | return; 101 | } 102 | files.add(f); 103 | } else { 104 | files.insert(0, f); 105 | } 106 | setState(() {}); 107 | }); 108 | } 109 | 110 | @override 111 | Widget build(BuildContext context) { 112 | theme = Theme.of(context); 113 | return ValueListenableBuilder( 114 | valueListenable: widget.selectedFile, 115 | builder: (BuildContext context, value, Widget? child) { 116 | Widget child = Column( 117 | children: [ 118 | Toolbar( 119 | children: [ 120 | Expanded( 121 | child: Tooltip( 122 | message: dir.path, 123 | child: Text( 124 | dir.path, 125 | softWrap: false, 126 | overflow: TextOverflow.fade, 127 | ), 128 | )), 129 | if (!widget.onlyImg) 130 | IconButton( 131 | tooltip: type.getDesc(context), 132 | onPressed: () { 133 | type = type.next; 134 | refreshDir(); 135 | }, 136 | icon: Icon(type.icon), 137 | ), 138 | IconButton( 139 | tooltip: S.of(context).add_folder, 140 | icon: const Icon( 141 | Icons.add_link, 142 | ), 143 | onPressed: () async { 144 | String? source = await getDirectoryPath(); 145 | if (source == null) { 146 | return; 147 | } 148 | String basePath = 149 | path.join(dir.path, path.split(source).last); 150 | String target = basePath; 151 | int i = 1; 152 | while (true) { 153 | if (i > 1) { 154 | target = "${basePath}_$i"; 155 | } 156 | if (!await Directory(target).exists()) { 157 | break; 158 | } 159 | i++; 160 | } 161 | try { 162 | await Process.start('mklink', ["/D", target, source], 163 | runInShell: true); 164 | debugPrint("Finished"); 165 | } catch (err) { 166 | debugPrint("Err:$err"); 167 | } 168 | }, 169 | ), 170 | IconButton( 171 | tooltip: S.of(context).parent_folder, 172 | icon: const Icon( 173 | Icons.arrow_upward, 174 | ), 175 | onPressed: !dir.parent.path.startsWith(widget.dir) 176 | ? null 177 | : () { 178 | setState(() { 179 | setDir(dir.parent); 180 | }); 181 | }, 182 | ), 183 | IconButton( 184 | tooltip: S.of(context).reveal_in_file_explorer, 185 | icon: const Icon( 186 | Icons.folder_open, 187 | ), 188 | onPressed: () { 189 | Util.revealInExplorer(dir, widget.selectedFile.value); 190 | }, 191 | ), 192 | IconButton( 193 | tooltip: S.of(context).refresh, 194 | icon: const Icon( 195 | Icons.refresh, 196 | ), 197 | onPressed: () { 198 | setState(() { 199 | refreshDir(); 200 | }); 201 | }, 202 | ), 203 | ], 204 | ), 205 | Expanded( 206 | child: Container( 207 | color: theme.colorScheme.background, 208 | child: GridView.builder( 209 | gridDelegate: 210 | const SliverGridDelegateWithMaxCrossAxisExtent( 211 | maxCrossAxisExtent: 126), 212 | itemCount: files.length, 213 | itemBuilder: (context, index) { 214 | var f = files[index]; 215 | bool isTempSelected = tempSelectedFile == f; 216 | bool isSelected = f != null && 217 | widget.selectedFile.value?.path == f.path; 218 | Widget child; 219 | String fName; 220 | if (f == null) { 221 | child = Icon( 222 | Icons.folder_outlined, 223 | size: 60, 224 | color: theme.colorScheme.secondary, 225 | ); 226 | fName = S.of(context).parent_folder; 227 | } else { 228 | if (f is File) { 229 | fName = f.uri.pathSegments.last; 230 | if (f.isImgOrGif) { 231 | child = Image.file(f); 232 | } else { 233 | child = Icon( 234 | Icons.video_file_outlined, 235 | size: 60, 236 | color: theme.colorScheme.secondary, 237 | ); 238 | } 239 | } else { 240 | child = Icon( 241 | Icons.folder, 242 | size: 60, 243 | color: theme.colorScheme.secondary, 244 | ); 245 | fName = f.uri.pathSegments 246 | .elementAt(f.uri.pathSegments.length - 2); 247 | } 248 | } 249 | child = GestureDetector( 250 | onDoubleTap: () { 251 | if (f is File) { 252 | widget.selectedFile.value = f; 253 | } else { 254 | setState(() { 255 | setDir(dir = 256 | f == null ? dir.parent : f as Directory); 257 | }); 258 | } 259 | }, 260 | child: Container( 261 | width: 100, 262 | height: 100, 263 | decoration: BoxDecoration( 264 | borderRadius: BorderRadius.circular(10), 265 | border: Border.all( 266 | color: isSelected 267 | ? theme.colorScheme.primary 268 | : Colors.transparent, 269 | ), 270 | color: isTempSelected 271 | ? theme.colorScheme.secondaryContainer 272 | : Colors.transparent, 273 | ), 274 | padding: const EdgeInsets.all(5), 275 | margin: const EdgeInsets.all(2), 276 | child: Column( 277 | children: [ 278 | Expanded( 279 | child: child, 280 | ), 281 | Text( 282 | fName, 283 | overflow: TextOverflow.ellipsis, 284 | style: TextStyle( 285 | color: theme.colorScheme.onBackground), 286 | ) 287 | ], 288 | ), 289 | )); 290 | child = Listener( 291 | onPointerDown: (v) { 292 | setState(() { 293 | tempSelectedFile = f; 294 | }); 295 | }, 296 | child: child, 297 | ); 298 | child = Tooltip( 299 | waitDuration: const Duration(seconds: 1), 300 | message: fName, 301 | child: child, 302 | ); 303 | return child; 304 | }, 305 | )), 306 | ) 307 | ], 308 | ); 309 | return child; 310 | }, 311 | ); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /lib/face_swap_list.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:linked_scroll_controller/linked_scroll_controller.dart'; 5 | 6 | import 'data.dart'; 7 | import 'generated/l10n.dart'; 8 | 9 | class FaceSwapList extends StatefulWidget { 10 | final SwapData swapData; 11 | const FaceSwapList({super.key, required this.swapData}); 12 | 13 | @override 14 | State createState() => _FaceSwapListState(); 15 | } 16 | 17 | class _FaceSwapListState extends State { 18 | late LinkedScrollControllerGroup _swapListScrollControllers; 19 | late ScrollController _swapListScrollController_source; 20 | late ScrollController _swapListScrollController_arrow; 21 | late ScrollController _swapListScrollController_target; 22 | 23 | late ThemeData theme; 24 | 25 | @override 26 | void initState() { 27 | _swapListScrollControllers = LinkedScrollControllerGroup(); 28 | _swapListScrollController_source = _swapListScrollControllers.addAndGet(); 29 | _swapListScrollController_arrow = _swapListScrollControllers.addAndGet(); 30 | _swapListScrollController_target = _swapListScrollControllers.addAndGet(); 31 | super.initState(); 32 | } 33 | 34 | @override 35 | void dispose() { 36 | _swapListScrollController_source.dispose(); 37 | _swapListScrollController_arrow.dispose(); 38 | _swapListScrollController_target.dispose(); 39 | super.dispose(); 40 | } 41 | 42 | Widget _buildFaceListItem(RectData item, void Function() onDelTap, 43 | [List? trailings]) { 44 | return Container( 45 | decoration: BoxDecoration( 46 | borderRadius: BorderRadius.circular(10), 47 | border: Border.all( 48 | color: theme.colorScheme.outlineVariant.withOpacity(0.5)), 49 | color: theme.cardColor, 50 | ), 51 | child: ClipRRect( 52 | borderRadius: BorderRadius.circular(10), 53 | child: Row( 54 | mainAxisSize: MainAxisSize.max, 55 | children: [ 56 | const SizedBox( 57 | width: 3, 58 | ), 59 | Stack( 60 | children: [ 61 | SizedBox( 62 | width: 80, 63 | child: FittedBox( 64 | child: SizedOverflowBox( 65 | size: Size(item.rect.width, item.rect.height), 66 | alignment: Alignment.topLeft, 67 | child: Transform.translate( 68 | offset: Offset(-item.rect.left, -item.rect.top), 69 | child: ClipRect( 70 | clipper: FaceCliper(item.rect), 71 | child: Image.file( 72 | item.parent.fileForShow, 73 | fit: BoxFit.none, 74 | alignment: Alignment.topLeft, 75 | ), 76 | ), 77 | ), 78 | )), 79 | ), 80 | Tooltip( 81 | message: S.of(context).delete, 82 | child: SizedBox( 83 | width: 26, 84 | height: 26, 85 | child: RawMaterialButton( 86 | shape: RoundedRectangleBorder( 87 | borderRadius: BorderRadius.circular(10)), 88 | fillColor: theme.colorScheme.secondaryContainer 89 | .withOpacity(0.6), 90 | child: Icon( 91 | Icons.delete_forever, 92 | color: theme.colorScheme.secondary, 93 | ), 94 | onPressed: onDelTap, 95 | ), 96 | )), 97 | ], 98 | ), 99 | if (trailings != null) ...trailings, 100 | ], 101 | ))); 102 | } 103 | 104 | Widget _buildThreeStateCheckBox( 105 | bool? isSelected, 106 | void Function() onTap, 107 | ) { 108 | if (isSelected != null) { 109 | return Checkbox( 110 | value: isSelected, 111 | onChanged: (value) { 112 | onTap(); 113 | }, 114 | ); 115 | } 116 | return Stack( 117 | alignment: Alignment.center, 118 | children: [ 119 | Container( 120 | width: 10, 121 | height: 10, 122 | color: theme.primaryColor, 123 | ), 124 | Checkbox( 125 | onChanged: (bool? value) { 126 | onTap(); 127 | }, 128 | value: false, 129 | ), 130 | ], 131 | ); 132 | } 133 | 134 | @override 135 | Widget build(BuildContext context) { 136 | theme = Theme.of(context); 137 | return Column(children: [ 138 | Container( 139 | decoration: BoxDecoration( 140 | border: Border( 141 | bottom: BorderSide(color: theme.colorScheme.outlineVariant)), 142 | //color: theme.colorScheme.primaryContainer, 143 | ), 144 | height: 30, 145 | child: Row( 146 | children: [ 147 | const Spacer( 148 | flex: 2, 149 | ), 150 | Flexible( 151 | flex: 3, 152 | child: Row(children: [ 153 | const SizedBox(width: 80 - 20), 154 | ListenableBuilder( 155 | listenable: widget.swapData.target, 156 | builder: (context, child) { 157 | var isAllEnhance = true; 158 | var isAllNoEnhance = true; 159 | for (var item in widget.swapData.target.value) { 160 | if (item.enhance) { 161 | isAllNoEnhance = false; 162 | } else { 163 | isAllEnhance = false; 164 | } 165 | } 166 | return _buildThreeStateCheckBox( 167 | isAllEnhance 168 | ? true 169 | : (isAllNoEnhance ? false : null), () { 170 | if (isAllNoEnhance) { 171 | for (var item in widget.swapData.target.value) { 172 | item.enhance = true; 173 | } 174 | } else { 175 | for (var item in widget.swapData.target.value) { 176 | item.enhance = false; 177 | } 178 | } 179 | widget.swapData.target.notifyListeners(); 180 | }); 181 | }, 182 | ), 183 | Text( 184 | S.of(context).face_enhance, 185 | style: TextStyle( 186 | color: theme.colorScheme.onSecondaryContainer), 187 | ) 188 | ])), 189 | ], 190 | )), 191 | Expanded( 192 | child: ValueListenableBuilder( 193 | valueListenable: widget.swapData.source, 194 | builder: (context, _, child) => ValueListenableBuilder( 195 | valueListenable: widget.swapData.target, 196 | builder: (context, _, child) { 197 | var count = max(widget.swapData.source.value.length, 198 | widget.swapData.target.value.length); 199 | return Row(children: [ 200 | Flexible( 201 | flex: 2, 202 | child: ScrollConfiguration( 203 | behavior: ScrollConfiguration.of(context) 204 | .copyWith(scrollbars: false), 205 | child: ReorderableListView( 206 | proxyDecorator: (child, index, animation) { 207 | return Material( 208 | color: Colors.transparent, 209 | child: Container( 210 | padding: const EdgeInsets.all(2), 211 | decoration: BoxDecoration( 212 | borderRadius: 213 | BorderRadius.circular(10), 214 | color: theme.primaryColor), 215 | child: child, 216 | )); 217 | }, 218 | buildDefaultDragHandles: false, 219 | scrollController: 220 | _swapListScrollController_source, 221 | footer: SizedBox( 222 | height: 100.0 * 223 | (count - 224 | widget.swapData.source.value.length), 225 | ), 226 | onReorder: (int oldIndex, int newIndex) { 227 | if (newIndex > oldIndex) { 228 | newIndex--; 229 | } 230 | var old = widget.swapData.source.value 231 | .removeAt(oldIndex); 232 | widget.swapData.source.value 233 | .insert(newIndex, old); 234 | widget.swapData.source.notifyListeners(); 235 | }, 236 | children: [ 237 | for (var i = 0; 238 | i < widget.swapData.source.value.length; 239 | i++) 240 | ReorderableDragStartListener( 241 | index: i, 242 | key: ValueKey( 243 | widget.swapData.source.value[i].id), 244 | child: Container( 245 | height: 100, 246 | child: _buildFaceListItem( 247 | widget.swapData.source.value[i], 248 | () { 249 | widget.swapData.source.value 250 | .removeAt(i); 251 | widget.swapData.source 252 | .notifyListeners(); 253 | }), 254 | )) 255 | ], 256 | ))), 257 | SizedBox( 258 | width: 40, 259 | child: ScrollConfiguration( 260 | behavior: ScrollConfiguration.of(context) 261 | .copyWith(scrollbars: false), 262 | child: ListView.builder( 263 | controller: _swapListScrollController_arrow, 264 | itemCount: count, 265 | itemBuilder: (context, index) { 266 | return Container( 267 | height: 100, 268 | child: Column( 269 | mainAxisAlignment: 270 | MainAxisAlignment.center, 271 | children: [ 272 | Text("${index + 1}", 273 | style: TextStyle( 274 | color: theme 275 | .colorScheme.secondary)), 276 | Icon(Icons.arrow_forward, 277 | color: theme.colorScheme.secondary) 278 | ], 279 | ), 280 | ); 281 | }, 282 | ))), 283 | Flexible( 284 | flex: 3, 285 | child: ReorderableListView( 286 | padding: const EdgeInsets.only(right: 12), 287 | proxyDecorator: (child, index, animation) { 288 | return Material( 289 | color: Colors.transparent, 290 | child: Container( 291 | padding: const EdgeInsets.all(2), 292 | decoration: BoxDecoration( 293 | borderRadius: BorderRadius.circular(10), 294 | color: theme.primaryColor), 295 | child: child, 296 | )); 297 | }, 298 | buildDefaultDragHandles: false, 299 | scrollController: _swapListScrollController_target, 300 | footer: SizedBox( 301 | height: 100.0 * 302 | (count - widget.swapData.target.value.length), 303 | ), 304 | onReorder: (int oldIndex, int newIndex) { 305 | if (newIndex > oldIndex) { 306 | newIndex--; 307 | } 308 | var old = 309 | widget.swapData.target.value.removeAt(oldIndex); 310 | widget.swapData.target.value.insert(newIndex, old); 311 | widget.swapData.target.notifyListeners(); 312 | }, 313 | children: [ 314 | for (var i = 0; 315 | i < widget.swapData.target.value.length; 316 | i++) 317 | ReorderableDragStartListener( 318 | index: i, 319 | key: ValueKey( 320 | widget.swapData.target.value[i].id), 321 | child: SizedBox( 322 | height: 100, 323 | child: _buildFaceListItem( 324 | widget.swapData.target.value[i], () { 325 | widget.swapData.target.value.removeAt(i); 326 | widget.swapData.target.notifyListeners(); 327 | }, [ 328 | SizedOverflowBox( 329 | size: const Size(30, 30), 330 | child: Checkbox( 331 | onChanged: (v) { 332 | widget.swapData.target.value[i] 333 | .enhance = v!; 334 | widget.swapData.target 335 | .notifyListeners(); 336 | }, 337 | value: widget 338 | .swapData.target.value[i].enhance, 339 | ), 340 | ), 341 | ]), 342 | )) 343 | ], 344 | ), 345 | ), 346 | ]); 347 | }))) 348 | ]); 349 | } 350 | } 351 | 352 | class FaceCliper extends CustomClipper { 353 | final Rect rect; 354 | FaceCliper(this.rect); 355 | 356 | @override 357 | Rect getClip(Size size) { 358 | return Rect.fromLTWH(rect.left, rect.top, rect.width, rect.height); 359 | } 360 | 361 | @override 362 | bool shouldReclip(covariant CustomClipper oldClipper) { 363 | return false; 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | print("starting...",flush=True) 2 | 3 | import subprocess 4 | import sys 5 | import shutil 6 | import time 7 | import glob 8 | from http.server import BaseHTTPRequestHandler 9 | from http.server import ThreadingHTTPServer 10 | import os 11 | import json 12 | import traceback 13 | import roop.core 14 | import roop.face_analyser 15 | from roop.processors.frame import face_swapper 16 | import roop.globals 17 | import roop.predictor 18 | import threading 19 | import cv2 20 | import numpy as np 21 | from roop import utilities 22 | from tqdm import tqdm 23 | import onnxruntime 24 | 25 | enhancer=None 26 | def face_enhance(target_face,frame_img): 27 | global enhancer 28 | if not enhancer: 29 | from roop.processors.frame import face_enhancer 30 | enhancer=face_enhancer 31 | enhancer.pre_check() 32 | return enhancer.enhance_face(target_face,frame_img) 33 | 34 | def run_ffmpeg(args) -> bool: 35 | commands = ['ffmpeg', '-hide_banner', '-loglevel', roop.globals.log_level] 36 | commands.extend(args) 37 | print(' '.join(commands)) 38 | try: 39 | subprocess.check_output(commands) 40 | return True 41 | except Exception: 42 | pass 43 | return False 44 | 45 | def extract_frames(input_path: str,output_dir:str, fps: float = 30) -> bool: 46 | temp_frame_quality = roop.globals.temp_frame_quality * 31 // 100 47 | run_ffmpeg(['-hwaccel', 'auto', '-i', input_path, '-q:v', str(temp_frame_quality), '-pix_fmt', 'rgb24', '-vf', 'fps=' + str(fps), os.path.join(output_dir, '%04d.' + roop.globals.temp_frame_format)]) 48 | 49 | def create_gif(input_frames_dir,fps, output_path): 50 | input_frames_dir=os.path.join(input_frames_dir,"%04d."+roop.globals.temp_frame_format) 51 | return run_ffmpeg(['-hwaccel', 'auto', '-f', 'image2', '-r', str(fps), '-i', input_frames_dir,output_path]) 52 | 53 | def create_video(input_frames_dir, fps, output_path): 54 | output_dir = os.path.join(input_frames_dir, '%04d.' + roop.globals.temp_frame_format) 55 | output_video_quality = (roop.globals.output_video_quality + 1) * 51 // 100 56 | commands = ['-hwaccel', 'auto', 57 | '-r', str(fps), 58 | '-i', output_dir, 59 | '-vf', "pad=ceil(iw/2)*2:ceil(ih/2)*2", 60 | '-c:v', roop.globals.output_video_encoder] 61 | if roop.globals.output_video_encoder in ['libx264', 'libx265', 'libvpx']: 62 | commands.extend(['-crf', str(output_video_quality)]) 63 | if roop.globals.output_video_encoder in ['h264_nvenc', 'hevc_nvenc']: 64 | commands.extend(['-cq', str(output_video_quality)]) 65 | commands.extend(['-pix_fmt', 'yuv420p', '-vf', 'colorspace=bt709:iall=bt601-6-625:fast=1', '-y', output_path]) 66 | return run_ffmpeg(commands) 67 | 68 | def add_audio(video_path:str,audio_path: str, output_path: str) -> bool: 69 | return run_ffmpeg(['-hwaccel', 'auto', 70 | '-i', video_path, 71 | '-i', audio_path, 72 | '-c:v', 'copy', 73 | '-map', '0:v:0', 74 | '-map', '1:a:0', 75 | '-y', output_path]) 76 | 77 | def get_most_similar_face(faces, face_to_find,min_similarity): 78 | target_face_embedding = face_to_find.embedding 79 | max_similarity=-1000 80 | max_similarityFace=None 81 | norm_b = np.linalg.norm(target_face_embedding) 82 | # Compare faces 83 | for face in faces: 84 | # Calculate facial similarity 85 | similarity = np.dot(face.embedding, target_face_embedding) 86 | # Calculate vector norm 87 | norm_a = np.linalg.norm(face.embedding) 88 | # Calculate cosine similarity 89 | similarity = similarity / (norm_a * norm_b) 90 | if similarity>max_similarity: 91 | max_similarity=similarity 92 | max_similarityFace=face 93 | if max_similarity>=min_similarity: 94 | return max_similarityFace 95 | return None 96 | 97 | def clean_temp(dir): 98 | if not roop.globals.keep_frames: 99 | if os.path.exists(dir): 100 | shutil.rmtree(dir) 101 | parent_dir=os.path.dirname(dir) 102 | if os.path.exists(parent_dir) and not os.listdir(parent_dir): 103 | os.rmdir(parent_dir) 104 | 105 | class JsonCustomEncoder(json.JSONEncoder): 106 | def default(self, field): 107 | if isinstance(field, np.ndarray): 108 | return field.tolist() 109 | elif isinstance(field, np.floating): 110 | return field.tolist() 111 | elif isinstance(field, np.integer): 112 | return field.tolist() 113 | else: 114 | return json.JSONEncoder.default(self, field) 115 | 116 | def cv2imread2rgb(file_path): 117 | #return cv2.imread(file_path) 118 | return cv2.imdecode(np.fromfile(file_path,dtype=np.uint8),cv2.IMREAD_COLOR) 119 | 120 | def rgb2bgr(imgdata): 121 | #return imgdata 122 | return cv2.cvtColor(imgdata,cv2.COLOR_RGB2BGR) 123 | 124 | def cv2imwrite(output_path,imgdata): 125 | #return cv2.imwrite(output_path,imgdata) 126 | return cv2.imencode("."+os.path.splitext(output_path)[-1],imgdata)[1].tofile(output_path) 127 | 128 | def process_frames(source_face_infos,target_face_infos,frame_paths,output_dir, progress,min_similarity): 129 | for frame_path in frame_paths: 130 | frame = cv2imread2rgb(frame_path) 131 | try: 132 | result = process_one_frame(source_face_infos,target_face_infos, frame,min_similarity) 133 | cv2imwrite(os.path.join(output_dir,os.path.split(frame_path)[-1]), result) 134 | except Exception: 135 | print(f"process frame err:{traceback.format_exc()}") 136 | pass 137 | if progress: 138 | progress.update(1) 139 | 140 | def process_one_frame(source_face_infos,target_face_infos,frame,min_similarity): 141 | frame_faces=roop.face_analyser.get_many_faces(rgb2bgr(frame)) 142 | result=frame 143 | if frame_faces is not None and len(frame_faces)>0: 144 | min_len=min(len(source_face_infos),len(target_face_infos)) 145 | if min_len>0: 146 | ser=face_swapper.get_face_swapper() 147 | for i in range(min_len): 148 | face=get_most_similar_face(frame_faces,target_face_infos[i]["face"],min_similarity) 149 | if face: 150 | result = ser.get(result, face, source_face_infos[i]["face"], paste_back=True) 151 | if target_face_infos[i]["enhance"]==True: 152 | result=face_enhance(face,result) 153 | if len(target_face_infos)>min_len: 154 | for i in range(min_len,len(target_face_infos)): 155 | info=target_face_infos[i] 156 | if info["enhance"]==True: 157 | face=get_most_similar_face(frame_faces,info["face"],min_similarity) 158 | result=face_enhance(face,result) 159 | return result 160 | 161 | def multi_process_frame(source_face_infos,target_face_infos,frame_paths,output_dir, progress,min_similarity): 162 | threads = [] 163 | num_threads = roop.globals.execution_threads 164 | num_frames_per_thread = len(frame_paths) // num_threads 165 | remaining_frames = len(frame_paths) % num_threads 166 | 167 | # create thread and launch 168 | start_index = 0 169 | for _ in range(num_threads): 170 | end_index = start_index + num_frames_per_thread 171 | if remaining_frames > 0: 172 | end_index += 1 173 | remaining_frames -= 1 174 | thread_frame_paths = frame_paths[start_index:end_index] 175 | thread = threading.Thread(target=process_frames, args=(source_face_infos,target_face_infos,thread_frame_paths,output_dir, progress,min_similarity)) 176 | threads.append(thread) 177 | thread.start() 178 | start_index = end_index 179 | 180 | # threading 181 | for thread in threads: 182 | thread.join() 183 | 184 | def process_video(source_face_infos,target_face_infos,frame_paths,output_dir,min_similarity): 185 | do_multi = roop.globals.execution_threads > 1 186 | progress_bar_format = '{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]' 187 | with tqdm(total=len(frame_paths), desc="Processing", unit="frame", dynamic_ncols=True, bar_format=progress_bar_format) as progress: 188 | if do_multi: 189 | multi_process_frame(source_face_infos,target_face_infos,frame_paths,output_dir, progress,min_similarity) 190 | else: 191 | process_frames(source_face_infos,target_face_infos,frame_paths,output_dir, progress,min_similarity) 192 | 193 | class MyHTTPHandler(BaseHTTPRequestHandler): 194 | def do_POST(self): 195 | content_length = int(self.headers['Content-Length']) 196 | post_data = self.rfile.read(content_length).decode('utf-8') 197 | code=-1 198 | evalResult=None 199 | errMsg=None 200 | try: 201 | print("[Request]",post_data) 202 | evalResult=eval("self.func_"+post_data) 203 | code=0 204 | except Exception as err: 205 | traceback.print_exc() 206 | errMsg=str(err) 207 | code=-2 208 | self.send_response(200) 209 | self.send_header('Content-type', 'application/json;charset:utf-8') 210 | self.end_headers() 211 | result={ 212 | "code":code, 213 | "errMsg":errMsg, 214 | "result":evalResult 215 | } 216 | print("[Response]",result) 217 | resultStr=json.dumps(result,cls=JsonCustomEncoder) 218 | self.wfile.write(resultStr.encode('utf-8')) 219 | 220 | def func_get_faces(self,input_img_path): 221 | img=cv2imread2rgb(input_img_path) 222 | t=roop.face_analyser.get_many_faces(rgb2bgr(img)) 223 | faces=[] 224 | if t is not None: 225 | for item in t: 226 | faces.append(item['bbox']) 227 | return {'width':img.shape[1],'height':img.shape[0],'faces':faces} 228 | 229 | def func_swap_video(self,source_face_infos,target_face_infos,target_path,output_path,min_similarity): 230 | print("Checking nsfw...") 231 | if roop.predictor.predict_video(target_path): 232 | return 'nsfw' 233 | video_name_full = os.path.split(target_path)[-1] 234 | temp_output_dir_name=video_name_full.replace('.','_')+"_"+os.path.splitext(os.path.split(output_path)[-1])[0] 235 | temp_output_dir = os.path.join(os.path.dirname(target_path),"faceswap_temp",temp_output_dir_name) 236 | if os.path.exists(temp_output_dir): 237 | shutil.rmtree(temp_output_dir) 238 | os.makedirs(temp_output_dir,exist_ok=True) 239 | print("detecting video's FPS...") 240 | fps = utilities.detect_fps(target_path) 241 | if not roop.globals.keep_fps: 242 | fps=30 243 | print(f'Extracting frames with {fps} FPS...') 244 | original_frames_temp_dir=os.path.join(temp_output_dir,"original") 245 | if os.path.exists(original_frames_temp_dir): 246 | shutil.rmtree(original_frames_temp_dir) 247 | os.makedirs(original_frames_temp_dir,exist_ok=True) 248 | extract_frames(target_path,original_frames_temp_dir,fps) 249 | frame_paths = tuple(sorted( 250 | glob.glob(os.path.join(original_frames_temp_dir,"*."+roop.globals.temp_frame_format)), 251 | key=lambda x: int(os.path.splitext(os.path.split(x)[-1])[0]) 252 | )) 253 | 254 | source_cache={} 255 | for i in range(len(source_face_infos)): 256 | info=source_face_infos[i] 257 | if info["file"] in source_cache: 258 | source_img_faces=source_cache[info["file"]] 259 | else: 260 | source_img=rgb2bgr(cv2imread2rgb(info["file"])) 261 | source_img_faces=roop.face_analyser.get_many_faces(source_img) 262 | source_cache[info["file"]]=source_img_faces 263 | info["face"]=source_img_faces[info["face_index"]] 264 | target_cache={} 265 | for i in range(len(target_face_infos)): 266 | info=target_face_infos[i] 267 | if info["file"] in target_cache: 268 | target_img_faces=target_cache[info["file"]] 269 | else: 270 | target_img=rgb2bgr(cv2imread2rgb(info["file"])) 271 | target_img_faces=roop.face_analyser.get_many_faces(target_img) 272 | target_cache[info["file"]]=target_img_faces 273 | info["face"]=target_img_faces[info["face_index"]] 274 | frames_temp_dir = os.path.join(temp_output_dir,"swapped") 275 | os.makedirs(frames_temp_dir,exist_ok=True) 276 | process_video(source_face_infos,target_face_infos,frame_paths,frames_temp_dir,min_similarity) 277 | if video_name_full.endswith(".gif"): 278 | print("creating gif...") 279 | if not create_gif(frames_temp_dir,fps,output_path): 280 | clean_temp(temp_output_dir) 281 | return 'Create gif failed.' 282 | else: 283 | print("creating video...") 284 | if roop.globals.skip_audio: 285 | if not create_video(frames_temp_dir, fps, output_path): 286 | clean_temp(temp_output_dir) 287 | return 'Create video failed.' 288 | else: 289 | temp_output_path=os.path.join(temp_output_dir,"tempvideo.mp4") 290 | if not create_video(frames_temp_dir, fps, temp_output_path): 291 | if os.path.exists(temp_output_path): 292 | os.remove(temp_output_path) 293 | clean_temp(temp_output_dir) 294 | return 'Create video failed.' 295 | print("adding audio...") 296 | if not add_audio(temp_output_path, target_path, output_path): 297 | print("add audio failed") 298 | if os.path.exists(temp_output_path): 299 | os.remove(temp_output_path) 300 | clean_temp(temp_output_dir) 301 | return 'succ' 302 | 303 | def func_video_screenshot(self,duration,input_path,output_path): 304 | if run_ffmpeg(['-hwaccel','auto', '-ss' ,str(duration),'-i',input_path,'-r','1','-vframes','1','-an','-vcodec','mjpeg','-y',output_path]): 305 | return 'succ' 306 | return 'fail' 307 | 308 | def func_set_args( 309 | self, 310 | providers, 311 | execution_threads, 312 | keep_fps, 313 | keep_frames, 314 | skip_audio, 315 | temp_frame_format, 316 | temp_frame_quality, 317 | output_video_encoder, 318 | output_video_quality 319 | ): 320 | if ','.join(providers)!=','.join(roop.globals.execution_providers): 321 | if enhancer is not None: 322 | enhancer.clear_face_enhancer() 323 | roop.face_analyser.clear_face_analyser() 324 | face_swapper.clear_face_swapper() 325 | roop.globals.execution_providers = providers 326 | 327 | if execution_threads>0: 328 | roop.globals.execution_threads=execution_threads 329 | roop.globals.keep_fps=keep_fps 330 | roop.globals.keep_frames=keep_frames 331 | roop.globals.skip_audio=skip_audio 332 | roop.globals.temp_frame_format=temp_frame_format 333 | roop.globals.temp_frame_quality=temp_frame_quality 334 | roop.globals.output_video_encoder=output_video_encoder 335 | roop.globals.output_video_quality=output_video_quality 336 | 337 | def func_get_available_providers(self): 338 | return onnxruntime.get_available_providers() 339 | 340 | def func_swap_image(self,source_face_infos,target_face_infos,target_img_path,output_file): 341 | print("Checking nsfw...") 342 | if roop.predictor.predict_image(target_img_path): 343 | return 'nsfw' 344 | source_cache={} 345 | target_img = cv2imread2rgb(target_img_path) 346 | all_target_faces=roop.face_analyser.get_many_faces(rgb2bgr(target_img)) 347 | result=target_img 348 | if all_target_faces is not None and len(all_target_faces)>0: 349 | min_len=min(len(source_face_infos),len(target_face_infos)) 350 | if min_len>0: 351 | ser=face_swapper.get_face_swapper() 352 | for i in range(min_len): 353 | info=source_face_infos[i] 354 | if info["file"] in source_cache: 355 | source_img_faces=source_cache[info["file"]] 356 | else: 357 | source_img=rgb2bgr(cv2imread2rgb(info["file"])) 358 | source_img_faces=roop.face_analyser.get_many_faces(source_img) 359 | source_cache[info["file"]]=source_img_faces 360 | source_face=source_img_faces[info["face_index"]] 361 | target_face=all_target_faces[target_face_infos[i]["face_index"]] 362 | result = ser.get(result, target_face, source_face, paste_back=True) 363 | if target_face_infos[i]["enhance"]==True: 364 | result=face_enhance(target_face,result) 365 | if len(target_face_infos)>min_len: 366 | for i in range(min_len,len(target_face_infos)): 367 | info=target_face_infos[i] 368 | if info["enhance"]==True: 369 | target_face=all_target_faces[info["face_index"]] 370 | result=face_enhance(target_face,result) 371 | cv2imwrite(output_file, result) 372 | print("\n\nImage saved as:", output_file, "\n\n") 373 | return 'succ' 374 | 375 | 376 | def run(server_class=ThreadingHTTPServer, 377 | handler_class=MyHTTPHandler, 378 | port=0, 379 | bind='127.0.0.1'): 380 | with server_class((bind, port), handler_class) as httpd: 381 | if port==0: 382 | _,port=httpd.socket.getsockname() 383 | print(f"Serving HTTP on http://{bind}:{port}/") 384 | try: 385 | httpd.serve_forever() 386 | except KeyboardInterrupt: 387 | print("\nKeyboard interrupt received, exiting.") 388 | sys.exit(0) 389 | 390 | 391 | if __name__ == '__main__': 392 | if len(sys.argv)>1: 393 | port=int(sys.argv[1]) 394 | sys.argv.pop(0) 395 | else: 396 | port=53499 397 | roop.core.parse_args() 398 | #roop.globals.log_level='info' 399 | if not roop.core.pre_check(): 400 | exit(1) 401 | if not face_swapper.pre_check(): 402 | exit(1) 403 | roop.core.limit_resources() 404 | run(port=port) --------------------------------------------------------------------------------