【问题标题】:parsing .srt files解析 .srt 文件
【发布时间】:2012-07-25 21:45:10
【问题描述】:
1
00:00:00,074 --> 00:00:02,564
Previously on Breaking Bad...

2
00:00:02,663 --> 00:00:04,393
Words...

我需要用 php 解析 srt 文件,并用变量打印文件中的所有 subs。

我找不到正确的正则表达式。这样做时,我需要获取 id、时间和字幕变量。并且打印时不能没有 array() 等,必须与原始文件中的打印相同。

我的意思是我必须像这样打印;

$number <br> (e.g. 1)
$time <br> (e.g. 00:00:00,074 --> 00:00:02,564)
$subtitle <br> (e.g. Previously on Breaking Bad...)

顺便说一句,我有这个代码。但它没有看到线条。它必须被编辑,但如何?

$srt_file = file('test.srt',FILE_IGNORE_NEW_LINES);
$regex = "/^(\d)+ ([\d]+:[\d]+:[\d]+,[\d]+) --> ([\d]+:[\d]+:[\d]+,[\d]+) (\w.+)/";

foreach($srt_file as $srt){

    preg_match($regex,$srt,$srt_lines);

    print_r($srt_lines);
    echo '<br />';

}

【问题讨论】:

  • 好像网上已经有一些关于这个问题的资料,像github.com/delphiki/SubRip-File-Parser这样的库,你可能想避免重新发明轮子。如果您有疑问,请谷歌“解析 srt 文件 php”;)。
  • 我用谷歌搜索了很多次。没有好的结果。一些结果有效,但不打印所有字幕并显示 array() 东西。顺便说一句,这会创建一个新的 srt 文件等。这不是我想要的吗?我必须用 php 打印 srt 文件的全部内容,与 srt 文件中的内容完全相同。但是在这样做时,我必须有一个循环和 3 个变量才能使一些工作。
  • 我链接的库将 srt 文件转换为对象,因此从这个起点你可以对这些对象做任何你想做的事情,主页上的代码只是你可以做的一个示例。
  • 现在非常容易做到这一点。我输入了 2021 年的答案

标签: php parsing srt


【解决方案1】:

这是一个简短的状态机,用于逐行解析 SRT 文件:

define('SRT_STATE_SUBNUMBER', 0);
define('SRT_STATE_TIME',      1);
define('SRT_STATE_TEXT',      2);
define('SRT_STATE_BLANK',     3);

$lines   = file('test.srt');

$subs    = array();
$state   = SRT_STATE_SUBNUMBER;
$subNum  = 0;
$subText = '';
$subTime = '';

foreach($lines as $line) {
    switch($state) {
        case SRT_STATE_SUBNUMBER:
            $subNum = trim($line);
            $state  = SRT_STATE_TIME;
            break;

        case SRT_STATE_TIME:
            $subTime = trim($line);
            $state   = SRT_STATE_TEXT;
            break;

        case SRT_STATE_TEXT:
            if (trim($line) == '') {
                $sub = new stdClass;
                $sub->number = $subNum;
                list($sub->startTime, $sub->stopTime) = explode(' --> ', $subTime);
                $sub->text   = $subText;
                $subText     = '';
                $state       = SRT_STATE_SUBNUMBER;

                $subs[]      = $sub;
            } else {
                $subText .= $line;
            }
            break;
    }
}

if ($state == SRT_STATE_TEXT) {
    // if file was missing the trailing newlines, we'll be in this
    // state here.  Append the last read text and add the last sub.
    $sub->text = $subText;
    $subs[] = $sub;
}

print_r($subs);

结果:

Array
(
    [0] => stdClass Object
        (
            [number] => 1
            [stopTime] => 00:00:24,400
            [startTime] => 00:00:20,000
            [text] => Altocumulus clouds occur between six thousand
        )

    [1] => stdClass Object
        (
            [number] => 2
            [stopTime] => 00:00:27,800
            [startTime] => 00:00:24,600
            [text] => and twenty thousand feet above ground level.
        )

)

然后您可以遍历 subs 数组或通过数组偏移量访问它们:

echo $subs[0]->number . ' says ' . $subs[0]->text . "\n";

通过循环遍历每个子并显示它来显示所有子:

foreach($subs as $sub) {
    echo $sub->number . ' begins at ' . $sub->startTime .
         ' and ends at ' . $sub->stopTime . '.  The text is: <br /><pre>' .
         $sub->text . "</pre><br />\n";
}

延伸阅读:SubRip Text File Format

【讨论】:

  • 这很好。但以阵列外观显示。我试过你的最后一个代码,它只显示第一个字幕。一切都好。但是我怎样才能像 srt 文件的视图一样打印所有字幕?我的意思是逐行。并且没有其他功能变薄。
  • 是的,它创建了一个数组,其中数组中的每个条目都是 SRT 文件中的一个条目。我稍后会编辑帖子。
  • @user1447165 现在在底部查看额外的 foreach 循环。这会遍历每个并显​​示它。
  • 好的。这没有什么问题。非常感谢。
  • 这不会将最后的 $sub 添加到数组中,除非文件末尾有 两个 空行。我会在$lines = 之后添加$lines[] = ''; 作为解决方法。
【解决方案2】:

这不匹配,因为您的 $srt_file 数组可能如下所示:

Array
([0] => '1',
[1] => '00:00:00,074 --> 00:00:02,564',
[2] => 'Previously on Breaking Bad...'.
[3] => '',
[4] => '2',
...
)

您的正则表达式不会匹配任何这些元素。

如果您的意图是将整个文件读入一个长的 memory-hog-of-a-string,然后使用 file_get_contents 将整个文件内容读入一个字符串。然后使用 preg_match_all 获取所有正则表达式匹配。

否则,您可能会尝试遍历数组并尝试匹配各种正则表达式模式以确定该行是 id、时间范围还是文本,并适当地执行操作。显然,您可能还需要一些逻辑来确保您以正确的顺序获取值(id,然后是时间范围,然后是文本)。

【讨论】:

    【解决方案3】:

    使用 array_chunk()file() 数组分成 4 个块,然后省略最后一个条目,因为它是一个空行,如下所示:

    foreach( array_chunk( file( 'test.srt'), 4) as $entry) {
        list( $number, $time, $subtitle) = $entry;
        echo $number . '<br />';
        echo $time . '<br />';
        echo $subtitle . '<br />';
    }
    

    【讨论】:

    • 这不起作用的唯一原因是因为 SubRip 格式说你可以有 1 行或多行文本,以空行结尾。
    • 感谢您提供的信息 - 从 OP 的示例中不会知道这一点。
    • 顺便说一句,我注意到了一些东西。它没有捕捉到某些字幕的 ID、文本和时间。为什么会这样?
    • 我喜欢这个解决方案,比我的要短得多。我想您可能会变得棘手并检查最后一个条目是否不是空行,您可以从循环内的数组中弹出一个元素,直到您确实碰到空行并重新对齐字幕#。
    【解决方案4】:

    我创建了一个类来将 .srt 文件转换为数组。 数组的每个条目都有以下属性:

    • id:一个数字,代表字幕(2)的id
    • start:float,以秒为单位的开始时间(24.443)
    • end: float,结束时间,以秒为单位 (27.647)
    • startString:人类可读格式的开始时间 (00:00:24.443)
    • endString:人类可读格式的结束时间 (00:00:24.647)
    • duration:字幕的时长,单位为毫秒(3204)
    • 文本:字幕文本(孔雀统治公门城。

    代码是php7:

    <?php
    
    namespace VideoSubtitles\Srt;
    
    
    class SrtToArrayTool
    {
    
    
        public static function getArrayByFile(string $file): array
        {
    
            $ret = [];
    
            $gen = function ($filename) {
                $file = fopen($filename, 'r');
                while (($line = fgets($file)) !== false) {
                    yield rtrim($line);
                }
                fclose($file);
            };
    
            $c = 0;
            $item = [];
            $text = '';
            $n = 0;
            foreach ($gen($file) as $line) {
    
                if ('' !== $line) {
                    if (0 === $n) {
                        $item['id'] = $line;
                        $n++;
                    }
                    elseif (1 === $n) {
                        $p = explode('-->', $line);
                        $start = str_replace(',', '.', trim($p[0]));
                        $end = str_replace(',', '.', trim($p[1]));
                        $startTime = self::toMilliSeconds(str_replace('.', ':', $start));
                        $endTime = self::toMilliSeconds(str_replace('.', ':', $end));
                        $item['start'] = $startTime / 1000;
                        $item['end'] = $endTime / 1000;
                        $item['startString'] = $start;
                        $item['endString'] = $end;
                        $item['duration'] = $endTime - $startTime;
                        $n++;
                    }
                    else {
                        if ($n >= 2) {
                            if ('' !== $text) {
                                $text .= PHP_EOL;
                            }
                            $text .= $line;
                        }
                    }
                }
                else {
                    if (0 !== $n) {
                        $item['text'] = $text;
                        $ret[] = $item;
                        $text = '';
                        $n = 0;
                    }
                }
                $c++;
            }
            return $ret;
        }
    
    
        private static function toMilliSeconds(string $duration): int
        {
            $p = explode(':', $duration);
            return (int)$p[0] * 3600000 + (int)$p[1] * 60000 + (int)$p[2] * 1000 + (int)$p[3];
        }
    
    
    }
    

    或在这里查看:https://github.com/lingtalfi/VideoSubtitles

    【讨论】:

    • 这是非常复杂且不可靠的。你现在可以用 2 行代码完成整个事情。
    【解决方案5】:

    你可以使用这个项目:https://github.com/captioning/captioning

    示例代码:

    <?php
    require_once __DIR__.'/../vendor/autoload.php';
    
    use Captioning\Format\SubripFile;
    
    try {
        $file = new SubripFile('your_file.srt');
    
        foreach ($file->getCues() as $line) {
            echo 'start: ' . $line->getStart() . "<br />\n";
            echo 'stop: ' . $line->getStop() . "<br />\n";
            echo 'startMS: ' . $line->getStartMS() . "<br />\n";
            echo 'stopMS: ' . $line->getStopMS() . "<br />\n";
            echo 'text: ' . $line->getText() . "<br />\n";
            echo "=====================<br />\n";
        }
    
    } catch(Exception $e) {
        echo "Error: ".$e->getMessage()."\n";
    }
    

    样本输出:

    > php index.php
    start: 00:01:48,387<br />
    stop: 00:01:53,269<br />
    startMS: 108387<br />
    stopMS: 113269<br />
    text: ┘ç┘à╪د┘ç┘┌»█î ╪▓█î╪▒┘┘ê█î╪│ ╪ذ╪د ┌ر█î┘█î╪ز ╪ذ┘┘ê╪▒█î ┘ê ┌ر╪»┌ر x265
    =====================<br />
    start: 00:02:09,360<br />
    stop: 00:02:12,021<br />
    startMS: 129360<br />
    stopMS: 132021<br />
    text: .┘à╪د ┘╪ذ╪د┘è╪» ╪ز┘┘ç╪د┘è┘è ╪د┘è┘╪ش╪د ╪ذ╪د╪┤┘è┘à -
    ┌╪▒╪د ╪ا<br />
    =====================<br />
    start: 00:02:12,022<br />
    stop: 00:02:14,725<br />
    startMS: 132022<br />
    stopMS: 134725<br />
    text: ..╪د┌»┘ç ┘╛╪»╪▒╪ز -
    .╪د┘ê┘ ┘ç┘è┌┘ê┘é╪ز ┘à╪ز┘ê╪ش┘ç ╪▒┘╪ز┘┘à┘ê┘ ┘┘à┘è╪┤┘ç -<br />
    =====================<br />
    

    【讨论】:

      【解决方案6】:

      这可以通过使用 php 换行符来完成。 我可以成功 让我看看我的代码

      $srt=preg_split("/\\r\\n\\r\\n/",trim($movie->SRT));
                  $result[$i]['IMDBID']=$movie->IMDBID;
                  $result[$i]['TMDBID']=$movie->TMDBID;
      

      这里 $movie->SRT 是在这个问题中发布格式的副标题。 如我们所见,每个时间空间是两个新行, 希望你得到答案。

      【讨论】:

        【解决方案7】:

        简单、自然、琐碎的解决方案

        srt subs 看起来像这样,由两个换行符分隔:

        3
        00:00:07,350 --> 00:00:09,780
        The ability to destroy a planet is
        nothing next to the power of the force
        

        显然你想解析时间,使用Java中已经存在的dateFormat.parse,所以它是即时的。

        class Sub {
            float start;
            String text;
        
            Sub(String block) {
                this.start = null; this.text = null;
                String[] lines = block.split("\n");
                if (lines.length < 3) { return; }
        
                String timey = lines[1].replaceAll(" .+$", "");
                try {
                    DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss,SSS");
                    Date zero = dateFormat.parse("00:00:00,000");
                    Date date = dateFormat.parse(timey);
                    this.start = (float)(date.getTime() - zero.getTime()) / 1000f;
                } catch (ParseException e) {
                    e.printStackTrace();
                }
        
                this.text = TextUtils.join(" ", Arrays.copyOfRange(lines, 2, lines.length) );
            }
        }
        

        显然,要获取文件中的所有子项

            List<Sub> subs = new ArrayList<>();
            String[] tt = fileText.split("\n\n");
            for (String s:tt) { subs.add(new Sub(s)); }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-12-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多