使用FFMpeg 提取MKV文件中的字幕

使用FFMpeg 提取MKV文件中的字幕MKV 封装格式是万能封装格式 可以封装几乎所有的视频和音频编码格式 可以包含多个视频流 音频流和字幕流 本文将介绍使用 FFMpeg 解码视频文件 提去字幕内容并保存 这里仅提取 ASS 格式的字幕文件 使用 FFMpeg 解 MKV 封装 获取字幕流信息 voidFFMpegAs openVideoFil QStringfileN 打开视频文件 i

MKV封装格式是万能封装格式,可以封装几乎所有的视频和音频编码格式。可以包含多个视频流、音频流和字幕流。本文将介绍使用FFMpeg 解码视频文件,提去字幕内容并保存。这里仅提取ASS格式的字幕文件。

使用FFMpeg解MKV封装,获取字幕流信息

void FFMpegAssThread::openVideoFile(QString fileName) { // 打开视频文件 int result = avformat_open_input(&m_FormatContext, fileName.toLocal8Bit().data(), nullptr, nullptr); if (result < 0) return; // 查找流信息 result = avformat_find_stream_info(m_FormatContext, nullptr); if (result < 0) return; // 获取字幕流 int streamCount = m_FormatContext->nb_streams; m_SubtitleCount = 0; for (int i=0; i 
  
    if (m_FormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { m_SubtitleStream[m_SubtitleCount++] = i; 
   continue; } } 
   if (m_SubtitleCount == 
   0) { avformat_close_input(&m_FormatContext); 
   return; } 
   // 获取视频总时长 m_TotalTime = m_FormatContext->duration * 
   1.0 / AV_TIME_BASE * 
   1000; 
   // 获取解码器 
   for ( 
   int i= 
   0; i 
   
     streams[m_SubtitleStream[i]]->codec; 
    if (codecContext->codec_id == AV_CODEC_ID_ASS) { AVCodec *codec = avcodec_find_decoder(codecContext->codec_id); result = avcodec_open2(codecContext, codec, 
    nullptr); 
    if (result < 
    0) 
    continue; m_SubtitleCodecContext[i] = codecContext; } } } 
    
  

使用函数avcodec_decode_subtitle2解码字幕Packet,AVSubtitle中存储字幕的具体内容信息。下面是在线程中解码函数

void FFMpegAssThread::run(void) { while (!this->isInterruptionRequested()) { if (m_FormatContext != nullptr) { AVPacket pkt; av_init_packet(&pkt); // 获取一帧数据 int result = av_read_frame(m_FormatContext, &pkt); if (result < 0) { emit sendCurrentProgress(100); av_packet_unref(&pkt); break; } bool needDecodec = false; AVCodecContext *codecContext = nullptr; for (int i=0; i 
  
    if (m_SubtitleStream[i] == pkt.stream_index) { needDecodec = 
   true; codecContext = m_SubtitleCodecContext[i]; 
   break; } } 
   if (!needDecodec) { av_packet_unref(&pkt); 
   continue; } 
   int streamIndex = pkt.stream_index; AVRational rational = m_FormatContext->streams[streamIndex]->time_base; qreal value = pkt.pts * 
   1.0 / rational.den * rational.num * 
   1000 / m_TotalTime * 
   100; emit sendCurrentProgress(value); 
   // 解码 AVSubtitle subtitle; 
   int gotSub = 
   0; result = avcodec_decode_subtitle2(codecContext, &subtitle, &gotSub, &pkt); 
   if (result < 
   0) { av_packet_unref(&pkt); 
   continue; } 
   if (gotSub > 
   0) { 
   int number = subtitle.num_rects; 
   for ( 
   int i= 
   0; i 
   
     0); file->write(subtitle.rects[i]->ass, 
    strlen(subtitle.rects[ 
    0]->ass)); } avsubtitle_free(&subtitle); } av_packet_unref(&pkt); } 
    else QThread::msleep( 
    10); } } 
    
  
#ifndef FFMPEG_ASS_GET_H #define FFMPEG_ASS_GET_H #include "UIBase/UIBaseWindow.h" #include "FFMpegASSThread.h" #include 
    #include 
    #include 
    #include 
    class FFMpegAssGetWidget : public UIBaseWindow { Q_OBJECT public: FFMpegAssGetWidget(QWidget *parent = nullptr); ~FFMpegAssGetWidget(); private: void initUi(void); // 设置ASS路径 void setAssPathCount(int count); QLineEdit *m_SrcFileNamePathLineEdit = nullptr; QPushButton *m_BrowseButton = nullptr; QList 
  
    m_DecodecLineEditList; QList 
   
     m_DestBrowseButtonList; QPushButton *m_ConvertButton = 
    nullptr; QProgressBar *m_ProgressBar = 
    nullptr; 
    private slots: 
    void onClickedBrowseButton( 
    void); 
    void onClickedDestBrowseButton( 
    void); 
    void onClickedConvertButton( 
    void); 
    void onRecvConvertProgress(qreal); 
    private: FFMpegAssThread *m_FFMpegAssThread = 
    nullptr; QWidget *m_AssSubtitleWidget = 
    nullptr; }; 
    #endif 
    
  

界面-FFMpegAssGetWidget.cpp

#include "FFMpegASSGet.h" #include 
    #include 
    #include 
    #include 
    #include "UIBase/UIGlobalTool.h" FFMpegAssGetWidget::FFMpegAssGetWidget(QWidget *parent) :UIBaseWindow(parent) { av_register_all(); avcodec_register_all(); initUi(); m_FFMpegAssThread = new FFMpegAssThread; QObject::connect(m_FFMpegAssThread, SIGNAL(sendCurrentProgress(qreal)), \ this, SLOT(onRecvConvertProgress(qreal))); } FFMpegAssGetWidget::~FFMpegAssGetWidget() { } void FFMpegAssGetWidget::setAssPathCount(int count) { QVBoxLayout *layout = new QVBoxLayout(m_AssSubtitleWidget); for (int i=0; i<count; ++i) { QLabel *destVideoTag = new QLabel(tr("字幕文件目录:")); QLineEdit *destFileNamePathLineEdit = new QLineEdit; QPushButton *destBrowseButton = new QPushButton(tr("浏览")); destBrowseButton->setObjectName(QString::number(i)); QObject::connect(destBrowseButton, SIGNAL(clicked()), this, SLOT(onClickedDestBrowseButton())); m_DecodecLineEditList.push_back(destFileNamePathLineEdit); m_DestBrowseButtonList.push_back(destBrowseButton); QHBoxLayout *row2Layout = new QHBoxLayout; row2Layout->addWidget(destVideoTag, 1); row2Layout->addWidget(destFileNamePathLineEdit, 4); row2Layout->addWidget(destBrowseButton, 1); g_GlobalTool->addShadowEffect(destBrowseButton); layout->addLayout(row2Layout); } } void FFMpegAssGetWidget::initUi(void) { m_SrcFileNamePathLineEdit = new QLineEdit; m_BrowseButton = new QPushButton(tr("浏览")); QObject::connect(m_BrowseButton, SIGNAL(clicked()), this, SLOT(onClickedBrowseButton())); QLabel *srcVideoTag = new QLabel(tr("视频文件目录:")); m_DecodecLineEditList.clear(); m_DestBrowseButtonList.clear(); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addSpacing(30); // Row1 Layout QHBoxLayout *row1Layout = new QHBoxLayout; row1Layout->addWidget(srcVideoTag, 1); row1Layout->addWidget(m_SrcFileNamePathLineEdit, 4); row1Layout->addWidget(m_BrowseButton, 1); g_GlobalTool->addShadowEffect(m_BrowseButton); // Row2 Layout m_AssSubtitleWidget = new QWidget; // Row3 Layout QHBoxLayout *row3Layout = new QHBoxLayout; m_ConvertButton = new QPushButton(tr("转换")); QObject::connect(m_ConvertButton, SIGNAL(clicked()), this, SLOT(onClickedConvertButton())); g_GlobalTool->addShadowEffect(m_ConvertButton); row3Layout->addStretch(); row3Layout->addWidget(m_ConvertButton); // Row4 Layout m_ProgressBar = new QProgressBar; QHBoxLayout *row4Layout = new QHBoxLayout; row4Layout->addWidget(m_ProgressBar); m_ProgressBar->setMinimum(0); m_ProgressBar->setMaximum(100); mainLayout->addLayout(row1Layout); mainLayout->addWidget(m_AssSubtitleWidget); mainLayout->addLayout(row3Layout); mainLayout->addLayout(row4Layout); mainLayout->addStretch(); } void FFMpegAssGetWidget::onClickedBrowseButton(void) { QString fileName = QFileDialog::getOpenFileName(this, "Open File", "./", tr("Video (*.mkv)")); if (fileName.isEmpty()) return; m_SrcFileNamePathLineEdit->setText(fileName); QString srcFileName = m_SrcFileNamePathLineEdit->text(); m_FFMpegAssThread->openVideoFile(srcFileName); int count = m_FFMpegAssThread->getSubtitleStreamCount(); setAssPathCount(count); } void FFMpegAssGetWidget::onClickedDestBrowseButton(void) { QString fileName = QFileDialog::getSaveFileName(this, "Open File", "./", tr("Video (*.ass)")); if (fileName.isEmpty()) return; int number = sender()->objectName().toInt(); m_DecodecLineEditList[number]->setText(fileName); } void FFMpegAssGetWidget::onClickedConvertButton(void) { int count = m_FFMpegAssThread->getSubtitleStreamCount(); QStringList fileNameList; for (int i=0; i<count; ++i) { QString assFileName = m_DecodecLineEditList.at(i)->text(); fileNameList << assFileName; } m_FFMpegAssThread->openAssSaveFile(fileNameList); m_FFMpegAssThread->writeHeader(); if (!m_FFMpegAssThread->isRunning()) m_FFMpegAssThread->start(); } void FFMpegAssGetWidget::onRecvConvertProgress(qreal value) { m_ProgressBar->setValue(value); if (value >= 100) m_FFMpegAssThread->closeVideoFile(); } 

线程-FFMpegAssThread.h

#ifndef FFMPEG_ASS_THREAD_H #define FFMPEG_ASS_THREAD_H #include 
    #include 
    #include 
    extern "C"{ #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    } class FFMpegAssThread : public QThread { Q_OBJECT public: FFMpegAssThread(QObject *parent = nullptr); ~FFMpegAssThread(); void run(void) override; // 打开文件 void openVideoFile(QString fileName); // 获取字幕流数目 int getSubtitleStreamCount(void); // 打开ASS文件 void openAssSaveFile(QStringList pathList); // 关闭文件 void closeVideoFile(void); // 写入头 void writeHeader(void); private: AVFormatContext *m_FormatContext = nullptr; int m_SubtitleStream[20]; AVCodecContext *m_SubtitleCodecContext[20]; int m_SubtitleCount; int m_TotalTime; // ms QList 
  
    m_FileList; signals: 
   void sendCurrentProgress(qreal); }; 
   #endif 
  

线程-FFMpegAssThread.cpp

#include "FFMpegASSThread.h" FFMpegAssThread::FFMpegAssThread(QObject *parent) { m_SubtitleCount = 0; } FFMpegAssThread::~FFMpegAssThread() { } void FFMpegAssThread::run(void) { while (!this->isInterruptionRequested()) { if (m_FormatContext != nullptr) { AVPacket pkt; av_init_packet(&pkt); // 获取一帧数据 int result = av_read_frame(m_FormatContext, &pkt); if (result < 0) { emit sendCurrentProgress(100); av_packet_unref(&pkt); break; } bool needDecodec = false; AVCodecContext *codecContext = nullptr; int index = -1; for (int i=0; i 
  
    if (m_SubtitleStream[i] == pkt.stream_index) { needDecodec = 
   true; index = i; codecContext = m_SubtitleCodecContext[i]; 
   break; } } 
   if (!needDecodec) { av_packet_unref(&pkt); 
   continue; } 
   int streamIndex = pkt.stream_index; AVRational rational = m_FormatContext->streams[streamIndex]->time_base; qreal value = pkt.pts * 
   1.0 / rational.den * rational.num * 
   1000 / m_TotalTime * 
   100; emit sendCurrentProgress(value); 
   // 解码 AVSubtitle subtitle; 
   int gotSub = 
   0; result = avcodec_decode_subtitle2(codecContext, &subtitle, &gotSub, &pkt); 
   if (result < 
   0) { av_packet_unref(&pkt); 
   continue; } 
   if (gotSub > 
   0) { 
   int number = subtitle.num_rects; 
   for ( 
   int i= 
   0; i 
   
     write(subtitle.rects[i]->ass, 
    strlen(subtitle.rects[ 
    0]->ass)); } avsubtitle_free(&subtitle); } av_packet_unref(&pkt); } 
    else QThread::msleep( 
    10); } } 
    int FFMpegAssThread::getSubtitleStreamCount( 
    void) { 
    return m_SubtitleCount; } 
    void FFMpegAssThread::openVideoFile(QString fileName) { 
    // 打开视频文件 
    int result = avformat_open_input(&m_FormatContext, fileName.toLocal8Bit().data(), 
    nullptr, 
    nullptr); 
    if (result < 
    0) 
    return; 
    // 查找流信息 result = avformat_find_stream_info(m_FormatContext, 
    nullptr); 
    if (result < 
    0) 
    return; 
    // 获取字幕流 
    int streamCount = m_FormatContext->nb_streams; m_SubtitleCount = 
    0; 
    for ( 
    int i= 
    0; i 
    
      if (m_FormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { m_SubtitleStream[m_SubtitleCount++] = i; 
     continue; } } 
     if (m_SubtitleCount == 
     0) { avformat_close_input(&m_FormatContext); 
     return; } 
     // 获取视频总时长 m_TotalTime = m_FormatContext->duration * 
     1.0 / AV_TIME_BASE * 
     1000; 
     // 获取解码器 
     for ( 
     int i= 
     0; i 
     
       streams[m_SubtitleStream[i]]->codec; 
      if (codecContext->codec_id == AV_CODEC_ID_ASS) { AVCodec *codec = avcodec_find_decoder(codecContext->codec_id); result = avcodec_open2(codecContext, codec, 
      nullptr); 
      if (result < 
      0) 
      continue; m_SubtitleCodecContext[i] = codecContext; } } } 
      void FFMpegAssThread::openAssSaveFile(QStringList pathList) { 
      for ( 
      int i= 
      0; i 
      
        new QFile(pathList.at(i)); file->open(QFile::WriteOnly); m_FileList.push_back(file); } } 
       void FFMpegAssThread::writeHeader( 
       void) { 
       for ( 
       int i= 
       0; i 
       
         write(( 
        const 
        char*)m_SubtitleCodecContext[i]->subtitle_header, \ m_SubtitleCodecContext[i]->subtitle_header_size); } } 
        void FFMpegAssThread::closeVideoFile( 
        void) { avformat_close_input(&m_FormatContext); m_SubtitleCount = 
        0; 
        for ( 
        int i= 
        0; i 
        
          close(); 
         delete file; } m_FileList.clear(); } 
         
        
       
      
     
    
  

ASS字幕截图

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/209417.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 上午9:17
下一篇 2026年3月19日 上午9:17


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号