Android解析SRT字幕文件

Android解析SRT字幕文件


  • 说明一下

这是本人第一次写Markdown,很多写的不规范的还希望大家能够理解,好的废话不多说。


SRT的数据格式

1313
01:45:15,600 –> 01:45:17,240
向「活死人墓」资助人提供服务
and offered his services to the backers of The Tomb.

以上就是SRT的数据格式,通过以上单个数据节点可以提供一个大致的思路是:先定位一个数据节点的固定格式,然后将一行一行的读取到数据。本文使用时间作为固定格式,将通过正则表达式”\d\d:\d\d:\d\d,\d\d\d –> \d\d:\d\d:\d\d,\d\d\d”得到01:45:15,600 –> 01:45:17,240格式的时间。逐行读取保存即可得到该节点的全部数据。

获取数据节点的代码

节点定义

package com.pplive.androidphone.layout.subtitle;

/**
 * @Auther jixiongxu
 * @date 2017/9/20.
 * @descraption 字幕数据结构
 */

public class SubtitlesModel
{
    /**
     * 当前节点
     */
    public int node;

    /**
     * 开始显示的时间
     */
    public int star;

    /**
     * 结束显示的时间
     */
    public int end;

    /**
     * 显示的内容《英文》
     */
    public String contextE;

    /**
     * 显示的内容《中文》
     */
    public String contextC;
}

读取文件和筛选内容

package com.pplive.androidphone.layout.subtitle;

import android.util.Log;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.regex.Pattern;

/**
 * @Auther jixiongxu
 * @date 2017/9/20.
 * @descraption 用于解析字幕
 */

public class SubtitlesDataCoding
{
    /**
     * 一秒=1000毫秒
     */
    private final static int oneSecond = 1000;

    private final static int oneMinute = 60 * oneSecond;

    private final static int oneHour = 60 * oneMinute;

    /**
     * 每一个数据节点
     */
    public static ArrayList<SubtitlesModel> list = new ArrayList<SubtitlesModel>();

    /**
     * 正则表达式,判断是否是时间的格式
     */
    private final static String equalStringExpress = "\\d\\d:\\d\\d:\\d\\d,\\d\\d\\d --> \\d\\d:\\d\\d:\\d\\d,\\d\\d\\d";

    /**
     * 读取本地文件
     *
     * @param path
     */
    public static ArrayList<SubtitlesModel> readFile(String path)
    {
        String line;
        FileInputStream is;
        File subtitlesFile = new File(path);
        BufferedReader in = null;

        if (!subtitlesFile.exists() || !subtitlesFile.isFile())
        {
            Log.e("jixiongxu:", "open subtitle file fill");
            return null;
        }
        /**
         * 读取文件,转流,方便读取行
         */
        try
        {
            is = new FileInputStream(subtitlesFile);
            try
            {
                in = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            }
            catch (UnsupportedEncodingException e)
            {
                e.printStackTrace();
            }
        }
        catch (FileNotFoundException e)
        {
            e.printStackTrace();
        }
        try
        {
            assert in != null;
            while ((line = in.readLine()) != null)
            {
                SubtitlesModel sm = new SubtitlesModel();
                // 匹配正则表达式,不符合提前结束当前行;
                if (Pattern.matches(equalStringExpress, line))
                {
                    // 填充开始时间数据
                    sm.star = getTime(line.substring(0, 12));
                    // 填充结束时间数据
                    sm.end = getTime(line.substring(17, 29));
                    // 填充中文数据
                    sm.contextC = in.readLine();
                    // 填充英文数据
                    sm.contextE = in.readLine();
                    // 当前字幕的节点位置
                    sm.node = list.size() + 1;
                    list.add(sm);
                }
            }
            return getSubtitles();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        if (list != null)
        {
            Log.d("jixiongxu:", "open subtitle file ok");
            return getSubtitles();
        }
        return null;
    }

    /**
     * @param line
     * @return 字幕所在的时间节点
     * @descraption 将String类型的时间转换成int的时间类型
     */
    private static int getTime(String line)
    {
        try
        {
            return Integer.parseInt(line.substring(0, 2)) * oneHour// 时
                    + Integer.parseInt(line.substring(3, 5)) * oneMinute// 分
                    + Integer.parseInt(line.substring(6, 8)) * oneSecond// 秒
                    + Integer.parseInt(line.substring(9, line.length()));// 毫秒
        }
        catch (NumberFormatException e)
        {
            e.printStackTrace();
        }
        return -1;
    }

    /**
     * @return list?null
     * @descraption 返回解析后的字幕数据
     */
    public static ArrayList<SubtitlesModel> getSubtitles()
    {
        return list != null && list.size() > 0 ? list : null;
    }
}

本文的字幕数据是直接从本地读取,大家在使用的时候如果是从网络单独下载的str或者从视频文件中提琴的文件需要更改这个类的一些方法。因为srt中读取的时间格式是String格式,在读取过程中本文直接转换了成毫秒,这样在调用的时候就不必再转换(因为视频一般是以毫秒为单位播放)。

显示的实现

由上文的返回数据可以看大返回了一个ArrayList< SubtitlesModel >数据,数据节点需要在相应的时间显示出相应的字幕,则需要一个查找过程,为了提高效率本文使用二分查找法进行查找。同时为了提高代码的可扩展性,定义一个接口方便以后再修改。

定义控制接口

package com.pplive.androidphone.layout.subtitle;

import java.util.ArrayList;

/**
 * @Auther jixiongxu
 * @dat 2017/9/21
 * @descraption 字幕控制接口
 */

public interface ISubtitleControl
{
    /**
     * 设置中文字幕
     * 
     * @param item
     */
    void setItemSubtitleChina(String item);

    /**
     * 设置英文字幕
     * 
     * @param item
     */
    void setItemSubtitleEnglish(String item);

    /**
     * 定位设置字幕
     * 
     * @param position
     */
    void seekTo(int position);

    /**
     * 设置数据
     * 
     * @param list
     */
    void setData(ArrayList<SubtitlesModel> list);

    /**
     * 设置显示的语言
     * 
     * @param type
     */
    void setLanguage(int type);

    /**
     * 暂停
     * 
     * @param pause
     */
    void setPause(boolean pause);

    /**
     * 开始
     * 
     * @param start
     */
    void setStart(boolean start);

    /**
     * 停止
     * 
     * @param stop
     */
    void setStop(boolean stop);

    /**
     * 后台播放
     * 
     * @param pb
     */
    void setPlayOnBackground(boolean pb);
}

接下来是布局的编写,显示的字幕中包含两个textview,一个显示中文一个显示英文。由于本文在设计过程中需要对textview的显示效果和点击都有要求,因此还自定义了一个继承与TextView的SubtitleTextView。代码也是非常简单。

SubtitleTextView代码

package com.pplive.androidphone.layout.subtitle;

import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

/**
 * @Auther jixiongxu
 * @date 2017/9/20
 * @descraption 显示字幕的字体样式
 */

public class SubtitleTextView extends TextView implements View.OnTouchListener
{
    private Context context;

    private SubtitleClickListener listener;

    public SubtitleTextView(Context context)
    {
        this(context, null);
    }

    public SubtitleTextView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        this.context = context;
        // 默认白色字体
        setTextColor(Color.WHITE);
        setSingleLine(true);
        setShadowLayer(3, 0, 0, Color.RED);
        this.setOnTouchListener(this);
    }

    public void setSubtitleOnTouchListener(SubtitleClickListener listener)
    {
        this.listener = listener;
    }

    @Override
    public boolean onTouch(View view, MotionEvent event)
    {
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                if (listener != null)
                    listener.ClickDown();
                break;
            case MotionEvent.ACTION_UP:
                if (listener != null)
                    listener.ClickUp();
                break;
        }
        return true;
    }
}

/**
 * 对字幕进行监听的接口
 */
interface SubtitleClickListener
{
    /**
     * 按下
     */
    void ClickDown();

    /**
     * 取消
     */
    void ClickUp();
}

接下来是字幕布局的代码

SubtitleView代码

package com.pplive.androidphone.layout.subtitle;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Toast;

import com.pplive.androidphone.R;
import com.pplive.androidphone.ui.videoplayer.layout.VideoJjController;

import java.util.ArrayList;
import java.util.zip.Inflater;

/**
 * @date 2017/9/20
 * @Auther jixiongxu
 * @descraptio 显示字幕的图层
 */
public class SubtitleView extends LinearLayout implements ISubtitleControl, SubtitleClickListener
{
    /**
     * 只显示中文
     */
    public final static int LANGUAGE_TYPE_CHINA = 0;

    /**
     * 只显示英文
     */
    public final static int LANGUAGE_TYPE_ENGLISH = LANGUAGE_TYPE_CHINA + 1;

    /**
     * 双语显示
     */
    public final static int LANGUAGE_TYPE_BOTH = LANGUAGE_TYPE_ENGLISH + 1;

    /**
     * 字幕所有的数据
     */
    private ArrayList<SubtitlesModel> data = new ArrayList<SubtitlesModel>();

    /**
     * 中文字幕
     */
    private SubtitleTextView subChina;

    /**
     * 英文字幕
     */
    private SubtitleTextView subEnglish;

    /**
     * 当前显示节点
     */
    private View subTitleView;

    /**
     * 单条字幕数据
     */
    private SubtitlesModel model = null;

    /**
     * 后台播放
     */
    private boolean palyOnBackground = false;

    private int language = LANGUAGE_TYPE_BOTH;

    private Context context;

    public SubtitleView(Context context)
    {
        this(context, null);
    }

    public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr)
    {
        this(context, attrs);
    }

    public SubtitleView(final Context context, AttributeSet attrs)
    {
        super(context, attrs);
        this.context = context;
        subTitleView = View.inflate(context, R.layout.subtitleview, null);
        subChina = (SubtitleTextView) subTitleView.findViewById(R.id.subTitleChina);
        subEnglish = (SubtitleTextView) subTitleView.findViewById(R.id.subTitleEnglish);
        subChina.setSubtitleOnTouchListener(this);
        subEnglish.setSubtitleOnTouchListener(this);
        this.setOrientation(VERTICAL);
        this.setGravity(Gravity.BOTTOM);
        this.addView(subTitleView);
    }

    @Override
    public void setItemSubtitleChina(String item)
    {
        subChina.setText(item);
    }

    @Override
    public void setItemSubtitleEnglish(String item)
    {
        subEnglish.setText(item);
    }

    @Override
    public void seekTo(int position)
    {
        if (data != null && !data.isEmpty())
        {
            model = searchSub(data, position);
            Log.d("jixiongxu", position + "/" + data.get(data.size() - 1).end);
        }
        if (model != null)
        {
            setItemSubtitleChina(model.contextC);
            setItemSubtitleEnglish(model.contextE);
        }
        else
        {
            setItemSubtitleChina("");
            setItemSubtitleEnglish("");
        }
    }

    @Override
    public void setData(ArrayList<SubtitlesModel> list)
    {
        if (list == null || list.size() <= 0)
        {
            Log.e("jixiongxu", "subtitle data is empty");
            return;
        }
        this.data = list;
    }

    @Override
    public void setLanguage(int type)
    {
        if (type == LANGUAGE_TYPE_CHINA)
        {
            subChina.setVisibility(View.VISIBLE);
            subEnglish.setVisibility(View.GONE);
        }
        else if (type == LANGUAGE_TYPE_ENGLISH)
        {
            subChina.setVisibility(View.GONE);
            subEnglish.setVisibility(View.VISIBLE);
        }
        else if (type == LANGUAGE_TYPE_BOTH)
        {
            subChina.setVisibility(View.VISIBLE);
            subEnglish.setVisibility(View.VISIBLE);
        }
        else
        {
            subChina.setVisibility(View.GONE);
            subEnglish.setVisibility(View.GONE);
        }
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus)
    {
        super.onWindowFocusChanged(hasWindowFocus);
        Log.d("jixiongxu", "onWindowFocusChanged:" + hasWindowFocus);
    }

    @Override
    public void setPause(boolean pause)
    {

    }

    @Override
    public void setStart(boolean start)
    {

    }

    @Override
    public void setStop(boolean stop)
    {

    }

    @Override
    public void setPlayOnBackground(boolean pb)
    {
        this.palyOnBackground = pb;
    }

    /**
     * 采用二分法去查找当前应该播放的字幕
     *
     * @param list 全部字幕
     * @param key 播放的时间点
     * @return
     */
    public static SubtitlesModel searchSub(ArrayList<SubtitlesModel> list, int key)
    {
        int start = 0;
        int end = list.size() - 1;
        while (start <= end)
        {
            int middle = (start + end) / 2;
            if (key < list.get(middle).star)
            {
                if (key > list.get(middle).end)
                {
                    return list.get(middle);
                }
                end = middle - 1;
            }
            else if (key > list.get(middle).end)
            {
                if (key < list.get(middle).star)
                {
                    return list.get(middle);
                }
                start = middle + 1;
            }
            else if (key >= list.get(middle).star && key <= list.get(middle).end)
            {
                return list.get(middle);
            }
        }
        return null;
    }

    @Override
    public void ClickDown()
    {
        language++;
        setLanguage(language % 3);
    }

    @Override
    public void ClickUp()
    {
    }
}

SubtitleView的xml布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="bottom"
    android:orientation="vertical">

    <com.pplive.androidphone.layout.subtitle.SubtitleTextView
        android:id="@+id/subTitleChina"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:gravity="center"
        android:text="中文" />

    <com.pplive.androidphone.layout.subtitle.SubtitleTextView
        android:id="@+id/subTitleEnglish"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:gravity="center"
        android:text="English" />

</LinearLayout>

基本到这里就大功告成了,接下来就是使用了。

MainActivity使用

package demo.pplive.com.subtitles;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity
{
    private static final int REQUEST_EXTERNAL_STORAGE = 1;

    private static String[] PERMISSIONS_STORAGE = {android.Manifest.permission.READ_EXTERNAL_STORAGE,
            android.Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS};

    private ArrayList<SubtitlesModel> list = new ArrayList<>();

    private Button bt;

    private EditText et;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout);
        list = SubtitlesCoding.getSubtitles();
        final SubtitleView subtitleView = (SubtitleView) findViewById(R.id.subtitleview);
        bt = (Button) findViewById(R.id.button);
        et = (EditText) findViewById(R.id.edittext);
        subtitleView.setData(list);
        checkAndApplyPermission();
        bt.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                if (!et.getText().toString().isEmpty())
                {
                    SubtitlesCoding.readFile(
                            Environment.getExternalStorageDirectory().getAbsolutePath() + et.getText().toString());
                    list = SubtitlesCoding.getSubtitles();
                    subtitleView.setData(list);
                }
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
    {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    public void checkAndApplyPermission()
    {
        if (ActivityCompat.checkSelfPermission(this,
                android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
        {
            ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
        }
    }
}

主界面的布局代码

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#000"
    android:focusable="true"
    android:gravity="center">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitXY"
        android:src="@drawable/player_view_bg2" />

    <demo.pplive.com.subtitles.SubtitleView
        android:id="@+id/subtitleview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"></demo.pplive.com.subtitles.SubtitleView>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/edittext"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="输入字幕位置"
            android:textColor="#ffffff"
            android:textColorHint="#ffffff" />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="开始" />
    </LinearLayout>
</FrameLayout>

使用的效果图如下

这里写图片描述

关于使用在本文的MainActivity没有体现出,而是直接seekTo()到某一时刻。在这里说一下。在使用过程中通过seekTo()函数就可以调用,也就是说视频播放过程中每隔一段时间就去seekTo()一次就可以与视频同步。视频播放一般都会有回调方法去返回当前播放的进度,而且是以毫秒为单位的。还有就是记得添加权限哟。如果大家有什么问题或改进建议也可以直接联系我。同时附上我的 gitup:https://github.com/jixiongxu/Subtitles.git 地址。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值