I have noticed that a few videos on YouTube have now started using rich SRV3/VTT transcripts where text changes color/position sort of like an animation. These "animations" are obtained via duplicated cues shown one right after the other with different colors/positions.
In the "dumb" XML format text animations such as changes in position appear as duplicated text. Thus, sometimes we have consecutive segments that are exactly identical (same text, start and duration), and other times we have consecutive segments that have identical text content but start time equal to the end time of the previous segment.
One good example: https://www.youtube.com/watch?v=5F5MKI_jXJw. Here, it looks like almost every single cue is duplicated. Here's the first few snippets:
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bWe are trapped inside \u200b \u200b\u200b\n\u200b\u200b \u200bthis Minecraft house\u200b \u200b\u200b', start=0.0, duration=1.817)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bWe are trapped inside \u200b \u200b\u200b\n\u200b\u200b \u200bthis Minecraft house\u200b \u200b\u200b', start=0.0, duration=1.817)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bfor the next hundred days\u200b \u200b\u200b', start=1.851, duration=1.083)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bfor the next hundred days\u200b \u200b\u200b', start=1.851, duration=1.083)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200buntil we beat realistic Minecraft.\u200b \u200b\u200b', start=2.968, duration=1.983)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200buntil we beat realistic Minecraft.\u200b \u200b\u200b', start=2.968, duration=1.983)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bWhoaaa!\u200b \u200b\u200b', start=5.484, duration=1.0330000000000004)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bWhoaaa!\u200b \u200b\u200b', start=5.484, duration=0.067)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bWhoaaa!\u200b \u200b\u200b', start=5.551, duration=0.067)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bWhoaaa!\u200b \u200b\u200b', start=5.551, duration=0.067)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bWhoaaa!\u200b \u200b\u200b', start=5.618, duration=0.899)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bWhoaaa!\u200b \u200b\u200b', start=5.618, duration=0.899)
...
It is rather annoying to see a bunch of duplicated segments that are obviously an artifact of VTT/SRV3 animations. I have therefore implemented my own deduplication logic that takes the result of YouTubeTranscriptApi().fetch() and tries to dedup according to the couple of patterns I have noted above.
I feel like one deduplicate boolean argument could be added to .fetch() to apply this kind of filtering.
Sharing my current homemade solution in case it helps:
from youtube_transcript_api import FetchedTranscriptSnippet
def dedup_transcript_snippets(raw: list[FetchedTranscriptSnippet]) -> list[FetchedTranscriptSnippet]:
'''
Deduplicate transcript snippets returned by youtube_transcript_api.
YT XML may show duplicated segments in two ways:
1. Exact pairs: identical start/dur/text emitted twice for each cue.
2. Animation frames: same text repeated across consecutive frames as the
karaoke colour-sweep advances (animation metadata is not present in XML
format, but only available in the richer vtt/srv3 formats).
If two consecutive segments share the same text AND either their start/dur
are identical (exact dupe) OR the current start equals the end of the last
discarded segment (frame continuation), the second is dropped. For frame
continuations the first snippet's duration is extended to cover the full
span of the collapsed segments.
'''
snippets: list[FetchedTranscriptSnippet] = []
prev: FetchedTranscriptSnippet|None = None
prev_end: float|None = None
for s in raw:
if prev is not None and s.text == prev.text:
if s.start == prev.start and s.duration == prev.duration:
# Exact dupe
prev = s
prev_end = s.start + s.duration
continue
elif prev_end is not None and s.start - prev_end < 1e-6:
# Likely some text animation continuation
prev = s
prev_end = s.start + s.duration
snippets[-1].duration = prev_end - snippets[-1].start
continue
else:
prev_end = None
snippets.append(s)
prev = s
return snippets
if __name__ == '__main__':
from youtube_transcript_api import YouTubeTranscriptApi
ytt_api = YouTubeTranscriptApi()
ts = ytt_api.fetch()
deduped = dedup_transcript_snippets(ts.snippets)
for s in deduped:
print(s)
The result is as follows:
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bWe are trapped inside \u200b \u200b\u200b\n\u200b\u200b \u200bthis Minecraft house\u200b \u200b\u200b', start=0.0, duration=1.817)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bfor the next hundred days\u200b \u200b\u200b', start=1.851, duration=1.083)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200buntil we beat realistic Minecraft.\u200b \u200b\u200b', start=2.968, duration=1.983)
FetchedTranscriptSnippet(text='\u200b\u200b\u200b \u200bWhoaaa!\u200b \u200b\u200b', start=5.484, duration=1.0330000000000004)
...
I have noticed that a few videos on YouTube have now started using rich SRV3/VTT transcripts where text changes color/position sort of like an animation. These "animations" are obtained via duplicated cues shown one right after the other with different colors/positions.
In the "dumb" XML format text animations such as changes in position appear as duplicated text. Thus, sometimes we have consecutive segments that are exactly identical (same text, start and duration), and other times we have consecutive segments that have identical text content but start time equal to the end time of the previous segment.
One good example:
https://www.youtube.com/watch?v=5F5MKI_jXJw. Here, it looks like almost every single cue is duplicated. Here's the first few snippets:It is rather annoying to see a bunch of duplicated segments that are obviously an artifact of VTT/SRV3 animations. I have therefore implemented my own deduplication logic that takes the result of
YouTubeTranscriptApi().fetch()and tries to dedup according to the couple of patterns I have noted above.I feel like one
deduplicateboolean argument could be added to.fetch()to apply this kind of filtering.Sharing my current homemade solution in case it helps:
The result is as follows: