From 0137e434085f7260d31af3387a75ce19bf40220b Mon Sep 17 00:00:00 2001 From: zsyg <3872006562@qq.com> Date: Sat, 28 Jun 2025 16:10:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A7=86=E9=A2=91=E5=8E=8B?= =?UTF-8?q?=E7=BC=A9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- others/C/video_compression/Makefile | 34 ++ others/C/video_compression/include/config.h | 65 ++++ others/C/video_compression/include/error.h | 37 ++ .../C/video_compression/include/file_utils.h | 27 ++ others/C/video_compression/include/gui.h | 16 + others/C/video_compression/include/logger.h | 38 +++ others/C/video_compression/include/progress.h | 21 ++ .../video_compression/include/string_utils.h | 61 ++++ others/C/video_compression/include/utils.h | 26 ++ .../include/video_compressor.h | 24 ++ others/C/video_compression/src/config.c | 127 +++++++ others/C/video_compression/src/error.c | 54 +++ others/C/video_compression/src/file_utils.c | 39 +++ others/C/video_compression/src/gui.c | 161 +++++++++ others/C/video_compression/src/logger.c | 61 ++++ others/C/video_compression/src/main.c | 26 ++ others/C/video_compression/src/string_utils.c | 120 +++++++ .../video_compression/src/video_compressor.c | 323 ++++++++++++++++++ 18 files changed, 1260 insertions(+) create mode 100644 others/C/video_compression/Makefile create mode 100644 others/C/video_compression/include/config.h create mode 100644 others/C/video_compression/include/error.h create mode 100644 others/C/video_compression/include/file_utils.h create mode 100644 others/C/video_compression/include/gui.h create mode 100644 others/C/video_compression/include/logger.h create mode 100644 others/C/video_compression/include/progress.h create mode 100644 others/C/video_compression/include/string_utils.h create mode 100644 others/C/video_compression/include/utils.h create mode 100644 others/C/video_compression/include/video_compressor.h create mode 100644 others/C/video_compression/src/config.c create mode 100644 others/C/video_compression/src/error.c create mode 100644 others/C/video_compression/src/file_utils.c create mode 100644 others/C/video_compression/src/gui.c create mode 100644 others/C/video_compression/src/logger.c create mode 100644 others/C/video_compression/src/main.c create mode 100644 others/C/video_compression/src/string_utils.c create mode 100644 others/C/video_compression/src/video_compressor.c diff --git a/others/C/video_compression/Makefile b/others/C/video_compression/Makefile new file mode 100644 index 0000000..0299519 --- /dev/null +++ b/others/C/video_compression/Makefile @@ -0,0 +1,34 @@ +# 编译器设置 +CC = gcc +CFLAGS = -Wall -Wextra -O2 -Iinclude +LDFLAGS = -lavcodec -lavformat -lavutil -lswscale -lgdi32 -lcomdlg32 -mwindows + +# 目录设置 +OBJ_DIR = obj +BUILD_DIR = build + +# 源文件和目标文件 +SRC = $(wildcard src/*.c) +OBJ = $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(SRC)) +EXEC = $(BUILD_DIR)/video_compressor.exe + +# 创建目录 +$(shell mkdir -p $(OBJ_DIR)) +$(shell mkdir -p $(BUILD_DIR)) + +# 默认目标 +all: $(EXEC) + +# 链接可执行文件 +$(EXEC): $(OBJ) + $(CC) -o $@ $^ $(LDFLAGS) + +# 编译规则 +$(OBJ_DIR)/%.o: src/%.c + $(CC) $(CFLAGS) -c $< -o $@ + +# 清理规则 +clean: + del /Q $(OBJ_DIR)\*.o $(EXEC) + +.PHONY: all clean diff --git a/others/C/video_compression/include/config.h b/others/C/video_compression/include/config.h new file mode 100644 index 0000000..2cdd634 --- /dev/null +++ b/others/C/video_compression/include/config.h @@ -0,0 +1,65 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +/** + * 加载配置文件 + * @param config_file 配置文件路径 + * @return true表示成功,false表示失败 + */ +bool config_load(const char* config_file); + +/** + * 保存配置文件 + * @param config_file 配置文件路径 + * @return true表示成功,false表示失败 + */ +bool config_save(const char* config_file); + +/** + * 获取字符串配置值 + * @param key 配置键 + * @param default_value 默认值 + * @return 配置值 + */ +const char* config_get_string(const char* key, const char* default_value); + +/** + * 获取整数配置值 + * @param key 配置键 + * @param default_value 默认值 + * @return 配置值 + */ +int config_get_int(const char* key, int default_value); + +/** + * 获取布尔配置值 + * @param key 配置键 + * @param default_value 默认值 + * @return 配置值 + */ +bool config_get_bool(const char* key, bool default_value); + +/** + * 设置字符串配置值 + * @param key 配置键 + * @param value 配置值 + */ +void config_set_string(const char* key, const char* value); + +/** + * 设置整数配置值 + * @param key 配置键 + * @param value 配置值 + */ +void config_set_int(const char* key, int value); + +/** + * 设置布尔配置值 + * @param key 配置键 + * @param value 配置值 + */ +void config_set_bool(const char* key, bool value); + +#endif // CONFIG_H diff --git a/others/C/video_compression/include/error.h b/others/C/video_compression/include/error.h new file mode 100644 index 0000000..4530f0a --- /dev/null +++ b/others/C/video_compression/include/error.h @@ -0,0 +1,37 @@ +#ifndef ERROR_H +#define ERROR_H + +typedef enum { + ERR_NONE = 0, + ERR_FILE_NOT_FOUND, + ERR_INVALID_ARGUMENT, + ERR_MEMORY_ALLOC, + ERR_FFMPEG, + ERR_UNKNOWN +} ErrorCode; + +/** + * 设置当前错误代码 + * @param code 错误代码 + * @param message 错误信息(可选) + */ +void error_set(ErrorCode code, const char* message); + +/** + * 获取当前错误代码 + * @return 错误代码 + */ +ErrorCode error_get_code(); + +/** + * 获取当前错误信息 + * @return 错误信息字符串 + */ +const char* error_get_message(); + +/** + * 清除错误状态 + */ +void error_clear(); + +#endif // ERROR_H diff --git a/others/C/video_compression/include/file_utils.h b/others/C/video_compression/include/file_utils.h new file mode 100644 index 0000000..1d45a03 --- /dev/null +++ b/others/C/video_compression/include/file_utils.h @@ -0,0 +1,27 @@ +#ifndef FILE_UTILS_H +#define FILE_UTILS_H + +#include + +/** + * 检查文件是否存在 + * @param path 文件路径 + * @return true表示存在,false表示不存在 + */ +bool file_exists(const char* path); + +/** + * 获取文件大小 + * @param path 文件路径 + * @return 文件大小(字节),-1表示错误 + */ +long file_size(const char* path); + +/** + * 获取文件扩展名 + * @param path 文件路径 + * @return 扩展名字符串(包含.),NULL表示没有扩展名 + */ +const char* file_extension(const char* path); + +#endif // FILE_UTILS_H diff --git a/others/C/video_compression/include/gui.h b/others/C/video_compression/include/gui.h new file mode 100644 index 0000000..558b488 --- /dev/null +++ b/others/C/video_compression/include/gui.h @@ -0,0 +1,16 @@ +#ifndef GUI_H +#define GUI_H + +#include + +// 初始化GUI界面 +BOOL init_gui(HINSTANCE hInstance); + +// 主窗口过程函数 +LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam); + +// 创建压缩参数设置控件 +void create_compression_controls(HWND hwnd); + +#endif // GUI_H diff --git a/others/C/video_compression/include/logger.h b/others/C/video_compression/include/logger.h new file mode 100644 index 0000000..58ada11 --- /dev/null +++ b/others/C/video_compression/include/logger.h @@ -0,0 +1,38 @@ +#ifndef LOGGER_H +#define LOGGER_H + +#include + +typedef enum { + LOG_DEBUG, + LOG_INFO, + LOG_WARNING, + LOG_ERROR +} LogLevel; + +/** + * 初始化日志系统 + * @param log_file 日志文件路径,NULL表示输出到stdout + */ +void logger_init(const char* log_file); + +/** + * 设置日志级别 + * @param level 日志级别 + */ +void logger_set_level(LogLevel level); + +/** + * 记录日志 + * @param level 日志级别 + * @param format 格式化字符串 + * @param ... 可变参数 + */ +void logger_log(LogLevel level, const char* format, ...); + +/** + * 关闭日志系统 + */ +void logger_close(); + +#endif // LOGGER_H diff --git a/others/C/video_compression/include/progress.h b/others/C/video_compression/include/progress.h new file mode 100644 index 0000000..097b298 --- /dev/null +++ b/others/C/video_compression/include/progress.h @@ -0,0 +1,21 @@ +#ifndef PROGRESS_H +#define PROGRESS_H + +/** + * 初始化进度显示 + * @param total 总工作量 + */ +void progress_init(long total); + +/** + * 更新进度 + * @param current 当前进度 + */ +void progress_update(long current); + +/** + * 完成进度显示 + */ +void progress_finish(); + +#endif // PROGRESS_H diff --git a/others/C/video_compression/include/string_utils.h b/others/C/video_compression/include/string_utils.h new file mode 100644 index 0000000..f267224 --- /dev/null +++ b/others/C/video_compression/include/string_utils.h @@ -0,0 +1,61 @@ +#ifndef STRING_UTILS_H +#define STRING_UTILS_H + +#include + +/** + * 安全的字符串拷贝 + * @param dest 目标缓冲区 + * @param src 源字符串 + * @param dest_size 目标缓冲区大小 + * @return 目标字符串 + */ +char* str_copy(char* dest, const char* src, size_t dest_size); + +/** + * 安全的字符串连接 + * @param dest 目标缓冲区 + * @param src 要连接的字符串 + * @param dest_size 目标缓冲区大小 + * @return 目标字符串 + */ +char* str_concat(char* dest, const char* src, size_t dest_size); + +/** + * 去除字符串两端的空白字符 + * @param str 要处理的字符串 + * @return 处理后的字符串 + */ +char* str_trim(char* str); + +/** + * 检查字符串是否以指定前缀开头 + * @param str 要检查的字符串 + * @param prefix 前缀 + * @return 1表示是,0表示否 + */ +int str_starts_with(const char* str, const char* prefix); + +/** + * 检查字符串是否以指定后缀结尾 + * @param str 要检查的字符串 + * @param suffix 后缀 + * @return 1表示是,0表示否 + */ +int str_ends_with(const char* str, const char* suffix); + +/** + * 将字符串转换为小写 + * @param str 要转换的字符串 + * @return 转换后的字符串 + */ +char* str_to_lower(char* str); + +/** + * 将字符串转换为大写 + * @param str 要转换的字符串 + * @return 转换后的字符串 + */ +char* str_to_upper(char* str); + +#endif // STRING_UTILS_H diff --git a/others/C/video_compression/include/utils.h b/others/C/video_compression/include/utils.h new file mode 100644 index 0000000..73e129d --- /dev/null +++ b/others/C/video_compression/include/utils.h @@ -0,0 +1,26 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +/** + * 获取当前时间戳(毫秒) + * @return 时间戳 + */ +long long get_timestamp(); + +/** + * 生成随机字符串 + * @param buffer 输出缓冲区 + * @param length 字符串长度 + */ +void generate_random_string(char* buffer, int length); + +/** + * 检查指针是否有效 + * @param ptr 要检查的指针 + * @return true表示有效,false表示无效 + */ +bool is_pointer_valid(const void* ptr); + +#endif // UTILS_H diff --git a/others/C/video_compression/include/video_compressor.h b/others/C/video_compression/include/video_compressor.h new file mode 100644 index 0000000..3bb6851 --- /dev/null +++ b/others/C/video_compression/include/video_compressor.h @@ -0,0 +1,24 @@ +#ifndef VIDEO_COMPRESSOR_H +#define VIDEO_COMPRESSOR_H + +/** + * 压缩视频文件 + * @param input_file 输入文件路径 + * @param output_file 输出文件路径 + * @param quality 压缩质量(1-10000) + * @return 0表示成功,非0表示失败 + */ +int compress_video(const char* input_file, const char* output_file, int quality); + +/** + * 初始化视频压缩模块 + * @return 0表示成功,非0表示失败 + */ +int init_video_compressor(); + +/** + * 清理视频压缩模块资源 + */ +void cleanup_video_compressor(); + +#endif // VIDEO_COMPRESSOR_H diff --git a/others/C/video_compression/src/config.c b/others/C/video_compression/src/config.c new file mode 100644 index 0000000..f683c50 --- /dev/null +++ b/others/C/video_compression/src/config.c @@ -0,0 +1,127 @@ +#include "config.h" +#include "string_utils.h" +#include +#include +#include + +#define MAX_CONFIG_SIZE 1024 + +typedef struct ConfigEntry { + char* key; + char* value; + struct ConfigEntry* next; +} ConfigEntry; + +static ConfigEntry* config_list = NULL; + +static ConfigEntry* config_find_entry(const char* key) { + ConfigEntry* entry = config_list; + while (entry != NULL) { + if (strcmp(entry->key, key) == 0) { + return entry; + } + entry = entry->next; + } + return NULL; +} + +bool config_load(const char* config_file) { + FILE* file = fopen(config_file, "r"); + if (file == NULL) { + return false; + } + + char line[MAX_CONFIG_SIZE]; + while (fgets(line, sizeof(line), file) != NULL) { + // 去除注释和空白行 + char* comment = strchr(line, '#'); + if (comment != NULL) { + *comment = '\0'; + } + + str_trim(line); + if (strlen(line) == 0) { + continue; + } + + // 解析键值对 + char* separator = strchr(line, '='); + if (separator == NULL) { + continue; + } + + *separator = '\0'; + char* key = str_trim(line); + char* value = str_trim(separator + 1); + + // 添加到配置列表 + config_set_string(key, value); + } + + fclose(file); + return true; +} + +bool config_save(const char* config_file) { + FILE* file = fopen(config_file, "w"); + if (file == NULL) { + return false; + } + + ConfigEntry* entry = config_list; + while (entry != NULL) { + fprintf(file, "%s=%s\n", entry->key, entry->value); + entry = entry->next; + } + + fclose(file); + return true; +} + +const char* config_get_string(const char* key, const char* default_value) { + ConfigEntry* entry = config_find_entry(key); + if (entry != NULL) { + return entry->value; + } + return default_value; +} + +int config_get_int(const char* key, int default_value) { + const char* str = config_get_string(key, NULL); + if (str != NULL) { + return atoi(str); + } + return default_value; +} + +bool config_get_bool(const char* key, bool default_value) { + const char* str = config_get_string(key, NULL); + if (str != NULL) { + return strcmp(str, "true") == 0 || strcmp(str, "1") == 0; + } + return default_value; +} + +void config_set_string(const char* key, const char* value) { + ConfigEntry* entry = config_find_entry(key); + if (entry != NULL) { + free(entry->value); + entry->value = strdup(value); + } else { + entry = malloc(sizeof(ConfigEntry)); + entry->key = strdup(key); + entry->value = strdup(value); + entry->next = config_list; + config_list = entry; + } +} + +void config_set_int(const char* key, int value) { + char str[32]; + snprintf(str, sizeof(str), "%d", value); + config_set_string(key, str); +} + +void config_set_bool(const char* key, bool value) { + config_set_string(key, value ? "true" : "false"); +} diff --git a/others/C/video_compression/src/error.c b/others/C/video_compression/src/error.c new file mode 100644 index 0000000..115848d --- /dev/null +++ b/others/C/video_compression/src/error.c @@ -0,0 +1,54 @@ +#include "error.h" +#include "logger.h" +#include +#include + +static ErrorCode current_error = ERR_NONE; +static char* error_message = NULL; + +void error_set(ErrorCode code, const char* message) { + current_error = code; + + if (error_message != NULL) { + free(error_message); + error_message = NULL; + } + + if (message != NULL) { + error_message = strdup(message); + } + + // 记录错误日志 + if (code != ERR_NONE) { + const char* error_name; + switch (code) { + case ERR_FILE_NOT_FOUND: error_name = "File not found"; break; + case ERR_INVALID_ARGUMENT: error_name = "Invalid argument"; break; + case ERR_MEMORY_ALLOC: error_name = "Memory allocation failed"; break; + case ERR_FFMPEG: error_name = "FFmpeg error"; break; + default: error_name = "Unknown error"; break; + } + + if (message != NULL) { + logger_log(LOG_ERROR, "%s: %s", error_name, message); + } else { + logger_log(LOG_ERROR, "%s", error_name); + } + } +} + +ErrorCode error_get_code() { + return current_error; +} + +const char* error_get_message() { + return error_message; +} + +void error_clear() { + current_error = ERR_NONE; + if (error_message != NULL) { + free(error_message); + error_message = NULL; + } +} diff --git a/others/C/video_compression/src/file_utils.c b/others/C/video_compression/src/file_utils.c new file mode 100644 index 0000000..5352815 --- /dev/null +++ b/others/C/video_compression/src/file_utils.c @@ -0,0 +1,39 @@ +#include "file_utils.h" +#include +#include +#include + +bool file_exists(const char* path) { + if (path == NULL) { + return false; + } + + struct stat buffer; + return stat(path, &buffer) == 0; +} + +long file_size(const char* path) { + if (path == NULL) { + return -1; + } + + struct stat buffer; + if (stat(path, &buffer) != 0) { + return -1; + } + + return (long)buffer.st_size; +} + +const char* file_extension(const char* path) { + if (path == NULL) { + return NULL; + } + + const char* dot = strrchr(path, '.'); + if (dot == NULL || dot == path) { + return NULL; + } + + return dot; +} diff --git a/others/C/video_compression/src/gui.c b/others/C/video_compression/src/gui.c new file mode 100644 index 0000000..1a92ff1 --- /dev/null +++ b/others/C/video_compression/src/gui.c @@ -0,0 +1,161 @@ +#include "gui.h" +#include "video_compressor.h" +#include "string_utils.h" +#include +#include + +#define ID_COMPRESS_BUTTON 101 +#define ID_QUALITY_SLIDER 102 +#define ID_INPUT_FILE_EDIT 103 +#define ID_OUTPUT_FILE_EDIT 104 +#define ID_BROWSE_INPUT_BUTTON 105 +#define ID_BROWSE_OUTPUT_BUTTON 106 + +// 全局变量 +static HWND g_hwndQualitySlider; +static HWND g_hwndInputFileEdit; +static HWND g_hwndOutputFileEdit; +static HFONT g_hFont = NULL; + +// 设置中文字体 +static void SetChineseFont(HWND hwnd) { + if (g_hFont == NULL) { + g_hFont = CreateFontW( + 16, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, + GB2312_CHARSET, + OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, + DEFAULT_PITCH | FF_SWISS, + L"Microsoft YaHei" + ); + } + if (g_hFont) { + SendMessageW(hwnd, WM_SETFONT, (WPARAM)g_hFont, TRUE); + } +} + +BOOL init_gui(HINSTANCE hInstance) { + // 注册窗口类 + WNDCLASSEXW wc = {0}; + wc.cbSize = sizeof(WNDCLASSEXW); + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = MainWndProc; + wc.hInstance = hInstance; + wc.hCursor = LoadCursorW(NULL, MAKEINTRESOURCEW(IDC_ARROW)); + wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wc.lpszClassName = L"VideoCompressorClass"; + + if (!RegisterClassExW(&wc)) { + return FALSE; + } + + // 创建主窗口 + HWND hwnd = CreateWindowExW(0, wc.lpszClassName, L"视频压缩工具", + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, + 500, 300, NULL, NULL, hInstance, NULL); + if (!hwnd) { + return FALSE; + } + + // 创建控件 + create_compression_controls(hwnd); + + ShowWindow(hwnd, SW_SHOW); + UpdateWindow(hwnd); + return TRUE; +} + +void create_compression_controls(HWND hwnd) { + // 设置字体 + SetChineseFont(hwnd); + + // 创建输入文件选择控件 + CreateWindowW(L"STATIC", L"输入文件:", WS_CHILD | WS_VISIBLE, + 20, 20, 80, 20, hwnd, NULL, NULL, NULL); + + g_hwndInputFileEdit = CreateWindowW(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER, + 110, 20, 250, 20, hwnd, (HMENU)ID_INPUT_FILE_EDIT, NULL, NULL); + CreateWindowW(L"BUTTON", L"浏览...", WS_CHILD | WS_VISIBLE, + 370, 20, 60, 20, hwnd, (HMENU)ID_BROWSE_INPUT_BUTTON, NULL, NULL); + + // 创建输出文件选择控件 + CreateWindowW(L"STATIC", L"输出文件:", WS_CHILD | WS_VISIBLE, + 20, 50, 80, 20, hwnd, NULL, NULL, NULL); + + g_hwndOutputFileEdit = CreateWindowW(L"EDIT", L"", WS_CHILD | WS_VISIBLE | WS_BORDER, + 110, 50, 250, 20, hwnd, (HMENU)ID_OUTPUT_FILE_EDIT, NULL, NULL); + CreateWindowW(L"BUTTON", L"浏览...", WS_CHILD | WS_VISIBLE, + 370, 50, 60, 20, hwnd, (HMENU)ID_BROWSE_OUTPUT_BUTTON, NULL, NULL); + + // 创建质量滑块 + CreateWindowW(L"STATIC", L"压缩质量(1-10000):", WS_CHILD | WS_VISIBLE, + 20, 80, 150, 20, hwnd, NULL, NULL, NULL); + + g_hwndQualitySlider = CreateWindowW(TRACKBAR_CLASSW, L"", WS_CHILD | WS_VISIBLE | TBS_AUTOTICKS, + 20, 100, 400, 30, hwnd, (HMENU)ID_QUALITY_SLIDER, NULL, NULL); + + SendMessageW(g_hwndQualitySlider, TBM_SETRANGE, TRUE, MAKELONG(1, 10000)); + SendMessageW(g_hwndQualitySlider, TBM_SETPOS, TRUE, 5000); + + // 创建压缩按钮 + CreateWindowW(L"BUTTON", L"开始压缩", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, + 180, 150, 100, 30, hwnd, (HMENU)ID_COMPRESS_BUTTON, NULL, NULL); +} + +LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_COMMAND: + if (LOWORD(wParam) == ID_COMPRESS_BUTTON) { + // 处理压缩按钮点击事件 + int quality = (int)SendMessageW(g_hwndQualitySlider, TBM_GETPOS, 0, 0); + + wchar_t inputFile[MAX_PATH]; + GetWindowTextW(g_hwndInputFileEdit, inputFile, MAX_PATH); + + wchar_t outputFile[MAX_PATH]; + GetWindowTextW(g_hwndOutputFileEdit, outputFile, MAX_PATH); + + // 转换为UTF-8 + char inputFileUtf8[MAX_PATH * 4]; + char outputFileUtf8[MAX_PATH * 4]; + WideCharToMultiByte(CP_UTF8, 0, inputFile, -1, inputFileUtf8, sizeof(inputFileUtf8), NULL, NULL); + WideCharToMultiByte(CP_UTF8, 0, outputFile, -1, outputFileUtf8, sizeof(outputFileUtf8), NULL, NULL); + + // 调用压缩函数 + compress_video(inputFileUtf8, outputFileUtf8, quality); + return 0; + } else if (LOWORD(wParam) == ID_BROWSE_INPUT_BUTTON || LOWORD(wParam) == ID_BROWSE_OUTPUT_BUTTON) { + // 文件选择对话框 + OPENFILENAMEW ofn; + wchar_t szFile[MAX_PATH] = L""; + + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = hwnd; + ofn.lpstrFile = szFile; + ofn.nMaxFile = sizeof(szFile)/sizeof(szFile[0]); + ofn.lpstrFilter = L"视频文件\0*.mp4;*.avi;*.mkv;*.mov\0所有文件\0*.*\0"; + ofn.nFilterIndex = 1; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + + if (GetOpenFileNameW(&ofn)) { + HWND targetEdit = (LOWORD(wParam) == ID_BROWSE_INPUT_BUTTON) ? + g_hwndInputFileEdit : g_hwndOutputFileEdit; + SetWindowTextW(targetEdit, szFile); + } + return 0; + } + break; + + case WM_DESTROY: + if (g_hFont) { + DeleteObject(g_hFont); + g_hFont = NULL; + } + PostQuitMessage(0); + return 0; + } + + return DefWindowProcW(hwnd, msg, wParam, lParam); +} diff --git a/others/C/video_compression/src/logger.c b/others/C/video_compression/src/logger.c new file mode 100644 index 0000000..9dd86cd --- /dev/null +++ b/others/C/video_compression/src/logger.c @@ -0,0 +1,61 @@ +#include "logger.h" +#include +#include +#include +#include + +static FILE* log_file = NULL; +static LogLevel current_level = LOG_INFO; + +void logger_init(const char* log_file_path) { + if (log_file_path != NULL) { + log_file = fopen(log_file_path, "a"); + } else { + log_file = stdout; + } +} + +void logger_set_level(LogLevel level) { + current_level = level; +} + +void logger_log(LogLevel level, const char* format, ...) { + if (log_file == NULL || level < current_level) { + return; + } + + // 获取当前时间 + time_t now; + time(&now); + char time_str[20]; + strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", localtime(&now)); + + // 日志级别字符串 + const char* level_str; + switch (level) { + case LOG_DEBUG: level_str = "DEBUG"; break; + case LOG_INFO: level_str = "INFO"; break; + case LOG_WARNING: level_str = "WARNING"; break; + case LOG_ERROR: level_str = "ERROR"; break; + default: level_str = "UNKNOWN"; break; + } + + // 写入时间戳和日志级别 + fprintf(log_file, "[%s] [%s] ", time_str, level_str); + + // 写入日志内容 + va_list args; + va_start(args, format); + vfprintf(log_file, format, args); + va_end(args); + + fprintf(log_file, "\n"); + fflush(log_file); +} + +void logger_close() { + if (log_file != NULL && log_file != stdout) { + fclose(log_file); + } + log_file = NULL; +} diff --git a/others/C/video_compression/src/main.c b/others/C/video_compression/src/main.c new file mode 100644 index 0000000..0ebbe43 --- /dev/null +++ b/others/C/video_compression/src/main.c @@ -0,0 +1,26 @@ +#include +#include "video_compressor.h" +#include "gui.h" + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) +{ + // 抑制未使用参数警告 + (void)hPrevInstance; + (void)lpCmdLine; + (void)nCmdShow; + + // 初始化GUI + if (!init_gui(hInstance)) { + return 1; + } + + // 主消息循环 + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return (int)msg.wParam; +} diff --git a/others/C/video_compression/src/string_utils.c b/others/C/video_compression/src/string_utils.c new file mode 100644 index 0000000..463d8da --- /dev/null +++ b/others/C/video_compression/src/string_utils.c @@ -0,0 +1,120 @@ +#include "string_utils.h" +#include +#include +#include + +char* str_copy(char* dest, const char* src, size_t dest_size) { + if (dest == NULL || src == NULL || dest_size == 0) { + return NULL; + } + + size_t i; + for (i = 0; i < dest_size - 1 && src[i] != '\0'; i++) { + dest[i] = src[i]; + } + dest[i] = '\0'; + + return dest; +} + +char* str_concat(char* dest, const char* src, size_t dest_size) { + if (dest == NULL || src == NULL || dest_size == 0) { + return NULL; + } + + size_t dest_len = strlen(dest); + if (dest_len >= dest_size) { + return dest; + } + + size_t i; + for (i = 0; i < dest_size - dest_len - 1 && src[i] != '\0'; i++) { + dest[dest_len + i] = src[i]; + } + dest[dest_len + i] = '\0'; + + return dest; +} + +char* str_trim(char* str) { + if (str == NULL) { + return NULL; + } + + char* end; + + // 去除前导空白字符 + while (isspace((unsigned char)*str)) { + str++; + } + + // 如果全是空白字符 + if (*str == '\0') { + return str; + } + + // 去除尾部空白字符 + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) { + end--; + } + + // 写入终止符 + *(end + 1) = '\0'; + + return str; +} + +int str_starts_with(const char* str, const char* prefix) { + if (str == NULL || prefix == NULL) { + return 0; + } + + size_t len_str = strlen(str); + size_t len_prefix = strlen(prefix); + + if (len_prefix > len_str) { + return 0; + } + + return strncmp(str, prefix, len_prefix) == 0; +} + +int str_ends_with(const char* str, const char* suffix) { + if (str == NULL || suffix == NULL) { + return 0; + } + + size_t len_str = strlen(str); + size_t len_suffix = strlen(suffix); + + if (len_suffix > len_str) { + return 0; + } + + return strncmp(str + len_str - len_suffix, suffix, len_suffix) == 0; +} + +char* str_to_lower(char* str) { + if (str == NULL) { + return NULL; + } + + for (char* p = str; *p != '\0'; p++) { + *p = tolower((unsigned char)*p); + } + + return str; +} + +char* str_to_upper(char* str) { + if (str == NULL) { + return NULL; + } + + for (char* p = str; *p != '\0'; p++) { + *p = toupper((unsigned char)*p); + } + + return str; +} diff --git a/others/C/video_compression/src/video_compressor.c b/others/C/video_compression/src/video_compressor.c new file mode 100644 index 0000000..48e80fc --- /dev/null +++ b/others/C/video_compression/src/video_compressor.c @@ -0,0 +1,323 @@ +#include "video_compressor.h" +#include "string_utils.h" +#include +#include +#include +#include +#include +#include // For mkdir() +#include // For errno + +// 将质量参数(1-10000)转换为FFmpeg的CRF值(0-51) +static int convert_quality_to_crf(int quality) { + // 更激进的映射关系,确保中等质量也能强力压缩 + if (quality < 3000) return 45 - (quality * 15) / 3000; // 低质量区间(30-45) + if (quality < 7000) return 30 - ((quality-3000) * 10) / 4000; // 中等质量区间(20-30) + return 20 - ((quality-7000) * 2) / 3000; // 高质量区间(18-20) +} + +int compress_video(const char* input_file, const char* output_file, int quality) { + AVFormatContext *input_ctx = NULL; + AVFormatContext *output_ctx = NULL; + AVCodecContext *encoder_ctx = NULL; + const AVCodec *encoder = NULL; + AVPacket *packet = NULL; + int ret = 0; + int video_stream_index = -1; + int crf = convert_quality_to_crf(quality); + + // 打开输入文件 + if (avformat_open_input(&input_ctx, input_file, NULL, NULL) < 0) { + fprintf(stderr, "无法打开输入文件\n"); + ret = -1; + goto cleanup; + } + + // 获取流信息 + if (avformat_find_stream_info(input_ctx, NULL) < 0) { + fprintf(stderr, "无法获取流信息\n"); + ret = -1; + goto cleanup; + } + + // 查找视频流 + for (unsigned int i = 0; i < input_ctx->nb_streams; i++) { + if (input_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + video_stream_index = i; + break; + } + } + + if (video_stream_index == -1) { + fprintf(stderr, "未找到视频流\n"); + ret = -1; + goto cleanup; + } + + // 创建输出上下文 + if (avformat_alloc_output_context2(&output_ctx, NULL, NULL, output_file) < 0) { + fprintf(stderr, "无法创建输出上下文\n"); + ret = -1; + goto cleanup; + } + + // 打开输出文件 + if (!(output_ctx->oformat->flags & AVFMT_NOFILE)) { + if (avio_open(&output_ctx->pb, output_file, AVIO_FLAG_WRITE) < 0) { + fprintf(stderr, "无法打开输出文件\n"); + ret = -1; + goto cleanup; + } + } + + // 配置编码器 + encoder = avcodec_find_encoder(AV_CODEC_ID_H264); + if (!encoder) { + fprintf(stderr, "未找到H.264编码器\n"); + ret = -1; + goto cleanup; + } + + encoder_ctx = avcodec_alloc_context3(encoder); + if (!encoder_ctx) { + fprintf(stderr, "无法分配编码器上下文\n"); + ret = -1; + goto cleanup; + } + + // 设置更激进的编码参数 + int64_t input_bit_rate = input_ctx->bit_rate; + if (input_bit_rate <= 0) { + input_bit_rate = 400000; // 默认值 + } + // 设置严格的编码参数确保播放兼容性 + encoder_ctx->bit_rate = input_bit_rate * 0.5; + encoder_ctx->rc_max_rate = input_bit_rate * 0.7; + encoder_ctx->rc_min_rate = input_bit_rate * 0.3; + encoder_ctx->rc_buffer_size = input_bit_rate * 0.7 / 25; + encoder_ctx->level = 31; // 明确设置level 3.1确保广泛兼容 + encoder_ctx->width = input_ctx->streams[video_stream_index]->codecpar->width; + encoder_ctx->height = input_ctx->streams[video_stream_index]->codecpar->height; + encoder_ctx->time_base = (AVRational){1, 25}; + encoder_ctx->framerate = (AVRational){25, 1}; + encoder_ctx->gop_size = 10; + encoder_ctx->max_b_frames = 1; + encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P; + + // 设置更激进的x264参数 + av_opt_set_int(encoder_ctx->priv_data, "crf", crf, 0); + av_opt_set(encoder_ctx->priv_data, "preset", "slow", 0); // 使用slow预设提高压缩率 + // 设置强制兼容性参数 + av_opt_set(encoder_ctx->priv_data, "profile", "main", 0); + av_opt_set(encoder_ctx->priv_data, "movflags", "+faststart", 0); // 添加流式播放支持 + av_opt_set(encoder_ctx->priv_data, "tune", "zerolatency", 0); + av_opt_set(encoder_ctx->priv_data, "x264-params", "ref=1:bframes=0:subme=2:me=dia:analyse=none:trellis=0", 0); + encoder_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + encoder_ctx->thread_count = 4; // 启用4线程编码 + encoder_ctx->thread_type = FF_THREAD_FRAME; // 帧级多线程 + encoder_ctx->gop_size = 250; // 更大的GOP提高压缩率 + encoder_ctx->max_b_frames = 0; // 禁用B帧提高解码兼容性 + + // 打开编码器 + if (avcodec_open2(encoder_ctx, encoder, NULL) < 0) { + fprintf(stderr, "无法打开编码器\n"); + ret = -1; + goto cleanup; + } + + // 添加视频流到输出文件 + AVStream *out_stream = avformat_new_stream(output_ctx, NULL); + if (!out_stream) { + fprintf(stderr, "无法创建输出流\n"); + ret = -1; + goto cleanup; + } + + // 复制编码器参数到输出流 + if (avcodec_parameters_from_context(out_stream->codecpar, encoder_ctx) < 0) { + fprintf(stderr, "无法复制编码器参数\n"); + ret = -1; + goto cleanup; + } + + // 写入文件头 + if (avformat_write_header(output_ctx, NULL) < 0) { + fprintf(stderr, "写入文件头失败\n"); + ret = -1; + goto cleanup; + } + + packet = av_packet_alloc(); + if (!packet) { + fprintf(stderr, "无法分配AVPacket\n"); + ret = -1; + goto cleanup; + } + + // 初始化解码器 + const AVCodec *decoder = avcodec_find_decoder(input_ctx->streams[video_stream_index]->codecpar->codec_id); + if (!decoder) { + fprintf(stderr, "未找到解码器\n"); + ret = -1; + goto cleanup; + } + + AVCodecContext *decoder_ctx = avcodec_alloc_context3(decoder); + if (!decoder_ctx) { + fprintf(stderr, "无法分配解码器上下文\n"); + ret = -1; + goto cleanup; + } + + if (avcodec_parameters_to_context(decoder_ctx, input_ctx->streams[video_stream_index]->codecpar) < 0) { + fprintf(stderr, "无法复制解码器参数\n"); + ret = -1; + goto cleanup; + } + + if (avcodec_open2(decoder_ctx, decoder, NULL) < 0) { + fprintf(stderr, "无法打开解码器\n"); + ret = -1; + goto cleanup; + } + + // 初始化帧和转换上下文 + AVFrame *frame = av_frame_alloc(); + AVFrame *tmp_frame = av_frame_alloc(); + if (!frame || !tmp_frame) { + fprintf(stderr, "无法分配帧\n"); + ret = -1; + goto cleanup; + } + + // 分配输入帧缓冲区 + frame->format = decoder_ctx->pix_fmt; + frame->width = decoder_ctx->width; + frame->height = decoder_ctx->height; + if (av_frame_get_buffer(frame, 32) < 0) { + fprintf(stderr, "无法分配输入帧缓冲区\n"); + ret = -1; + goto cleanup; + } + + // 分配输出帧缓冲区 + tmp_frame->format = encoder_ctx->pix_fmt; + tmp_frame->width = encoder_ctx->width; + tmp_frame->height = encoder_ctx->height; + if (av_frame_get_buffer(tmp_frame, 32) < 0) { + fprintf(stderr, "无法分配输出帧缓冲区\n"); + ret = -1; + goto cleanup; + } + + struct SwsContext *sws_ctx = NULL; + sws_ctx = sws_getContext( + decoder_ctx->width, decoder_ctx->height, decoder_ctx->pix_fmt, + encoder_ctx->width, encoder_ctx->height, encoder_ctx->pix_fmt, + SWS_BILINEAR, NULL, NULL, NULL); + if (!sws_ctx) { + fprintf(stderr, "无法创建图像转换上下文\n"); + ret = -1; + goto cleanup; + } + + // 彻底解决目录权限问题 + if (mkdir("build") != 0 && errno != EEXIST) { + fprintf(stderr, "错误:无法创建build目录(errno=%d),请手动创建并设置写权限\n", errno); + ret = -1; + goto cleanup; + } + + // 验证目录可写权限 + FILE *test_file = fopen("build/test_permission.tmp", "w"); + if (!test_file) { + fprintf(stderr, "致命错误:无法写入build目录(errno=%d),请确保:\n1. 目录存在且有写权限\n2. 没有其他程序锁定该目录\n3. 磁盘空间充足\n", errno); + ret = -1; + goto cleanup; + } + fclose(test_file); + remove("build/test_permission.tmp"); + + // 解码和编码循环 + while (av_read_frame(input_ctx, packet) >= 0) { + if (packet->stream_index == video_stream_index) { + // 发送包到解码器 + if (avcodec_send_packet(decoder_ctx, packet) < 0) { + fprintf(stderr, "解码错误\n"); + continue; + } + + // 接收解码后的帧 + while (avcodec_receive_frame(decoder_ctx, frame) >= 0) { + // 转换像素格式 + sws_scale(sws_ctx, (const uint8_t * const*)frame->data, + frame->linesize, 0, decoder_ctx->height, + tmp_frame->data, tmp_frame->linesize); + + // 设置帧参数 + tmp_frame->format = encoder_ctx->pix_fmt; + tmp_frame->width = encoder_ctx->width; + tmp_frame->height = encoder_ctx->height; + tmp_frame->pts = frame->pts; + + // 发送帧到编码器 + if (avcodec_send_frame(encoder_ctx, tmp_frame) < 0) { + fprintf(stderr, "编码错误\n"); + break; + } + + // 接收编码后的包 + while (avcodec_receive_packet(encoder_ctx, packet) >= 0) { + // 写入输出文件 + if (av_interleaved_write_frame(output_ctx, packet) < 0) { + fprintf(stderr, "写入帧失败\n"); + av_packet_unref(packet); + ret = -1; + goto cleanup; + } + av_packet_unref(packet); + } + } + } + av_packet_unref(packet); + } + + // 刷新编码器 + avcodec_send_frame(encoder_ctx, NULL); + while (avcodec_receive_packet(encoder_ctx, packet) >= 0) { + if (av_interleaved_write_frame(output_ctx, packet) < 0) { + fprintf(stderr, "写入帧失败\n"); + av_packet_unref(packet); + ret = -1; + goto cleanup; + } + av_packet_unref(packet); + } + + // 写入文件尾 + av_write_trailer(output_ctx); + +cleanup: + if (packet) av_packet_free(&packet); + if (frame) av_frame_free(&frame); + if (tmp_frame) av_frame_free(&tmp_frame); + if (sws_ctx) sws_freeContext(sws_ctx); + if (decoder_ctx) avcodec_free_context(&decoder_ctx); + if (input_ctx) avformat_close_input(&input_ctx); + if (output_ctx && !(output_ctx->oformat->flags & AVFMT_NOFILE)) { + avio_closep(&output_ctx->pb); + } + if (output_ctx) avformat_free_context(output_ctx); + if (encoder_ctx) avcodec_free_context(&encoder_ctx); + + return ret; +} + +int init_video_compressor() { + // 新版本FFmpeg不需要显式初始化 + return 0; +} + +void cleanup_video_compressor() { + // 当前无需特殊清理 +}