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

Code before:
        for timestamp in timestamps or []:
            caption = timestamp.get('Caption')
            start, duration = int_or_none(timestamp.get('Time')), int_or_none(timestamp.get('Duration'))
            if not caption or start is None or duration is None:
                continue
            chapters.append({
                'start_time': start,
                'end_time': start + duration,
                'title': caption
            })
        return chapters

    @staticmethod
    def _extract_mhtml_formats(base_url, timestamps):
        image_frags = {}
        for timestamp in timestamps or []:
            duration = timestamp.get('Duration')
            obj_id, obj_sn = timestamp.get('ObjectIdentifier'), timestamp.get('ObjectSequenceNumber'),
            if timestamp.get('EventTargetType') == 'PowerPoint' and obj_id is not None and obj_sn is not None:
                image_frags.setdefault('slides', []).append({
                    'url': base_url + f'/Pages/Viewer/Image.aspx?id={obj_id}&number={obj_sn}',
                    'duration': duration
                })

            obj_pid, session_id, abs_time = timestamp.get('ObjectPublicIdentifier'), timestamp.get('SessionID'), timestamp.get('AbsoluteTime')
            if all(v is not None for v in (obj_pid, session_id, abs_time)):
                image_frags.setdefault('chapter', []).append({
                    'url': base_url + f'/Pages/Viewer/Thumb.aspx?eventTargetPID={obj_pid}&sessionPID={session_id}&number={obj_sn}&isPrimary=false&absoluteTime={abs_time}',
                    'duration': duration,
                })
        for name, fragments in image_frags.items():
            yield {
                'format_id': name,
                'ext': 'mhtml',
                'protocol': 'mhtml',
                'acodec': 'none',
                'vcodec': 'none',
                'url': 'about:invalid',
                'fragments': fragments
            }

    @staticmethod
    def _json2srt(data, delivery):
        def _gen_lines():
            for i, line in enumerate(data):
                start_time = line['Time']
                duration = line.get('Duration')
                if duration:
                    end_time = start_time + duration
                else:
                    end_time = traverse_obj(data, (i + 1, 'Time')) or delivery['Duration']

Review comment (suggestion):
```suggestion
            if None not in (obj_pid, session_id, abs_time):
```

Code after:
                        'InvocationID': invocation_id,
                        'PlaybackSpeed': 1,
                        'SecondsListened': duration - 1,
                        'SecondsRejected': 0,
                        'StartPosition': 0,
                        'StartReason': 2,
                        'StopReason': None,
                        'StreamID': stream_id,
                        'TimeStamp': timestamp_str,
                        'UpdatesRejected': 0
                    },
                ]}

            self._download_webpage(
                base_url + '/Services/Analytics.svc/AddStreamRequests', video_id,
                fatal=False, data=json.dumps(data).encode('utf8'), headers={'content-type': 'application/json'},
                note='Marking watched', errnote='Unable to mark watched')

    @staticmethod
    def _extract_chapters(timestamps):
        chapters = []
        for timestamp in timestamps or []:
            caption = timestamp.get('Caption')
            start, duration = int_or_none(timestamp.get('Time')), int_or_none(timestamp.get('Duration'))
            if not caption or start is None or duration is None:
                continue
            chapters.append({
                'start_time': start,
                'end_time': start + duration,
                'title': caption
            })
        return chapters

    @staticmethod
    def _extract_mhtml_formats(base_url, timestamps):
        image_frags = {}
        for timestamp in timestamps or []:
            duration = timestamp.get('Duration')
            obj_id, obj_sn = timestamp.get('ObjectIdentifier'), timestamp.get('ObjectSequenceNumber'),
            if timestamp.get('EventTargetType') == 'PowerPoint' and obj_id is not None and obj_sn is not None:
                image_frags.setdefault('slides', []).append({
                    'url': base_url + f'/Pages/Viewer/Image.aspx?id={obj_id}&number={obj_sn}',
                    'duration': duration
                })

            obj_pid, session_id, abs_time = timestamp.get('ObjectPublicIdentifier'), timestamp.get('SessionID'), timestamp.get('AbsoluteTime')
            if None not in (obj_pid, session_id, abs_time):
                image_frags.setdefault('chapter', []).append({
                    'url': base_url + f'/Pages/Viewer/Thumb.aspx?eventTargetPID={obj_pid}&sessionPID={session_id}&number={obj_sn}&isPrimary=false&absoluteTime={abs_time}',
                    'duration': duration,
                })