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

Code before:
        return {
            'id': info['_id'],
            'title': info.get('title') or 'Untitled Broadcast',
            'description': info.get('description'),
            'duration': int_or_none(info.get('length')),
            'thumbnails': thumbnails,
            'uploader': info.get('channel', {}).get('display_name'),
            'uploader_id': info.get('channel', {}).get('name'),
            'timestamp': parse_iso8601(info.get('recorded_at')),
            'view_count': int_or_none(info.get('views')),
            'is_live': is_live,
        }

    @staticmethod
    def _extract_info_gql(info, item_id):
        vod_id = info.get('id') or item_id
        # id backward compatibility for download archives
        if vod_id[0] != 'v':
            vod_id = 'v%s' % vod_id
        thumbnail = url_or_none(info.get('previewThumbnailURL'))
        if thumbnail:
            for p in ('width', 'height'):
                thumbnail = thumbnail.replace('{%s}' % p, '0')

        chapters = list()
        for moment in info.get('moments'):
            moment = moment.get('node')
            if moment.get('type') != 'GAME_CHANGE':
                # TODO: warning or debug log if found unknown moment type
                continue

            momentPosition = int_or_none(moment.get('positionMilliseconds'))
            momentDuration = int_or_none(moment.get('durationMilliseconds'))

            if momentPosition is None or momentDuration is None:
                # TODO: warning or debug log about missing important data
                chapters.clear()
                break

            momentPosition /= 1000
            momentDuration /= 1000

            chapter = {
                'start_time': momentPosition,
                'end_time': momentPosition + momentDuration,
                'title': str_or_none(moment.get('description'))
            }

            chapters.append(chapter)

        return {

Review comment (suggestion):
```suggestion
        for moment in info.get('moments') or []:
```

Code after:
            if start_time is None or duration is None:
                self.report_warning(f'Important chapter information missing for chapter {name}', item_id)
                continue
            yield {
                'start_time': start_time,
                'end_time': start_time + duration,
                'title': name,
            }

    def _extract_info_gql(self, info, item_id):
        vod_id = info.get('id') or item_id
        # id backward compatibility for download archives
        if vod_id[0] != 'v':
            vod_id = 'v%s' % vod_id
        thumbnail = url_or_none(info.get('previewThumbnailURL'))
        if thumbnail:
            for p in ('width', 'height'):
                thumbnail = thumbnail.replace('{%s}' % p, '0')

        return {
            'id': vod_id,
            'title': info.get('title') or 'Untitled Broadcast',
            'description': info.get('description'),
            'duration': int_or_none(info.get('lengthSeconds')),
            'thumbnail': thumbnail,
            'uploader': try_get(info, lambda x: x['owner']['displayName'], compat_str),
            'uploader_id': try_get(info, lambda x: x['owner']['login'], compat_str),
            'timestamp': unified_timestamp(info.get('publishedAt')),
            'view_count': int_or_none(info.get('viewCount')),
            'chapters': list(self._extract_moments(info, item_id)),
        }

    def _real_extract(self, url):
        vod_id = self._match_id(url)

        info = self._download_info(vod_id)
        access_token = self._download_access_token(vod_id, 'video', 'id')

        formats = self._extract_m3u8_formats(
            '%s/vod/%s.m3u8?%s' % (
                self._USHER_BASE, vod_id,
                compat_urllib_parse_urlencode({
                    'allow_source': 'true',
                    'allow_audio_only': 'true',
                    'allow_spectre': 'true',
                    'player': 'twitchweb',
                    'playlist_include_framerate': 'true',
                    'nauth': access_token['value'],
                    'nauthsig': access_token['signature'],
                })),
            vod_id, 'mp4', entry_protocol='m3u8_native')