C语言文件操作实现字幕SRT格式的本地读写与解析字幕文件尤其是SRT格式是视频内容不可或缺的一部分。无论是制作个人视频、学习外语还是进行多媒体开发掌握如何用程序处理字幕文件都是一项非常实用的技能。对于C语言开发者来说这不仅是学习文件操作的好机会更是深入理解数据格式解析的绝佳实践。今天我们就从零开始用C语言的标准库一步步构建一个能够读写、解析SRT字幕文件的工具。整个过程不依赖任何第三方库纯粹用C语言实现让你彻底搞懂字幕文件背后的逻辑。即使你是C语言新手跟着步骤走也能轻松上手。1. 环境准备与目标设定在开始敲代码之前我们先明确两件事需要准备什么以及最终要实现什么。首先你只需要一个能运行C语言的环境。无论是Windows上的Dev-C、Code::Blocks还是Linux/macOS上的GCC都可以。确保你的编译器支持C99或以上标准因为我们会用到一些现代C语言的便利特性。我们的核心目标是处理SRT文件。一个典型的SRT文件片段长这样1 00:00:01,000 -- 00:00:04,000 大家好欢迎来到这个C语言教程。 2 00:00:05,000 -- 00:00:08,000 今天我们将学习如何操作文件。可以看到一个完整的字幕条目包含三部分序号一个简单的数字从1开始递增。时间轴格式为时:分:秒,毫秒 -- 时:分:秒,毫秒表示字幕显示的开始和结束时间。字幕文本可以是一行或多行文字直到遇到一个空行表示本条字幕结束。今天我们要做的就是写程序来读取这样的文件把时间、文字都解析出来也能把我们程序里处理好的字幕数据再正确地写回成SRT格式文件。最后我们还会做一个简单的工具能把两个字幕片段合并或者把一个大字幕文件按时间分割开。2. 核心数据结构设计写程序就像盖房子先得把图纸画好。处理SRT字幕我们首先要设计好用来存放一条字幕信息的“容器”。一条字幕信息主要就三样东西序号、时间范围、文字内容。在C语言里我们可以用一个结构体struct来把它们打包在一起。#include stdio.h #include stdlib.h #include string.h // 定义一个结构体来存放单条字幕信息 typedef struct { int index; // 字幕序号例如 1, 2, 3... int start_hour; // 开始时间 - 小时 int start_minute; // 开始时间 - 分钟 int start_second; // 开始时间 - 秒 int start_millisecond; // 开始时间 - 毫秒 int end_hour; // 结束时间 - 小时 int end_minute; // 结束时间 - 分钟 int end_second; // 结束时间 - 秒 int end_millisecond; // 结束时间 - 毫秒 char text[1024]; // 字幕文本内容 } SubtitleEntry;这里我们为什么要把时间拆成时、分、秒、毫秒这么多变量因为SRT文件里的时间格式是固定的00:01:23,456拆开来解析和后续计算都比较方便。text字段我们预留了1024个字符的空间对于大多数单条字幕来说足够了。有了存放单条字幕的结构我们还需要一个“仓库”来管理所有的字幕。因为字幕数量不确定使用动态数组链表会更灵活但为了入门简单我们先使用动态数组是个好主意。// 定义一个结构体来管理字幕列表 typedef struct { SubtitleEntry* entries; // 指向字幕条目数组的指针 int count; // 当前存储的字幕条数 int capacity; // 数组当前的最大容量 } SubtitleList; // 初始化一个字幕列表 SubtitleList* create_subtitle_list() { SubtitleList* list (SubtitleList*)malloc(sizeof(SubtitleList)); if (!list) { printf(内存分配失败\n); return NULL; } list-capacity 10; // 初始容量设为10条 list-count 0; list-entries (SubtitleEntry*)malloc(list-capacity * sizeof(SubtitleEntry)); if (!list-entries) { free(list); printf(内存分配失败\n); return NULL; } return list; }这个SubtitleList结构体包含一个动态数组(entries)、当前字幕数量(count)和数组容量(capacity)。create_subtitle_list函数负责初始化这个列表并分配初始内存。当字幕条数超过容量时我们还需要一个函数来扩容。// 向字幕列表中添加一条字幕 int add_subtitle_entry(SubtitleList* list, const SubtitleEntry* entry) { if (list-count list-capacity) { // 容量不足需要扩容简单起见每次翻倍 int new_capacity list-capacity * 2; SubtitleEntry* new_entries (SubtitleEntry*)realloc(list-entries, new_capacity * sizeof(SubtitleEntry)); if (!new_entries) { printf(内存扩容失败\n); return -1; // 添加失败 } list-entries new_entries; list-capacity new_capacity; } // 将新的字幕条目复制到数组中 list-entries[list-count] *entry; list-count; return 0; // 添加成功 }3. 解析SRT文件从文件到数据数据结构准备好了现在我们来解决最关键的一步如何读取一个.srt文件并把里面的文字变成我们程序能处理的数据。解析SRT文件可以看作是在按规则“拆包裹”。每个“包裹”字幕条目由空行分隔。我们需要按顺序处理读序号、读时间轴、读文本直到遇到空行然后开始下一个。// 解析时间字符串例如 00:01:23,456 int parse_time_string(const char* time_str, int* hour, int* minute, int* second, int* millisecond) { // 使用sscanf按照固定格式解析 int result sscanf(time_str, %d:%d:%d,%d, hour, minute, second, millisecond); if (result ! 4) { // 如果解析出的参数不是4个说明格式错误 return -1; } return 0; // 解析成功 } // 解析单条字幕条目假设文件指针已定位到条目开始 int parse_single_subtitle(FILE* file, SubtitleEntry* entry) { char line_buffer[1024]; // 1. 读取序号行 if (fgets(line_buffer, sizeof(line_buffer), file) NULL) { return -1; // 文件结束或读取失败 } // 去掉行尾的换行符 line_buffer[strcspn(line_buffer, \n)] 0; entry-index atoi(line_buffer); // 将字符串转换为整数 // 2. 读取时间轴行 if (fgets(line_buffer, sizeof(line_buffer), file) NULL) { return -1; } line_buffer[strcspn(line_buffer, \n)] 0; // 时间轴格式00:00:01,000 -- 00:00:04,000 char start_time[64], end_time[64]; if (sscanf(line_buffer, %s -- %s, start_time, end_time) ! 2) { printf(时间轴格式错误: %s\n, line_buffer); return -1; } // 解析开始时间和结束时间 if (parse_time_string(start_time, entry-start_hour, entry-start_minute, entry-start_second, entry-start_millisecond) ! 0) { printf(开始时间解析错误: %s\n, start_time); return -1; } if (parse_time_string(end_time, entry-end_hour, entry-end_minute, entry-end_second, entry-end_millisecond) ! 0) { printf(结束时间解析错误: %s\n, end_time); return -1; } // 3. 读取字幕文本可能有多行直到遇到空行 entry-text[0] \0; // 清空文本缓冲区 while (1) { if (fgets(line_buffer, sizeof(line_buffer), file) NULL) { break; // 文件结束 } line_buffer[strcspn(line_buffer, \n)] 0; if (strlen(line_buffer) 0) { break; // 遇到空行本条字幕结束 } // 如果不是第一行文本先添加一个换行符SRT内换行通常用\n表示 if (strlen(entry-text) 0) { strcat(entry-text, \n); } strcat(entry-text, line_buffer); } return 0; // 解析成功 }parse_single_subtitle函数是解析的核心。它严格按照SRT格式先读序号再读时间轴这里调用了parse_time_string来解析具体的时间数字最后读取文本。文本读取用一个循环直到遇到空行才停止这样就能正确处理多行字幕了。有了解析单条的函数我们就可以写出加载整个文件的函数了// 加载并解析整个SRT文件 SubtitleList* load_srt_file(const char* filename) { FILE* file fopen(filename, r); if (!file) { printf(无法打开文件: %s\n, filename); return NULL; } SubtitleList* list create_subtitle_list(); if (!list) { fclose(file); return NULL; } SubtitleEntry entry; while (!feof(file)) { // 尝试解析一条字幕 if (parse_single_subtitle(file, entry) 0) { // 解析成功添加到列表 if (add_subtitle_entry(list, entry) ! 0) { printf(添加字幕条目失败\n); break; } } else { // 解析失败可能是格式错误或文件结束 // 跳过可能存在的空行或格式错误的行 char buffer[1024]; fgets(buffer, sizeof(buffer), file); } } fclose(file); printf(成功加载 %d 条字幕。\n, list-count); return list; }这个函数打开文件创建一个字幕列表然后在一个循环里不断调用parse_single_subtitle把解析成功的条目添加到列表中直到文件结束。4. 生成SRT文件从数据到文件能读也要能写。当我们修改了字幕数据或者合并分割了字幕需要把结果保存回SRT文件。这个过程就是解析的逆过程把结构体里的数据按照SRT格式要求拼接成字符串写入文件。关键点在于时间数字的格式化输出。我们需要把1秒230毫秒变成00:00:01,230这样的字符串。// 将时间结构格式化为字符串 void format_time_string(int hour, int minute, int second, int millisecond, char* buffer, int buffer_size) { snprintf(buffer, buffer_size, %02d:%02d:%02d,%03d, hour, minute, second, millisecond); } // 将字幕列表写入SRT文件 int save_srt_file(const char* filename, const SubtitleList* list) { FILE* file fopen(filename, w); if (!file) { printf(无法创建文件: %s\n, filename); return -1; } char start_time_str[64]; char end_time_str[64]; for (int i 0; i list-count; i) { const SubtitleEntry* entry list-entries[i]; // 1. 写入序号 fprintf(file, %d\n, i 1); // 写入时序号可以从1开始重新编号 // 2. 格式化并写入时间轴 format_time_string(entry-start_hour, entry-start_minute, entry-start_second, entry-start_millisecond, start_time_str, sizeof(start_time_str)); format_time_string(entry-end_hour, entry-end_minute, entry-end_second, entry-end_millisecond, end_time_str, sizeof(end_time_str)); fprintf(file, %s -- %s\n, start_time_str, end_time_str); // 3. 写入字幕文本 fprintf(file, %s\n, entry-text); // 4. 写入空行分隔除非是最后一条 if (i list-count - 1) { fprintf(file, \n); } } fclose(file); printf(成功保存 %d 条字幕到文件: %s\n, list-count, filename); return 0; }format_time_string函数使用snprintf进行格式化输出%02d确保时、分、秒都是两位数字不足补零%03d确保毫秒是三位数字。save_srt_file函数则遍历整个字幕列表按照序号、时间轴、文本、空行的顺序写入文件生成一个标准的SRT文件。5. 实践制作字幕合并与分割工具读和写的基础功能都有了我们现在可以玩点更实用的。比如我手头有两个视频片段各自的字幕现在想把视频合并字幕也需要合并。或者我有一个长视频的字幕想按时间点把它切成两段。这些功能我们都可以基于上面的代码来实现。字幕合并工具思路很简单把第二个文件的所有字幕条目加到第一个文件的列表后面。但要注意时间轴需要调整。第二个字幕的开始时间应该接在第一个字幕的结束时间之后或者根据第二个视频的实际开始时间做偏移。// 合并两个字幕列表简单追加不处理时间偏移 SubtitleList* merge_subtitle_lists(const SubtitleList* list1, const SubtitleList* list2) { SubtitleList* merged_list create_subtitle_list(); if (!merged_list) return NULL; // 添加第一个列表的所有条目 for (int i 0; i list1-count; i) { add_subtitle_entry(merged_list, list1-entries[i]); } // 添加第二个列表的所有条目 for (int i 0; i list2-count; i) { add_subtitle_entry(merged_list, list2-entries[i]); } // (可选) 这里可以添加逻辑对第二个列表的时间进行偏移使其接在第一个之后 // 例如计算list1的最后结束时间将list2的所有时间点加上这个偏移量 return merged_list; }上面的合并是最简单的直接拼接。更实用的合并需要计算时间偏移。我们可以先计算第一个字幕列表的最后一个结束时间然后把第二个列表里所有字幕的开始和结束时间都加上这个值这样它们就会按顺序播放了。这涉及到时间计算比如把时、分、秒、毫秒转换成总毫秒数进行加法再转换回来你可以作为练习自己实现。字幕分割工具比如我想从第30秒的地方把字幕文件切成前后两部分。// 根据时间点分割字幕列表 // 将原列表分割为两部分时间点之前part1和之后part2 int split_subtitle_list(const SubtitleList* original, int split_hour, int split_minute, int split_second, int split_millisecond, SubtitleList** part1, SubtitleList** part2) { *part1 create_subtitle_list(); *part2 create_subtitle_list(); if (!*part1 || !*part2) return -1; // 将分割点时间转换为总毫秒数以便比较简化计算 long split_total_ms ((split_hour * 3600L) (split_minute * 60L) split_second) * 1000L split_millisecond; for (int i 0; i original-count; i) { const SubtitleEntry* entry original-entries[i]; // 计算当前条目的开始时间总毫秒数 long entry_start_ms ((entry-start_hour * 3600L) (entry-start_minute * 60L) entry-start_second) * 1000L entry-start_millisecond; if (entry_start_ms split_total_ms) { // 字幕开始时间在分割点之前归入第一部分 add_subtitle_entry(*part1, entry); } else { // 字幕开始时间在分割点之后归入第二部分 add_subtitle_entry(*part2, entry); } } return 0; }这个分割函数将用户输入的分割时间点转换成总毫秒数然后遍历所有字幕根据每条字幕的开始时间是否早于分割点来决定它属于前半部分还是后半部分。这是一个简单的按时间点切割实际应用中可能还需要处理那些横跨分割点的字幕即开始时间在分割点前结束时间在分割点后更复杂的逻辑是将其拆分成两条字幕。6. 把它们串起来一个完整的示例现在我们把上面所有的模块组合起来写一个简单的main函数来演示整个流程。// 示例加载、修改、保存字幕文件 int main() { const char* input_filename sample.srt; const char* output_filename output.srt; printf( SRT字幕文件处理示例 \n); // 1. 加载SRT文件 printf(正在加载文件: %s\n, input_filename); SubtitleList* subtitles load_srt_file(input_filename); if (!subtitles || subtitles-count 0) { printf(加载失败或文件为空。\n); // 这里可以创建一个示例字幕列表用于演示 printf(创建示例字幕进行演示...\n); subtitles create_subtitle_list(); SubtitleEntry example {1, 0,0,1,0, 0,0,4,0, 这是一个示例字幕。}; add_subtitle_entry(subtitles, example); } else { printf(加载成功共 %d 条字幕。\n, subtitles-count); // 简单打印前两条字幕内容 for (int i 0; i (subtitles-count 2 ? subtitles-count : 2); i) { printf( 条目 %d: %s\n, subtitles-entries[i].index, subtitles-entries[i].text); } } // 2. (示例) 这里可以添加处理逻辑比如修改某条字幕的文本 // if (subtitles-count 0) { // strcpy(subtitles-entries[0].text, 这是修改后的第一行字幕。); // } // 3. 保存处理后的字幕到新文件 printf(正在保存到文件: %s\n, output_filename); if (save_srt_file(output_filename, subtitles) 0) { printf(保存成功\n); } else { printf(保存失败。\n); } // 4. 清理内存 // 注意这里简化了完整的程序需要为SubtitleList写一个释放内存的函数 if (subtitles) { free(subtitles-entries); free(subtitles); } printf( 示例结束 \n); return 0; }这个示例程序尝试加载一个名为sample.srt的文件如果文件不存在或为空它会自己创建一条示例字幕。加载成功后它可以对字幕进行修改示例中被注释掉了最后将结果保存到output.srt文件中。你可以运行这个程序然后打开生成的output.srt文件看看效果。7. 总结走完这一趟你会发现用C语言处理SRT字幕文件并没有想象中那么复杂。核心就是理解文件格式然后用fgets逐行读取用sscanf和strtok等函数解析字符串最后再用fprintf按照格式写回去。过程中设计合理的数据结构结构体、动态数组能让代码清晰很多。我们实现的合并与分割工具虽然基础但已经揭示了处理这类问题的核心思路遍历数据、判断条件、执行操作添加、删除、修改时间戳。基于这个框架你可以扩展出更多功能比如批量修改时间轴、过滤特定关键词的字幕、转换不同编码如UTF-8与GBK等等。文件操作是C语言编程的基本功而像SRT解析这样的项目正好把字符串处理、内存管理、结构体、文件I/O这些知识点都串了起来。希望这个实践能帮你更扎实地掌握这些内容。下次当你需要处理任何有固定格式的文本文件时你都知道该从哪里入手了。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。