Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/twitcasting.py (Python)

Code before:
            'title': '南波一海のhear_here “ナタリー望月哲さんに聞く編集と「渋谷系狂騒曲」”',
            'duration': 6964.599334,
        },
    }]

    def _real_extract(self, url):
        uploader_id, video_id = self._match_valid_url(url).groups()

        video_password = self.get_param('videopassword')
        request_data = None
        if video_password:
            request_data = urlencode_postdata({
                'password': video_password,
            })
        webpage = self._download_webpage(
            url, video_id, data=request_data,
            headers={'Origin': 'https://twitcasting.tv'})

        title = (clean_html(get_element_by_id('movietitle', webpage))
                 or self._html_search_meta(['og:title', 'twitter:title'], webpage, fatal=True))

        video_js_data = try_get(
            webpage,
            lambda x: self._parse_json(self._search_regex(
                r"data-movie-playlist='([^']+?)'",
                x, 'movie playlist', default=None), video_id)["2"], list)

        stream_server_data = self._download_json(
            'https://twitcasting.tv/streamserver.php?target=%s&mode=client' % uploader_id, video_id,
            'Downloading live info', fatal=False)

        is_live = 'data-status="online"' in webpage or traverse_obj(stream_server_data, ('movie', 'live'))
        if not traverse_obj(stream_server_data, 'llfmp4') and is_live:
            self.raise_login_required(method='cookies')

        def find_dmu(x):
            data_movie_url = self._search_regex(
                r'data-movie-url=(["\'])(?P<url>(?:(?!\1).)+)\1',
                x, 'm3u8 url', group='url', default=None)
            if data_movie_url:
                return [data_movie_url]

        m3u8_urls = try_get(
            webpage,
            (find_dmu,
             lambda x: traverse_obj(video_js_data, (..., 'source', 'url')),
             lambda x: ['https://twitcasting.tv/%s/metastream.m3u8' % uploader_id]
                if is_live else None),
            list)
        if not m3u8_urls:
            raise ExtractorError('Failed to get m3u8 playlist')

Review comment (suggestion):
```suggestion
        video_js_data = try_get(
            webpage,
            lambda x: self._parse_json(self._search_regex(
                r'data-movie-playlist=\'([^\']+?)\'',
                x, 'movie playlist', default=None), video_id)['2'], list)
```
I prefer using `'` and just escaping the other single quotes present in the pattern.


Code after:
        'url': 'https://twitcasting.tv/loft_heaven/movie/685979292',
        'info_dict': {
            'id': '685979292',
            'ext': 'mp4',
            'title': '南波一海のhear_here “ナタリー望月哲さんに聞く編集と「渋谷系狂騒曲」”',
            'duration': 6964.599334,
        },
        'playlist_mincount': 2,
    }]

    def _real_extract(self, url):
        uploader_id, video_id = self._match_valid_url(url).groups()

        video_password = self.get_param('videopassword')
        request_data = None
        if video_password:
            request_data = urlencode_postdata({
                'password': video_password,
            })
        webpage = self._download_webpage(
            url, video_id, data=request_data,
            headers={'Origin': 'https://twitcasting.tv'})

        title = (clean_html(get_element_by_id('movietitle', webpage))
                 or self._html_search_meta(['og:title', 'twitter:title'], webpage, fatal=True))

        video_js_data = try_get(
            webpage,
            lambda x: self._parse_json(self._search_regex(
                r'data-movie-playlist=\'([^\']+?)\'',
                x, 'movie playlist', default=None), video_id)['2'], list)

        thumbnail = traverse_obj(video_js_data, (0, 'thumbnailUrl')) or self._og_search_thumbnail(webpage)
        description = clean_html(get_element_by_id(
            'authorcomment', webpage)) or self._html_search_meta(
            ['description', 'og:description', 'twitter:description'], webpage)
        duration = (try_get(video_js_data, lambda x: sum(float_or_none(y.get('duration')) for y in x) / 1000)
                    or parse_duration(clean_html(get_element_by_class('tw-player-duration-time', webpage))))
        view_count = str_to_int(self._search_regex(
            (r'Total\s*:\s*([\d,]+)\s*Views', r'総視聴者\s*:\s*([\d,]+)\s*</'), webpage, 'views', None))
        timestamp = unified_timestamp(self._search_regex(
            r'data-toggle="true"[^>]+datetime="([^"]+)"',
            webpage, 'datetime', None))

        stream_server_data = self._download_json(
            'https://twitcasting.tv/streamserver.php?target=%s&mode=client' % uploader_id, video_id,
            'Downloading live info', fatal=False)

        is_live = 'data-status="online"' in webpage
        if not traverse_obj(stream_server_data, 'llfmp4') and is_live:
            self.raise_login_required(method='cookies')