PHP正则表达式获取youtube视频ID和开始时间

问题描述 投票:0回答:1

我需要从用户可以输入的任何类型的 YouTube 网址中提取

video Id
start time
。我有一个可行的解决方案,但它不正确。

问题:

  • 有人可以帮我修复 preg_match 模式来处理测试中注释的 url 吗?
  • 还有其他类型的 YouTube 网址吗?

我已经检查了this stackoverflow 页面来构建我自己的 youtube url 解析器。

此 preg_match 可以提取

video Id
start time
但无法处理许多不同的 YouTube 网址格式:

preg_match("/[a-zA-Z\/\/:\.]*youtu(?:be.com\/watch\?v=|.be\/)([a-zA-Z0-9\-_]+)(?:[&?\/]t=)?(\d*)(?:[a-zA-Z0-9\/\*\-\_\?\&\;\%\=\.]*)/i", $url, $matches);

此 preg_match 处理许多不同的 YouTube 网址(也许是所有类型?),但不提取开始时间:

preg_match("/^(?:http(?:s)?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|user|shorts)\/))([^\?&\"'>]+)/", $url, $matches);

我已经更改了它并且它对我有用,但我知道我的更改是不正确的,因为我没有正确解析网址的末尾:

preg_match("/^(?:http(?:s)?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|user|shorts)\/))([^\?&\"'>]+)(?:[&?\/]t=)?(\d*)/", $url, $matches);

代码

<?php
declare(strict_types=1);

namespace AppBundle\Value;

class YoutubeVideoData
{
    private function __construct(public ?string $videoId = null, public ?int $time = null)
    {
    }

    public static function fromUrl(string $url): self
    {
        // `#action=share` is not supported
        preg_match("/^(?:http(?:s)?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|user|shorts)\/))([^\?&\"'>]+)(?:[&?\/]t=)?(\d*)/", $url, $matches);

        $videoId = null;
        if (isset($matches[1])) {
            $videoId = $matches[1];
        }

        $time = null;
        if (isset($matches[2]) && $matches[2] !== "") {
            $time = (int) $matches[2];
        }

        return new self($videoId, $time);
    }

}

测试:

<?php

namespace Justimmo\Tests\Value;

use AppBundle\Value\YoutubeVideoData;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

/**
 * @covers \AppBundle\Value\YoutubeVideoData::class
 */
class YoutubeVideoDataTest extends TestCase
{
    #[DataProvider('urlProvider')]
    public function testUrls(string $url, ?string $expectedVideoId, ?int $expectedTime)
    {
        $videoData = YoutubeVideoData::fromUrl($url);

        $this->assertSame($expectedVideoId, $videoData->videoId);
        $this->assertSame($expectedTime, $videoData->time);
    }

    public static function urlProvider(): iterable
    {
        // without https://www
        yield 'youtube_link_1' => ['youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_2' => ['youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_3' => ['youtube.com/vi/dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_4' => ['youtube.com/?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_5' => ['youtube.com/?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_6' => ['youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_7' => ['youtube.com/watch?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_8' => ['youtu.be/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_9' => ['youtube.com/embed/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_10' => ['youtube.com/shorts/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_11' => ['m.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        // without https://
        yield 'youtube_link_12' => ['www.youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_13' => ['www.youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_14' => ['www.youtube.com/vi/dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_15' => ['www.youtube.com/?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_16' => ['www.youtube.com/?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_17' => ['www.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_18' => ['www.youtube.com/watch?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_19' => ['www.youtu.be/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_20' => ['www.youtube.com/embed/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_21' => ['www.youtube.com/shorts/dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        // http
        yield 'youtube_link_22' => ['http://youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_23' => ['http://youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_24' => ['http://youtube.com/vi/dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_25' => ['http://www.youtube.com/?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_26' => ['http://www.youtube.com/?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_27' => ['http://www.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_28' => ['http://www.youtube.com/watch?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_29' => ['http://www.youtu.be/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_30' => ['http://youtube.com/embed/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_31' => ['http://www.youtube.com/shorts/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_32' => ['http://m.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        // https
        yield 'youtube_link_33' => ['https://youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_34' => ['https://youtube.com/v/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_35' => ['https://youtube.com/vi/dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_36' => ['https://www.youtube.com/?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_37' => ['https://www.youtube.com/?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_38' => ['https://www.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_39' => ['https://www.youtube.com/watch?vi=dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        yield 'youtube_link_40' => ['https://www.youtu.be/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_41' => ['https://youtube.com/embed/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_42' => ['https://www.youtube.com/shorts/dE5jPNvLvOk', 'dE5jPNvLvOk', null];
        yield 'youtube_link_43' => ['https://m.youtube.com/watch?v=dE5jPNvLvOk', 'dE5jPNvLvOk', null];

        // with start time
        yield 'youtube_link_44' => ['https://youtube.com/v/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];

        yield 'youtube_link_45' => ['https://youtube.com/v/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];
        yield 'youtube_link_46' => ['https://youtube.com/vi/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];

        yield 'youtube_link_47' => ['https://www.youtube.com/?v=dE5jPNvLvOk&t=30', 'dE5jPNvLvOk', 30];
        yield 'youtube_link_48' => ['https://www.youtube.com/?vi=dE5jPNvLvOk&t=30', 'dE5jPNvLvOk', 30];

        yield 'youtube_link_49' => ['https://www.youtube.com/watch?v=dE5jPNvLvOk&t=30', 'dE5jPNvLvOk', 30];
        yield 'youtube_link_50' => ['https://www.youtube.com/watch?vi=dE5jPNvLvOk&t=30', 'dE5jPNvLvOk', 30];

        yield 'youtube_link_51' => ['https://www.youtu.be/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];
        yield 'youtube_link_52' => ['https://youtube.com/embed/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];
        yield 'youtube_link_53' => ['https://www.youtube.com/shorts/dE5jPNvLvOk?t=30', 'dE5jPNvLvOk', 30];
        yield 'youtube_link_54' => ['https://m.youtube.com/watch?v=dE5jPNvLvOk&t=30', 'dE5jPNvLvOk', 30];

        // with feature
        yield 'youtube_link_55' => ['https://www.youtube.com/watch?dev=inprogress&v=7HCZvhRAk-M&feature=related', '7HCZvhRAk-M', null];

        yield 'youtube_link_56' => ['https://youtube.com/v/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];

        yield 'youtube_link_57' => ['https://youtube.com/v/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
        yield 'youtube_link_58' => ['https://youtube.com/vi/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];

        yield 'youtube_link_59' => ['https://www.youtube.com/?v=dE5jPNvLvOk&feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
        yield 'youtube_link_60' => ['https://www.youtube.com/?vi=dE5jPNvLvOk&feature=youtube_gdata_player', 'dE5jPNvLvOk', null];

        yield 'youtube_link_61' => ['https://www.youtube.com/watch?v=dE5jPNvLvOk&feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
        yield 'youtube_link_62' => ['https://www.youtube.com/watch?vi=dE5jPNvLvOk&feature=youtube_gdata_player', 'dE5jPNvLvOk', null];

        yield 'youtube_link_63' => ['https://www.youtu.be/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
        yield 'youtube_link_64' => ['https://youtube.com/embed/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
        yield 'youtube_link_65' => ['https://www.youtube.com/shorts/dE5jPNvLvOk?feature=youtube_gdata_player', 'dE5jPNvLvOk', null];
        yield 'youtube_link_66' => ['https://m.youtube.com/watch?v=dE5jPNvLvOk&feature=youtube_gdata_player', 'dE5jPNvLvOk', null];

        // do not implemented yet - with #action=share
//        yield 'youtube_link_67' => ['https://youtube.com/v/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//
//        yield 'youtube_link_68' => ['https://youtube.com/v/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//        yield 'youtube_link_69' => ['https://youtube.com/vi/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//
//        yield 'youtube_link_70' => ['https://www.youtube.com/?v=dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//        yield 'youtube_link_71' => ['https://www.youtube.com/?vi=dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//
//        yield 'youtube_link_72' => ['https://www.youtube.com/watch?v=dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//        yield 'youtube_link_73' => ['https://www.youtube.com/watch?vi=dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//
//        yield 'youtube_link_74' => ['https://www.youtu.be/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//        yield 'youtube_link_75' => ['https://youtube.com/embed/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//        yield 'youtube_link_76' => ['https://www.youtube.com/shorts/dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
//        yield 'youtube_link_77' => ['https://m.youtube.com/watch?v=dE5jPNvLvOk#action=share ', 'dE5jPNvLvOk', null];
    }

}
php regex youtube urlparse
1个回答
0
投票

我不太喜欢又长又复杂的正则表达式。它们很难理解,而且太多可能会出错。为什么不采用稍微不同的方法呢?

假设我们采用这样的 youTube URL:

https://www.youtube.com/watch?v=Og40mpl8VNc

视频 ID 为

Og40mpl8VNc
。这是一个 base64 编码 的数字。这是 URL 中唯一经过 Base64 编码的内容。所有其他部分,如
https
www
youtube
com
等)都不是有效的 Base64 编码字符串。也许我们可以用这个?

检查某些内容是否为有效的 Base64 字符串的一个简单方法是对其进行解码和重新编码。那不应该改变字符串。只有

Og40mpl8VNc
可以在不改变的情况下解码和重新编码。

我们可以使用您的

urlProvider()
方法将此检查应用于您所有 Youtube 视频 URL 的所有部分:

$youtubeId = [];
foreach (urlProvider() as $data) {
    foreach (array_reverse(preg_split('/[\/?=#&]/', $data[0])) as $part) {
       if (trim(base64_encode(base64_decode($part, true)) , '=') == $part) {
          $youtubeId[$data[0]] = $part; 
          break;
       }
    }
}

现场演示:https://3v4l.org/KKuRl

我将 URL 拆分为这五个字符:

/?=#&
,以获取所有部分。然后反转这些,因为视频 id 通常位于末尾,然后遍历所有部分寻找有效的 base64 字符串。

现在无可否认,我还没有彻底测试过这一点。这只是一个想法。显然,误报的风险非常小,但我希望这种风险可以忽略不计。

© www.soinside.com 2019 - 2024. All rights reserved.