视频硬字幕提取方法(可完全离线),开发个小工具辅助一下

作者分享了一款集成硬字幕提取、文字识别和图片缩放功能的工具Wondertool,基于Java开发并已在Gitee和GitHub开源。用户可以通过提供的教程进行操作,使用VideoSubFinder捕获字幕图片,然后通过PearOCR进行离线识别,最终由作者的工具生成TXT字幕文件。工具适用于需要大量视频字幕处理的场景,但OCR的准确性可能需要人工校对。
摘要由CSDN通过智能技术生成
没想到这么多人需要这些功能,本人整合了在这个最新的工具中,打开有教程

本人的最新工具:wondertool: 硬字幕提取、文字物体识别、图片缩放工具 (gitee.com)

3.0版本下载地址:wondertool 发行版 - Gitee.com

下载这4个包解压即可:

WonderTool_win-x64.zip
 WonderTool_win-x64.z01
 WonderTool_win-x64.z03
 WonderTool_win-x64.z02

原文如下:

最近博主闲下来了,思考人生接下来的方向,无聊时帮别人做了点小东西,贡献出来:

jre-17.0.7_win-x64的生成方式:

以管理员方式运行PowerShell执行命令:

cd $env:JAVA_HOME

jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,java.xml,java.desktop,jdk.management.agent --output E:\jre-17.0.7_win-x64

内部参数:

java -Dfile.encoding=UTF-8 -jar single-subtitle-text-1.0.0.jar

使用教程:

注意该图片的两个文件夹,都在下面这个软件的安装目录中。

VideoSubFinderWXW 拿来做字幕图片截取捕获,这是下载地址:

VideoSubFinder download | SourceForge.net

下载安装完成后,步骤:打开视频框选字幕位置,清空所有图片(之前识别的图片),开始识别。

捕获完在安装目录图片所示的第一个文件夹RGBImages中,PearOCR可以安装成浏览器应用可以离线识别,pearOCR官网如下:

PearOCR,在线图片转文字,免费OCR,在线图片文字提取,本地运算,无上传

上传RGBImages所有图片识别完后导出为txt。(如果后续出现问题,文件路径及文件名不建议带有中文)

接下来是我开发的小工具(代码已开源到github上),下载地址如下,

Release single-subtitle-text · Aaron-CJ/single-subtitle-text · GitHub

下载single-subtitle-text_win-x64.zip解压,看到single-subtitle-text_win-x64.exe

安全无毒,下面是代码:

package pers.lcj.tool;

import lombok.extern.log4j.Log4j2;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by Aaron on 2023/4/28 0:29
 */
@Log4j2
public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in, "GBK");

        System.out.println("请输入导入文件路径:");
        Path inputPath = getPath(scanner, "导入文件");
        System.out.println("请输入导出目录路径:");
        Path outputPath = getPath(scanner, "导出目录");

        Map<String, String> result = readFile(inputPath.toString());

        writeOutputFiles(outputPath, result);
    }

    public static Path getPath(Scanner sc, String pathType) {
        Path path;
        do {
            String inputPath = sc.nextLine().replaceAll("^\"+|\"+$", "");
            path = Paths.get(inputPath);
            if (pathType.equals("导入文件") && !Files.isRegularFile(path)) {
                System.out.println("输入路径不是一个有效文件路径,请重新输入:");
                path = null;
            } else if (pathType.equals("导出目录") && !Files.isDirectory(path)) {
                System.out.println("输入路径不是一个有效文件路径,请重新输入:");
                path = null;
            }
        } while (path == null);
        return path;
    }

    private static final int BUFFER_SIZE = 8192;
    private static final Pattern REGEX_PATTERN = Pattern.compile("(?<=-\\s)([^.]+)(?=\\.jpeg)");

    private static Map<String, String> readFile(String inputFilePath) {
        Map<String, String> result = new HashMap<>();
        try (BufferedReader br = new BufferedReader(new FileReader(inputFilePath), BUFFER_SIZE)) {
            StringBuilder currentValueBuilder = new StringBuilder(1024); // 初始化StringBuilder大小,避免过多扩容
            Matcher matcher;
            String key = null;
            String currentLine;
            while ((currentLine = br.readLine()) != null) {
                matcher = REGEX_PATTERN.matcher(currentLine);
                if (matcher.find()) {
                    if (key != null) {
                        String value = currentValueBuilder.toString().trim();
                        result.put(key, value.isEmpty() ? "null" : value); // 添加新的结果到Map中,并清空StringBuilder
                        currentValueBuilder.setLength(0);
                    }
                    key = matcher.group(1);
                } else if (key != null) { // 对currentValue进行构建
                    currentValueBuilder.append(currentLine.trim()).append(" "); // 添加空格,避免拼接相邻字符串时出现语义混淆
                }
            }
            if (key != null) { // 处理最后一个结果
                result.put(key, currentValueBuilder.toString().trim().replace("  ------------ prower by PearOCR.com ------------", ""));
            }
        } catch (IOException e) {
            log.error("Error reading file:", e);
        }
        return result;
    }

    private static void writeOutputFiles(Path outputFolderPath, Map<String, String> result) {
        System.out.println("正在处理,请稍等......");
        result.entrySet().parallelStream().forEach(entry -> {
            String fileName = entry.getKey();
            Path txtFilePath = outputFolderPath.resolve(fileName + ".txt");
            try (BufferedWriter bw = Files.newBufferedWriter(txtFilePath, StandardCharsets.UTF_8)) {
                bw.write(entry.getValue());
            } catch (IOException e) {
                log.error("Error write file:", e);
            }
        });
        System.out.println("文件总计(个):" + result.size());
    }

}

Gradle 8.1.1引入的依赖:

plugins {
    // Java插件,该插件提供了Java应用和库的基本功能
    id 'java'

    // lombok插件,该插件支持在Java项目中使用Lombok注解
    id 'io.freefair.lombok' version '8.0.1'
}

group = 'pers.lcj.tool'
version = '1.0.0'

repositories {
    mavenLocal {
        url 'file:///D:/m2/repository'
    }
    maven { url 'https://maven.aliyun.com/repository/public/' }
    mavenCentral()
}

dependencies {
    // Lombok
    compileOnly 'org.projectlombok:lombok:1.18.26'
    annotationProcessor 'org.projectlombok:lombok:1.18.26'

    // Log4j2
    implementation 'org.apache.logging.log4j:log4j-api:2.20.0'
    implementation 'org.apache.logging.log4j:log4j-core:2.20.0'
    implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.20.0'

    // JUnit 5
    testImplementation 'org.junit.jupiter:junit-jupiter:5.9.3'
}

jar {
    duplicatesStrategy = 'exclude'
    from {
        configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
    manifest {
        attributes 'Main-Class': 'pers.lcj.tool.Main'
    }
}

test {
    useJUnitPlatform()
}

log4j2.properties log4j2 配置文件

# 设置日志级别为info
rootLogger.level = info

# 控制台Appender的配置
appender.console.type = Console
appender.console.name = consoleLogger
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

# 根日志记录器引用控制台Appender
rootLogger.appenderRef.stdout.ref = consoleLogger

# 日志文件存放路径
property.basePath = logs

# 组件Appender的名称、模式、路径和滚动策略
appender.rolling.type = RollingFile
appender.rolling.name = fileLogger
appender.rolling.fileName= ${basePath}/sst.log
appender.rolling.filePattern= ${basePath}/sst_%d{yyyyMMdd}.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} %level [%t] [%l] - %msg%n
appender.rolling.policies.type = Policies

# 组件Appender的滚动策略,包括按大小滚动和按时间滚动两种策略。
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
# 按大小滚动策略大小限制
appender.rolling.policies.size.size = 310MB
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
# 按时间滚动策略间隔时间(单位:天)
appender.rolling.policies.time.interval = 1
# 按时间滚动策略是否调整时区
appender.rolling.policies.time.modulate = true

# 组件Appender的滚动策略,包括按大小滚动和按时间滚动两种策略。
appender.rolling.strategy.type = DefaultRolloverStrategy

# 组件Appender的删除策略。
appender.rolling.strategy.delete.type = Delete
# 删除文件所在目录路径。
appender.rolling.strategy.delete.basePath = ${basePath}
# 删除最旧的文件之前可以在目录中保留的最大文件数。默认值为0,这意味着不会删除任何文件。
appender.rolling.strategy.delete.maxDepth = 92
# 用于指定删除文件的条件类型,按最后修改时间删除。
appender.rolling.strategy.delete.ifLastModified.type = IfLastModified
# 删除所有早于92天的文件。
appender.rolling.strategy.delete.ifLastModified.age = 92d

# 配置根日志记录器,引用组件Appender。
rootLogger.appenderRef.rolling.ref = fileLogger

打开single-subtitle-text_win-x64.exe,根据提示输入该txt文本文件路径和输入安装目录图片所示的第二个文件夹TXTResults的路径,生成相应的TXTResults后

点击软件ocr菜单中的这个即可生成你想要的视频字幕了,贡献给所有做视频的朋友,市面上做这个的基本都收费,特别视频多且视频时长 长的工作,可以带来助力,大大缩短工作时长,但毕竟ocr识别不是完全准确的,还是需要一些人工审核。虽然意义不是很大但是对同声传译的视频提取字幕,新闻传播类等的二次制作等有帮助,以及博主不知道的领域等有帮助。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值