编译原理分析器大作业之字幕分析器

news2025/1/18 6:13:32

        写这篇文章的主要目的呢是分享一下编译原理大作业——电影字幕分析器,分享一下我的做法,可能采用的做法不是特别好的用法,希望各位多多指点,觉得文章不错给点小赞赞喔!!!

题目介绍

写一个srt字幕解释器,需要分析单词和语法,分别写出词法和语法,实现字幕的偏移,检验字幕单词语义。

字幕解释器功能与设计问题说明:

  1. 分析字幕的单词和语法,分别写出词法和语法的文法
  2. 在文法的基础上设计词法分析器和语法分析器
  3. 建立内存数据结构,缓冲字幕
  4. 实现字幕平移(例如,将字幕整体推迟2秒)等功能,更新字幕文件

分析

文法分析

我们先来看一条字幕的结构:

0

00:00:01,000 --> 00:00:25,000

English subtitle by : Eduun

1

00:00:36,700 --> 00:00:38,700

The late fourth century A.D. the

Roman Empire began to crumble.

通过上面我们可以看到该文法的大致结构为第一行为序号,第二行内容为时间,第三行为字幕内容,因此,我们可以先简单定义对应的语法和语义为,其中d---数字, 词法分析程序可以接受输入参数,如,d(2)表示数字只能是2位,d(0)表示数字不限位数

d(0)

d(2):d(2):d(2),d(3) --> d(2):d(2):d(2),d(3)

c

nn

d(0)

d(2):d(2):d(2),d(3) --> d(2):d(2):d(2),d(3)

c

代码实现

首先对于字幕我们需要用一个实体来文件中的记录,包括字幕的序号,字幕出现时间、字幕结束时间、字幕内容

public class SRT {
    private Integer id;
    private Integer beginTime;
    private Integer endTime;
    private String srtBody;
}

时间偏移的实现用一个TimeMove来实现,具体的思想是当需要偏移时间时,我们在原先的基础上加上需要偏移的时间:

public class TimeMove {

    int hour;
    int minute;
    int second;
    int msecond;

    public long getMilliSecond() {
        return (hour * 3600L + minute * 60L + second) * 1000L + msecond;
    }
}

上面是主要的功能设计,然后下面我们需要对字幕文件进行解析,将内容保存进内存中,对于字幕的内容我们直接将内容拼接了起来,防止有多行字幕,对于时间的解析,我单独封装了一个方法来进行专门解析,对于空行我们就用一个@去分隔

public void parseSrt(String srtPath){
    FileInputStream inputStream;
    try {
        inputStream = new FileInputStream(srtPath);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        return;// 有异常,就没必要继续下去了
    }
    BufferedReader br = new BufferedReader(new InputStreamReader(
            inputStream));
    String line;
    srtMap = new TreeMap<>();
    StringBuilder sb = new StringBuilder();
    int key = 0;
    try {
        while ((line = br.readLine()) != null || sb.length() > 0) {
            if (!"".equals(line) && line != null) {
                sb.append(line).append("@");
                continue;
            }
            String[] parseStrs = sb.toString().split("@");
            // 该if为了适应一开始就有空行以及其他不符格式的空行情况
            if (parseStrs.length < 3) {
                continue;
            }
            SRT srt = new SRT();
            StringBuilder srtBody = new StringBuilder();
            TimeToken timeToken = new TimeToken(srt);
            // 可能1句字幕,也可能2句及以上。
            srt.setId(Integer.valueOf(parseStrs[0].trim()));
            timeToken.parseTime(parseStrs[1]);
            // 删除最后一个"\n"
            for (int i = 2; i < parseStrs.length; i++) {
                srtBody.append(parseStrs[i], 0, parseStrs[i].length() - 1);
            }
            srt.setSrtBody(new String(srtBody.toString().getBytes(), StandardCharsets.UTF_8));
            srtMap.put(key, srt);
            key++;
            // 清空,否则影响下一个字幕元素的解析
            sb.delete(0, sb.length());
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

/**
 * startTime   -->     endTime
 * 解析时间
 * @param timeToken 时间的句子
 */
public void parseTime(String timeToken) {
    StringBuilder sb = new StringBuilder();
    List<String> list = new ArrayList<>();
    for (int i = 0; i < timeToken.length(); i++) {
        if (isToken(timeToken.charAt(i))) {
            if (sb.length() == 0 || sb.length() < 12) {
                continue;
            }
            list.add(sb.toString());
            sb.delete(0, sb.length());
        } else {
            if (timeToken.charAt(i) == ' ' || timeToken.charAt(i) == '\n') {
                continue;
            }
            sb.append(timeToken.charAt(i));
        }
    }
    if (sb.length() >=12) {
        list.add(sb.toString());
    }
    setStartTimeAndEndTime(list);
}

private boolean isToken(char ch) {
    return "-->".contains(String.valueOf(ch));
}

private void setStartTimeAndEndTime(List<String> time) {
    if (time.size() < 2) {
        throw new RuntimeException("字幕文件不合法!!!");
    }
    String startTime = time.get(0);
    String endTime = time.get(1);
    int lastIndexOfStartTime = startTime.lastIndexOf(',');
    int lastIndexOfEndTime = endTime.lastIndexOf(',');
    String[] startArray = startTime.substring(0, lastIndexOfStartTime).split(":");
    String[] endArray = endTime.substring(0, lastIndexOfEndTime).split(":");
    if (startArray.length < 3 || endArray.length < 3) {
        throw new RuntimeException("字幕文件不合法!!!");
    }
    int beginHour = Integer.parseInt(startArray[0]);
    int beginMintue = Integer.parseInt(startArray[1]);
    int beginSecond = Integer.parseInt(startArray[2]);
    int beginMilli = Integer.parseInt(startTime.substring(lastIndexOfStartTime + 1, startTime.length()));
    int beginTime = (beginHour * 3600 + beginMintue * 60 + beginSecond)
            * 1000 + beginMilli;
    srt.setBeginTime(beginTime);

    int endHour = Integer.parseInt(endArray[0]);
    int endMintue = Integer.parseInt(endArray[1]);
    int endSecond = Integer.parseInt(endArray[2]);
    // 校验时间格式
    if (!isValid(endMintue, endSecond, beginMintue, beginSecond)) {
        throw new RuntimeException("时间格式不合法!!!");
    }
    int endMilli = Integer.parseInt(endTime.substring(lastIndexOfStartTime + 1, endTime.length()));
    int endTimeInt = (endHour * 3600 + endMintue * 60 + endSecond )* 1000 + endMilli;
    srt.setEndTime(endTimeInt);
}

播放功能,需要加上字幕偏移:

public void controller() {
    // 字幕
    TreeMap<Integer, SRT> srtMap = solution.getSrtMap();
    Long startMillSecond = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
    Set<Integer> set = srtMap.keySet();
    int index = 0;
    SRT srt;
    while (index < set.size()) {
        srt = srtMap.get(index);
        Long nowMillSecond = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
        Integer beginTime = srt.getBeginTime();
        Integer endTime = srt.getEndTime();
        // 加上时间偏移
        long diff = nowMillSecond - startMillSecond + timeMove.getMilliSecond();
        if (diff < beginTime) {
            continue;
        }
        String srtBody = srt.getSrtBody();
        // 更新字幕
        if (diff >= beginTime && diff <= endTime) {
            view.setLabel(srtBody);
        }
        if (diff <= endTime) {
            continue;
        }
        view.setLabel("");
        index++;
    }
}

运行截图展示

 不足之处

        上面是我采用的做法,都是按行读取的,没有按字符去读取解析,读取的解析也做得不是很好,对于一些异常输入没有处理好,还有很多的改进的地方。对于给字幕添加颜色这个功能也没有做出来,鄙人写的比较菜,希望也能给各位带来帮助,如果需要我全部的代码可以在下面评论,也会把代码放到我的资源中(不想花积分下载就私信我就好)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/155668.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动手深度学习-线性神经网络:线性回归

目录线性模型&#xff08;1&#xff09;度量模型质量-损失函数&#xff08;2&#xff09;更新模型以提高模型预测质量-随机梯度下降线性回归的从零开始实现线性回归的简洁实现参考教程&#xff1a;https://courses.d2l.ai/zh-v2/线性模型 定义&#xff1a;回归&#xff08;reg…

FFmpeg音频重采样API(libswresample)

目录 参考 lswr功能介绍 lswr使用说明 示例代码 \1. 参考 [1] FFmpeg/Libswresample Documentation [2] FFmpeg/Libswresample Detailed Description [3] FFmpeg/doc/examples/resampling_audio.c \2. lswr功能介绍 FFmpeg中重采样的功能由libswresample(后面简写为lswr…

代码随想录训练营第五十七天

1.回文子串 题647 ①dp数组含义 判断回文子串可以用头元素和尾元素是否相等的方式&#xff0c;设dp[i] [j]为[i,j]子串是否为回文子串&#xff0c;是则为true&#xff0c;否为false。 ②递推公式 若 s[i] s[j] &#xff0c;分三种情况&#xff1a;i j&#xff0c;即只有一…

Leetcode数组专题专练:经典题目+思路解读

文章目录 系列&#xff1a;数组专练 语言&#xff1a;java & go 题目来源&#xff1a;Leetcode 常考点&#xff1a; 二分 & 双指针 & 滑动窗口 & 模拟行为 思路和参考答案文章目录数组专题总结二分法专练双指针专练滑动窗口专练模拟行为专练题目描述数组专题总…

cnn平移等变性和不变性

通俗说法: 等变性&#xff08;Equivariance&#xff09;&#xff1a;对一个输入施加某种变换后所产生的结果同样反应在输出上&#xff0c;说明该变换具有等变性。寻找一个从输入图像到输出类别的映射&#xff0c;这个映射对目标的几何变换(如平移&#xff0c;旋转&#xff0c;…

linux系统中如何使用QT来进行网络编程实现

大家好&#xff0c;今天主要和大家分享一下&#xff0c;如何使用QT进行网络编程与实现。 目录 第一&#xff1a;网络编程基本简介 第二&#xff1a;TCP通信简介 第三&#xff1a;TCP服务器端代码具体实现 第四&#xff1a;源文件mainwindow.cpp的具体实现 第一&#xff1a;…

console的常用方法

console的常用方法&#x1f913; 这篇文章我们来总结一下前端常用的一些调试技巧。&#x1f913;&#x1f913;&#x1f913;&#x1f913;&#x1f913; 说到调试&#xff0c;我们避免不了console这个对象&#xff0c;在它身上有许多的方法&#xff0c;我们来看看最常用的几…

Java 处理JSON 数据小结

Java 处理 JSON 数据小结 JSON的格式类型 JSON有三种格式类型&#xff1a;基本类型、数组类型、对象嵌套 基本类型 格式说明&#xff1a;{“键” : 值, “键” : “值”,…}&#xff0c;以大括号开始&#xff0c;键的名称加上冒号&#xff0c;然后跟上对应的的值&#xff0…

【Git】Git分支操作

4、Git 分支操作 4.1、什么是分支 在版本控制过程中&#xff0c;同时推进多个任务&#xff0c;为每个任务&#xff0c;我们就可以创建每个任务的单独分支。使用分支意味着程序员可以把自己的工作从开发主线上分离开来&#xff0c;开发自己分支的时候&#xff0c;不会影响主线分…

复制PDF文字时去掉换行符

问题描述 当我们在pdf上复制文字时&#xff0c;每行总会出现换行符&#xff0c;乱糟糟的。 解决方法 ⚠️注意&#xff1a; windows推荐开源软件cpoy&#xff1a;gihub:copy 临时使用&#xff0c;推荐网页&#xff1a;文字替换在线处理工具 在快捷指令中新建“快捷服务”&…

RHCE学习笔记-253-2

electronic mail services(sendmail,postfix) sendmail features 支持许多种不同邮件地址的格式 TCP/IP userhostname BitNet UUCP FidoNet MCImail 可以伪装邮寄者寄出去的邮件 当传送失败自动重试 security and “anti-spam” features 安全性特性 如果无法解析地址就退回这封…

C++学习之旅 第五章:字符串详解

目录 开头&#xff1a; C字符串的两种形式&#xff1a; C 风格字符串 STL库中char类型的字符串操作函数&#xff1a; C 中的 String 类 STL库中string类型的操作函数&#xff1a; 1&#xff0e;声明一个C字符串 String类的构造函数和析构函数如下&#xff1a; 2&#…

RK3568开发板Visual Studio Code 插件安装

我们在此以 ubuntu 环境为例&#xff0c;讲解 Visual Studio Code 插件安装。 VSCode 支持多种语言&#xff0c;比如 C/C、Python、C#等等&#xff0c;对于嵌入式开发的我们主要用 来编写 C/C程序的&#xff0c;所以需要安装 C/C的扩展包&#xff0c;扩展包安装很简单&#xff…

若依移动端Ruoyi-App——开发总结

1. 去掉验证码 &#xff08;1&#xff09;在系统管理菜单中——》参数设置——》找到账户自助-验证码开关——》修改键值为false。 &#xff08;2&#xff09;在移动端前端将login.vue的captchaEnabled改为false&#xff0c;关闭验证码开关 &#xff08;3&#xff09;在移动端…

数据库,计算机网络、操作系统刷题笔记30

数据库&#xff0c;计算机网络、操作系统刷题笔记30 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle…

Linux篇【5】:Linux 进程概念(五):环境变量

目录 环境变量 常见的环境变量 基本概念 查看环境变量内容的方法 测试环境变量PATH 与环境变量相关的命令 Linux操作系统下C/C程序代码中获取环境变量的方式 环境变量的组织方式 环境变量通常具有全局属性 环境变量 问题&#xff1a; 注意&#xff1a;可执行程序 等价于 命令/指…

【记录二】图层添加+坐标系转换理论+dva理论

坐标系一、坐标系地理坐标系cesium中的几种坐标系代码封装二、网页通讯模块PWAServiceWorker三、代码四、dva理论知识dva定义从redux -> dva带model的代码结构带model的数据流图一、坐标系 地理坐标系 cesium中的几种坐标系 链接: Cesium中的几种坐标和相互转换 代码封装…

Flowable进阶学习(一)表结构、ProcessEngine、Service、BPMN图标

文章目录一、Flowable表结构1.表结构讲解二、ProcessEngine讲解2.1 加载默认的配置文件2.2 加载自定义配置文件2.3 ProcessEngine源码2.4 ProcessEngineConfiguration中的init()方法2.5 ProcessEngine各种方式对比三、Service服务接口3.1 Service创建方式与名称作用简介四、Flo…

mysql核心知识-----索引

文章目录索引的概念和用途应用层的mysql&#xff08;各种操作语句&#xff09;与底层的mysql数据库&#xff08;磁盘上的文件&#xff09;交互IO的单位深入理解索引聚簇索引 VS 非聚簇索引普通&#xff08;辅助&#xff09;索引什么字段适合做主键&#xff1f;索引的概念和用途…

1.ISAAC简介

ISAAC简介 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html Isaac 是 NVIDIA 的智能机器人开放平台。 Isaac SDK 提供了大量强大的 GPU 加速算法 GEM&#xff0c;用于导航和操作。 Isaac SDK Engine 是一个框架&#xff0c;可以轻松编写模块化应…