From 3a0e2011359f859b0c1b0b77c487e82e78ff4c8a Mon Sep 17 00:00:00 2001 From: dwithana Date: Mon, 11 Mar 2024 11:40:38 -0400 Subject: [PATCH 1/2] Add SRT support in Transcript component --- .../lunchroom_manners/lunchroom_manners.srt | 508 ++++++++++++++++++ public/manifests/dev/lunchroom_manners.json | 16 + public/manifests/prod/lunchroom_manners.json | 16 + src/components/Transcript/Transcript.js | 4 +- src/components/Transcript/Transcript.test.js | 18 +- src/services/transcript-parser.js | 142 ++--- src/services/transcript-parser.test.js | 110 +++- src/services/utility-helpers.js | 3 +- src/services/utility-helpers.test.js | 5 + 9 files changed, 737 insertions(+), 85 deletions(-) create mode 100644 public/lunchroom_manners/lunchroom_manners.srt diff --git a/public/lunchroom_manners/lunchroom_manners.srt b/public/lunchroom_manners/lunchroom_manners.srt new file mode 100644 index 00000000..97ff4ba5 --- /dev/null +++ b/public/lunchroom_manners/lunchroom_manners.srt @@ -0,0 +1,508 @@ +1 +00:00:01.200 --> 00:00:21.000 +[music] + +2 +00:00:22.200 --> 00:00:26.600 +Just before lunch one day, a puppet show +was put on at school. + +3 +00:00:26.700 --> 00:00:31.500 +It was called "Mister Bungle Goes to Lunch". + +4 +00:00:31.600 --> 00:00:34.500 +It was fun to watch. + +5 +00:00:36.100 --> 00:00:41.300 +In the puppet show, Mr. Bungle came to the +boys' room on his way to lunch. + +6 +00:00:41.400 --> 00:00:46.200 +He looked at his hands. His hands were dirty +and his hair was messy. + +7 +00:00:46.300 --> 00:00:51.100 +But Mr. Bungle didn't stop to wash his hands +or comb his hair. + +8 +00:00:51.200 --> 00:00:54.900 +He went right to lunch. + +9 +00:00:57.900 --> 00:01:05.700 +Then, instead of getting into line at the +lunchroom, Mr. Bungle pushed everyone aside +and went right to the front. + +10 +00:01:06.000 --> 00:01:11.800 +Even though this made the children laugh, +no one thought that was a fair thing to do. + +11 +00:01:11.900 --> 00:01:22.000 +Then, in the lunchroom, Mr. Bungle was so +clumsy and impolite that he knocked over +everything. And no one wanted to sit next +to him. + +12 +00:01:23.500 --> 00:01:29.000 +And when he finally knocked his own tray +off the table, that was the end of the puppet +show. + +13 +00:01:30.300 --> 00:01:36.300 +The children knew that even though Mr. Bungle +was funny to watch, he wouldn't be much fun +to eat with. + +14 +00:01:36.400 --> 00:01:42.500 +Phil knew that a Mr. Bungle wouldn't have +many friends. He wouldn't want to be like +Mr. Bungle. + +15 +00:01:43.900 --> 00:01:49.100 +Later Miss Brown said it was time to for +the children who ate in the cafeteria to +go to lunch. + +16 +00:01:49.200 --> 00:01:52.500 +She hoped there weren't any Mr. Bungles in +this room. + +17 +00:01:58.500 --> 00:02:03.200 +Phil stopped to return a book to Miss Brown +while his friends went on to the lunchroom. + +18 +00:02:03.700 --> 00:02:05.400 +He would have to catch up with them later. + +19 +00:02:08.500 --> 00:02:13.000 +One his way to catch up with his friends, +Phil almost walked passed the boys' room. + +20 +00:02:13.300 --> 00:02:16.400 +But he stopped and thought we're his hands +clean? + +21 +00:02:16.500 --> 00:02:22.800 +No, they were a little dirty. Phil remembered +that Mr. Bungle didn't wash his hands. + +22 +00:02:22.900 --> 00:02:25.600 +Mr. Bungle's hair was messy too. + +23 +00:02:25.800 --> 00:02:28.000 +Phil didn't want to be like Mr. Bungle. + +24 +00:02:31.200 --> 00:02:36.900 +Inside the boys' room, Phil was surprised +to see some of his friends washing their +hands too. + +25 +00:02:37.300 --> 00:02:40.900 +Phil washed his hands well with lots of soap. + +26 +00:02:45.400 --> 00:02:47.800 +Then he rinsed the soap off. + +27 +00:02:51.700 --> 00:02:58.900 +Phil dried his hands well too. When he was +finished, he threw the paper towel in the +basket where it belonged. + +28 +00:02:59.700 --> 00:03:02.200 +And then he made sure that his hair looked +neat. + +29 +00:03:05.500 --> 00:03:08.500 +Now Phil and his friends were ready for lunch. + +30 +00:03:17.600 --> 00:03:22.300 +There was a line of children waiting to get +into the lunchroom when Phil got there. + +31 +00:03:22.800 --> 00:03:25.100 +He saw some boys he knew at the front of +the line. + +32 +00:03:25.500 --> 00:03:28.600 +They waved for him to go up to the front +with them. + +33 +00:03:29.600 --> 00:03:33.100 +But Phil didn't want to break into line as +Mr. Bungle did. + +34 +00:03:34.300 --> 00:03:38.500 +So Phil went to the end. That was the fair +thing to do. + +35 +00:03:38.600 --> 00:03:41.500 +He would see his other friends inside the +lunchroom. + +36 +00:03:42.600 --> 00:03:46.500 +The line moved very fast, and soon Phil was +inside. + +37 +00:03:47.600 --> 00:03:49.200 +First he picked up his tray. + +38 +00:03:55.400 --> 00:03:57.300 +Then he got his silverware. + +39 +00:03:59.500 --> 00:04:04.300 +He put his knife, fork and spoon neatly on +the tray. + +40 +00:04:05.000 --> 00:04:07.500 +And then he slid his tray along. + +41 +00:04:11.900 --> 00:04:15.300 +He always enjoyed looking at the good food +in the cafeteria. + +42 +00:04:15.400 --> 00:04:18.200 +It tasted good and was good for him too. + +43 +00:04:18.900 --> 00:04:23.200 +Instead of having a sandwich today, Phil +decided to take the hot lunch. + +44 +00:04:26.100 --> 00:04:28.300 +Phil took some bread and butter too. + +45 +00:04:28.500 --> 00:04:31.600 +And he knew what else he wanted: milk. + +46 +00:04:31.700 --> 00:04:35.600 +But Alice took the last carton on the tray. + +47 +00:04:35.700 --> 00:04:40.600 +Maybe there was more more milk, so he said, +"May I please have some milk?" + +48 +00:04:41.100 --> 00:04:45.800 +Phil remembered to say "may I" and "please". +That was very polite. + +49 +00:04:45.900 --> 00:04:48.200 +Yes, there was more milk. + +50 +00:04:51.800 --> 00:04:55.500 +Phil remembered to say "thank you" when he +took the carton of milk. + +51 +00:04:56.700 --> 00:05:01.000 +Phil had good manners. He didn't want to +be like Mr. Bungle in the lunchroom. + +52 +00:05:01.500 --> 00:05:04.400 +Phil didn't want to forget his dessert. + +53 +00:05:05.100 --> 00:05:08.200 +The cake looked delicious [and was huge!]. + +54 +00:05:10.600 --> 00:05:15.300 +At the end of the line, the lunchroom supervisor +said she had noticed how polite Phil was. + +55 +00:05:15.400 --> 00:05:17.000 +And she smiled at him. + +56 +00:05:17.100 --> 00:05:19.400 +She wouldn't smile at a Mr. Bungle. + +57 +00:05:20.000 --> 00:05:22.600 +Phil went to table where his friends were. + +58 +00:05:23.500 --> 00:05:29.700 +He put his tray down carefully, pulled out +his chair quietly and sat down. + +59 +00:05:30.100 --> 00:05:33.600 +He knew his friends would like a noisy Mr. +Bungle at their table. + +60 +00:05:33.800 --> 00:05:36.900 +There was someone Phil liked: Freddy. + +61 +00:05:37.400 --> 00:05:39.700 +He always brought his lunch from home. + +62 +00:05:39.800 --> 00:05:40.700 +It looked good. + +63 +00:05:41.600 --> 00:05:45.000 +Freddy had a sandwich, and apple, a cookie, +and milk. + +64 +00:05:48.300 --> 00:05:52.100 +Before Phil began to eat, he always put a +napkin on his lap. + +65 +00:05:52.400 --> 00:05:53.900 +So did Freddy. + +66 +00:05:55.100 --> 00:05:58.300 +Everyone liked Freddy. He was very polite. + +67 +00:05:58.400 --> 00:06:01.900 +For example, if he had food in his mouth +when someone talked to him, + +68 +00:06:02.000 --> 00:06:07.200 +he always took time to chew the food with +his mouth closed and swallow before he answered. + +69 +00:06:08.900 --> 00:06:12.800 +Phil noticed how straight and tall Freddy +usually sat. + +70 +00:06:12.900 --> 00:06:15.600 +Freddy kept his feet on the floor too. + +71 +00:06:18.200 --> 00:06:21.500 +Phil would rather be like Freddy than like +Mr. Bungle. + +72 +00:06:21.600 --> 00:06:25.400 +Another polite person everyone liked was +Alice. + +73 +00:06:26.700 --> 00:06:33.200 +For example, when Alice sneezed, she covered +her mouth and nose. This protected her friends +at the table from any germs. + +74 +00:06:34.500 --> 00:06:38.000 +While Phil and his friends ate, a boy ran +past their table. + +75 +00:06:38.100 --> 00:06:42.300 +You shouldn't run in the lunchroom. Only +Mr. Bungle would do that. + +76 +00:06:45.800 --> 00:06:49.400 +Phil and his friends wouldn't like to have +a Mr. Bungle at their table. + +77 +00:06:49.600 --> 00:06:52.400 +Then lunchtime wouldn't be as much fun as +it is. + +78 +00:06:53.500 --> 00:06:56.100 +Phil ate slowly and enjoyed his lunch. + +79 +00:06:57.500 --> 00:07:00.400 +Finally, he had eaten everything except his +dessert. + +80 +00:07:01.000 --> 00:07:03.800 +He saved his cake for last. + +81 +00:07:03.900 --> 00:07:07.900 +Only a Mr. Bungle would eat his dessert before +he'd finished the rest of his lunch. + +82 +00:07:08.200 --> 00:07:10.200 +And Phil wan't a Mr. Bungle. + +83 +00:07:11.600 --> 00:07:13.000 +The cake was a lie. + +84 +00:07:16.800 --> 00:07:18.300 +Phil drank his milk carefully. + +85 +00:07:18.800 --> 00:07:22.200 +Some children are messy when they drink milk, +but not Phil. + +86 +00:07:24.600 --> 00:07:30.500 +As each of Phil's friends finished, they +didn't leave the table but waited for all +the others to finish eating too. + +87 +00:07:31.800 --> 00:07:34.500 +Phil was the last one done. + +88 +00:07:34.600 --> 00:07:37.500 +His wiped his mouth and hands carefully with +his napkin. + +89 +00:07:38.500 --> 00:07:40.500 +Then he cleaned the table where he sat. + +90 +00:07:41.200 --> 00:07:43.700 +He didn't want to leave his place at the +table dirty. + +91 +00:07:44.800 --> 00:07:52.500 +Everyone at the table cleaned his own place +well. But look at that table. It was left +very messy. + +92 +00:07:53.900 --> 00:07:56.500 +Phil thought a Mr. Bungle must have sat there. + +93 +00:07:58.200 --> 00:08:04.500 +But Phil didn't want to be like Mr. Bungle +so he put his chair neatly into place. + +94 +00:08:05.500 --> 00:08:08.600 +And his table looked fine. + +95 +00:08:09.100 --> 00:08:12.500 +Not a piece of paper or scrap of food was +left on it. + +96 +00:08:17.000 --> 00:08:19.500 +No Mr. Bungle sat here! + +97 +00:08:21.100 --> 00:08:26.400 +Phil's friends were careful to put their +waste papers and empty milk cartons where +they belonged. + +98 +00:08:27.300 --> 00:08:31.600 +In this way, they helped keep the lunchroom +clean. + +99 +00:08:31.900 --> 00:08:37.700 +Phil was certain that Mr. Bungle wouldn't +put his paper in the waste basket and his +empty carton on the milk tray. + +100 +00:08:38.300 --> 00:08:42.600 +Mr. Bungle probably wouldn't bother to put +his lunch tray in the right place either. + +101 +00:08:42.700 --> 00:08:47.500 +But Phil and his friends did. + +102 +00:08:47.600 --> 00:08:53.500 +Lunch was good today. And then Miss Brown +told Phil and his friends how proud she was +of them. + +103 +00:08:54.000 --> 00:08:57.300 +They had left their table the neatest in +the luchroom. + +104 +00:08:57.400 --> 00:09:02.200 +No one here was a Mr. Bungle. And no one +wanted to be. + +105 +00:09:02.800 --> 00:09:09.200 +Are you like Mr. Bungle? Mr. Bungle is ashamed +because he spoils lunchtime. + +106 +00:09:10.500 --> 00:09:12.000 +Don't be like Mr. Bungle. + +107 +00:09:12.100 --> 00:09:17.600 +Have good lunchtime manners and lunch will +be more fun for everyone. diff --git a/public/manifests/dev/lunchroom_manners.json b/public/manifests/dev/lunchroom_manners.json index 1efd2180..2ad17ab3 100644 --- a/public/manifests/dev/lunchroom_manners.json +++ b/public/manifests/dev/lunchroom_manners.json @@ -410,6 +410,22 @@ } }, "target": "http://localhost:3003/dev/lunchroom_manners/canvas/1" + }, + { + "id": "http://localhost:3003/dev/lunchroom_manners/canvas/1/annotation/srt", + "type": "Annotation", + "motivation": "supplementing", + "body": { + "id": "http://localhost:3003/lunchroom_manners/lunchroom_manners.srt", + "type": "Text", + "format": "application/x-subrip", + "label": { + "en": [ + "SRT Transcript (machine-generated)" + ] + } + }, + "target": "http://localhost:3003/dev/lunchroom_manners/canvas/1" } ] } diff --git a/public/manifests/prod/lunchroom_manners.json b/public/manifests/prod/lunchroom_manners.json index 9269a3b7..c28c6f37 100644 --- a/public/manifests/prod/lunchroom_manners.json +++ b/public/manifests/prod/lunchroom_manners.json @@ -282,6 +282,22 @@ } }, "target": "https://iiif-react-media-player.netlify.app/prod/lunchroom_manners/canvas/1" + }, + { + "id": "https://iiif-react-media-player.netlify.app/prod/lunchroom_manners/canvas/1/annotation/srt", + "type": "Annotation", + "motivation": "supplementing", + "body": { + "id": "https://iiif-react-media-player.netlify.app/lunchroom_manners/lunchroom_manners.srt", + "type": "Text", + "format": "application/x-subrip", + "label": { + "en": [ + "SRT Transcript (machine-generated)" + ] + } + }, + "target": "https://iiif-react-media-player.netlify.app/prod/lunchroom_manners/canvas/1" } ] } diff --git a/src/components/Transcript/Transcript.js b/src/components/Transcript/Transcript.js index ae1a2dee..d29e03eb 100644 --- a/src/components/Transcript/Transcript.js +++ b/src/components/Transcript/Transcript.js @@ -4,7 +4,7 @@ import 'lodash'; import TanscriptSelector from './TranscriptMenu/TranscriptSelector'; import { autoScroll, checkSrcRange, getMediaFragment, timeToHHmmss } from '@Services/utility-helpers'; import { - getSupplementingAnnotations, + readSupplementingAnnotations, parseTranscriptData, sanitizeTranscripts, TRANSCRIPT_TYPES, @@ -143,7 +143,7 @@ const Transcript = ({ playerID, manifestUrl, transcripts = [] }) => { allTranscripts = await sanitizeTranscripts(transcripts); } else if (manifestUrl) { // Read supplementing annotations from the given manifest - allTranscripts = await getSupplementingAnnotations(manifestUrl); + allTranscripts = await readSupplementingAnnotations(manifestUrl); } setTranscriptsList(allTranscripts); initTranscriptData(allTranscripts); diff --git a/src/components/Transcript/Transcript.test.js b/src/components/Transcript/Transcript.test.js index 69addbd8..9940399f 100644 --- a/src/components/Transcript/Transcript.test.js +++ b/src/components/Transcript/Transcript.test.js @@ -551,8 +551,8 @@ describe('Transcript component', () => { ] } ]; - const getSupplementingAnnotationsMock = jest - .spyOn(transcriptParser, 'getSupplementingAnnotations') + const readSupplementingAnnotationsMock = jest + .spyOn(transcriptParser, 'readSupplementingAnnotations') .mockReturnValue(transcriptsList); const parseTranscriptMock = jest @@ -573,7 +573,7 @@ describe('Transcript component', () => { await act(() => Promise.resolve()); await waitFor(() => { - expect(getSupplementingAnnotationsMock).toHaveBeenCalledTimes(1); + expect(readSupplementingAnnotationsMock).toHaveBeenCalledTimes(1); expect(parseTranscriptMock).toHaveBeenCalledTimes(1); expect(screen.queryByTestId('transcript-selector')).toBeInTheDocument(); expect(screen.queryByTestId('transcript_content_1')).toBeInTheDocument(); @@ -593,8 +593,8 @@ describe('Transcript component', () => { manifestUrl: 'http://example.com/manifest.json' }; - const getSupplementingAnnotationsMock = jest - .spyOn(transcriptParser, 'getSupplementingAnnotations') + const readSupplementingAnnotationsMock = jest + .spyOn(transcriptParser, 'readSupplementingAnnotations') .mockReturnValue([]); const parseTranscriptMock = jest @@ -610,7 +610,7 @@ describe('Transcript component', () => { await act(() => Promise.resolve()); await waitFor(() => { - expect(getSupplementingAnnotationsMock).toHaveBeenCalledTimes(1); + expect(readSupplementingAnnotationsMock).toHaveBeenCalledTimes(1); expect(parseTranscriptMock).not.toHaveBeenCalled(); expect(screen.queryByTestId('transcript-selector')).not.toBeInTheDocument(); expect(screen.queryByTestId('transcript_content_0')).toBeInTheDocument(); @@ -631,8 +631,8 @@ describe('Transcript component', () => { }], }; - const getSupplementingAnnotationsMock = jest - .spyOn(transcriptParser, 'getSupplementingAnnotations'); + const readSupplementingAnnotationsMock = jest + .spyOn(transcriptParser, 'readSupplementingAnnotations'); render( @@ -643,7 +643,7 @@ describe('Transcript component', () => { await act(() => Promise.resolve()); await waitFor(() => { - expect(getSupplementingAnnotationsMock).not.toHaveBeenCalled(); + expect(readSupplementingAnnotationsMock).not.toHaveBeenCalled(); expect(screen.queryByTestId('transcript-selector')).not.toBeInTheDocument(); expect(screen.queryByTestId('transcript_content_0')).toBeInTheDocument(); expect(screen.queryByTestId('no-transcript')).toBeInTheDocument(); diff --git a/src/services/transcript-parser.js b/src/services/transcript-parser.js index 4eef5eac..a11cf628 100644 --- a/src/services/transcript-parser.js +++ b/src/services/transcript-parser.js @@ -12,11 +12,21 @@ import { parseSequences, } from './utility-helpers'; -const TRANSCRIPT_MIME_TYPES = [ - { type: 'application/json', ext: 'json' }, - { type: 'text/vtt', ext: 'vtt' }, - { type: 'text/plain', ext: 'txt' }, - { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', ext: 'docx' } +// ENum for supported transcript MIME types +const TRANSCRIPT_MIME_TYPES = { + webvtt: 'text/vtt', + srt: 'application/x-subrip', + text: 'text/plain', + json: 'application/json', + docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' +}; + +const TRANSCRIPT_MIME_EXTENSIONS = [ + { type: TRANSCRIPT_MIME_TYPES.json, ext: 'json' }, + { type: TRANSCRIPT_MIME_TYPES.webvtt, ext: 'vtt' }, + { type: TRANSCRIPT_MIME_TYPES.text, ext: 'txt' }, + { type: TRANSCRIPT_MIME_TYPES.docx, ext: 'docx' }, + { type: TRANSCRIPT_MIME_TYPES.srt, ext: 'srt' } ]; // ENum for describing transcript types include invalid and no transcript info @@ -29,7 +39,7 @@ export const TRANSCRIPT_TYPES = { noSupport: -2, invalid: -1, noTranscript: 0, t * @returns {Array} array of supplementing annotations for transcripts for all * canvases in the Manifest */ -export async function getSupplementingAnnotations(manifestURL, title = '') { +export async function readSupplementingAnnotations(manifestURL, title = '') { let data = await fetch(manifestURL) .then(function (response) { const fileType = response.headers.get('Content-Type'); @@ -101,7 +111,7 @@ export async function getSupplementingAnnotations(manifestURL, title = '') { }) .catch(error => { console.error( - 'transcript-parser -> getSupplementingAnnotations() -> error fetching transcript resource at, ' + 'transcript-parser -> readSupplementingAnnotations() -> error fetching transcript resource at, ' , manifestURL ); return []; @@ -140,7 +150,7 @@ export async function sanitizeTranscripts(transcripts) { // For each item in the list check if it is a manifest and parse // the it to identify any supplementing annotations in the // manifest for each canvas - const manifestTranscripts = await getSupplementingAnnotations(url, title); + const manifestTranscripts = await readSupplementingAnnotations(url, title); let { isMachineGen, labelText } = identifyMachineGen(title); let manifestItems = []; if (manifestTranscripts?.length > 0) { @@ -237,14 +247,14 @@ export async function parseTranscriptData(url, canvasIndex) { // Use combination of the file extension and the Content-Type of // the fetch request to determine the file type - let type = TRANSCRIPT_MIME_TYPES.filter(tt => tt.type == contentType.split(';')[0]); + let type = TRANSCRIPT_MIME_EXTENSIONS.filter(tt => tt.type == contentType.split(';')[0]); let fileType = ''; if (type.length > 0) { fileType = type[0].ext; } else { let urlExt = url.split('.').reverse()[0]; // Only use this if it exists in the supported list of file types for the component - let filteredExt = TRANSCRIPT_MIME_TYPES.filter(tt => tt.ext === urlExt); + let filteredExt = TRANSCRIPT_MIME_EXTENSIONS.filter(tt => tt.ext === urlExt); fileType = filteredExt.length > 0 ? urlExt : ''; } @@ -253,6 +263,7 @@ export async function parseTranscriptData(url, canvasIndex) { return { tData, tUrl, tType: TRANSCRIPT_TYPES.noTranscript }; } + let textData, textLines; switch (fileType) { case 'json': let jsonData = await fileData.json(); @@ -263,22 +274,28 @@ export async function parseTranscriptData(url, canvasIndex) { let json = parseJSONData(jsonData); return { tData: json.tData, tUrl, tType: json.tType, tFileExt: fileType }; } - // for plain text and WebVTT files - case 'vtt': case 'txt': - let textData = await fileData.text(); - let textLines = textData.split('\n'); + textData = await fileData.text(); + textLines = textData.split('\n'); + if (textLines.length == 0) { return { tData: [], tUrl: url, tType: TRANSCRIPT_TYPES.noTranscript }; - } - const isWebVTT = validateWebVTT(textLines[0]); - if (isWebVTT) { - tData = parseWebVTT(textData); - return { tData, tUrl: url, tType: TRANSCRIPT_TYPES.timedText, tFileExt: fileType }; } else { let parsedText = textData.replace(/\n/g, "
"); return { tData: [parsedText], tUrl: url, tType: TRANSCRIPT_TYPES.plainText, tFileExt: fileType }; } + // for timed text with WebVTT/SRT files + case 'srt': + case 'vtt': + textData = await fileData.text(); + textLines = textData.split('\n'); + + if (textLines.length == 0) { + return { tData: [], tUrl: url, tType: TRANSCRIPT_TYPES.noTranscript }; + } else { + tData = parseTimedText(textData, fileType === 'srt'); + return { tData: tData, tUrl: url, tType: TRANSCRIPT_TYPES.timedText, tFileExt: fileType }; + } // for .docx files case 'docx': tData = await parseWordFile(fileData); @@ -392,7 +409,7 @@ export function parseManifestTranscript(manifest, manifestURL, canvasIndex) { } /** - * Parse annotation linking to external resources like WebVTT, Text, and + * Parse annotation linking to external resources like WebVTT, SRT, Text, and * AnnotationPage .json files * @param {Annotation} annotation Annotation from the manifest * @returns {Object} object with the structure { tData: [], tUrl: '', tType: '' } @@ -403,43 +420,32 @@ async function parseExternalAnnotations(annotation) { let tBody = annotation.getBody()[0]; let tUrl = tBody.getProperty('id'); let tType = tBody.getProperty('type'); + let tFormat = tBody.getFormat(); let tFileExt = ''; /** When external file contains text data */ if (tType === 'Text') { - if (tBody.getFormat() === 'text/vtt') { - await fetch(tUrl) - .then(handleFetchErrors) - .then((response) => response.text()) - .then((data) => { - tData = parseWebVTT(data); + await fetch(tUrl) + .then(handleFetchErrors) + .then((response) => response.text()) + .then((data) => { + if (tFormat === TRANSCRIPT_MIME_TYPES.webvtt || tFormat === TRANSCRIPT_MIME_TYPES.srt) { + tData = parseTimedText(data, tFormat === TRANSCRIPT_MIME_TYPES.srt); type = TRANSCRIPT_TYPES.timedText; - tFileExt = 'vtt'; - }) - .catch((error) => { - console.error( - 'transcript-parser -> parseExternalAnnotations() -> fetching WebVTT -> ', - error - ); - throw error; - }); - } else { - await fetch(tUrl) - .then(handleFetchErrors) - .then((response) => response.text()) - .then((data) => { + tFileExt = TRANSCRIPT_MIME_EXTENSIONS.filter(tm => tm.type === tFormat)[0].ext; + } else { tData = data.replace(/\n/g, "
"); type = TRANSCRIPT_TYPES.plainText; tFileExt = 'txt'; - }) - .catch((error) => { - console.error( - 'transcript-parser -> parseExternalAnnotations() -> fetching text -> ', - error - ); - throw error; - }); - } + } + }) + .catch((error) => { + console.error( + 'transcript-parser -> parseExternalAnnotations() -> fetching external transcript -> ', + error + ); + throw error; + }); /** When external file contains timed-text as annotations */ } else if (tType === 'AnnotationPage') { await fetch(tUrl) @@ -493,8 +499,9 @@ function createTData(annotations) { } /** - * Parsing transcript data from a given WebVTT file + * Parsing transcript data from a given file with timed text * @param {Object} fileData content in the transcript file + * @param {Boolean} isSRT given transcript file is an SRT * @returns {Array} array of JSON objects of the following * structure; * { @@ -503,19 +510,22 @@ function createTData(annotations) { * text: 'Transcript text sample' * } */ -export function parseWebVTT(fileData) { +export function parseTimedText(fileData, isSRT = false) { let tData = []; - const lines = cleanWebVTT(fileData); - const firstLine = lines.shift(); - const valid = validateWebVTT(firstLine); - if (!valid) { - console.error('Invalid WebVTT file'); - return []; + const lines = cleanTimedText(fileData); + + if (!isSRT) { + const firstLine = lines.shift(); + const valid = validateWebVTT(firstLine); + if (!valid) { + console.error('Invalid WebVTT file'); + return []; + } } - const groups = groupWebVTTLines(lines); + const groups = groupTimedTextLines(lines); groups.map((t) => { - let line = parseWebVTTLine(t); + let line = parseTimedTextLine(t, isSRT); if (line) { tData.push(line); } @@ -542,7 +552,7 @@ function validateWebVTT(line) { * @param {String} data WebVTT data as a blob of text * @returns {Array} */ -function cleanWebVTT(data) { +function cleanTimedText(data) { // split into lines let lines = data.split('\n'); // remove empty lines @@ -567,7 +577,7 @@ function cleanWebVTT(data) { * @param {Array} lines array of lines in the WebVTT file * @returns {Array} */ -function groupWebVTTLines(lines) { +function groupTimedTextLines(lines) { let groups = []; let i; for (i = 0; i < lines.length;) { @@ -598,8 +608,14 @@ function groupWebVTTLines(lines) { * text: 'Transcript text sample' * } */ -function parseWebVTTLine({ times, line }) { - const timestampRegex = /([0-9]*:){1,2}([0-9]{2})\.[0-9]{2,3}/g; +function parseTimedTextLine({ times, line }, isSRT) { + let timestampRegex; + if (isSRT) { + // SRT allows using comma for milliseconds while WebVTT does not + timestampRegex = /([0-9]*:){1,2}([0-9]{2})(\.|\,)[0-9]{2,3}/g; + } else { + timestampRegex = /([0-9]*:){1,2}([0-9]{2})\.[0-9]{2,3}/g; + } let [start, end] = times.split(' --> '); // FIXME:: remove any styles for now, refine this diff --git a/src/services/transcript-parser.test.js b/src/services/transcript-parser.test.js index 1d7da4e2..8ef2a03e 100644 --- a/src/services/transcript-parser.test.js +++ b/src/services/transcript-parser.test.js @@ -17,7 +17,7 @@ describe('transcript-parser', () => { cleanup(); }); - describe('getSupplementingAnnotations', () => { + describe('readSupplementingAnnotations', () => { test('invalid manifestURL', async () => { // mock console.error console.error = jest.fn(); @@ -25,7 +25,7 @@ describe('transcript-parser', () => { status: 500, }); - const transcripts = await transcriptParser.getSupplementingAnnotations( + const transcripts = await transcriptParser.readSupplementingAnnotations( 'htt://example.com/manifest.json' ); @@ -34,7 +34,7 @@ describe('transcript-parser', () => { expect(transcripts).toHaveLength(0); expect(console.error).toHaveBeenCalledTimes(1); expect(console.error).toHaveBeenCalledWith( - "transcript-parser -> getSupplementingAnnotations() -> error fetching transcript resource at, ", + "transcript-parser -> readSupplementingAnnotations() -> error fetching transcript resource at, ", 'htt://example.com/manifest.json' ); }); @@ -46,7 +46,7 @@ describe('transcript-parser', () => { json: jest.fn(() => manifestTranscript), }); - const transcripts = await transcriptParser.getSupplementingAnnotations( + const transcripts = await transcriptParser.readSupplementingAnnotations( 'https://example.com/volleyball-for-boys/manifest.json' ); @@ -64,7 +64,7 @@ describe('transcript-parser', () => { json: jest.fn(() => annotationTranscript), }); - const transcripts = await transcriptParser.getSupplementingAnnotations( + const transcripts = await transcriptParser.readSupplementingAnnotations( 'https://example.com/annotation-transcript/manifest.json' ); @@ -89,7 +89,7 @@ describe('transcript-parser', () => { json: jest.fn(() => multipleCanvas), }); - const transcripts = await transcriptParser.getSupplementingAnnotations( + const transcripts = await transcriptParser.readSupplementingAnnotations( 'https://example.com/multiple-canvas/manifest.json' ); @@ -390,6 +390,96 @@ describe('transcript-parser', () => { expect(response.tFileExt).toEqual('vtt'); }); + describe('with an SRT file URL', () => { + test('using fullstop as the decimal separator', async () => { + const mockResponse = + '1\r\n00:00:01.200 --> 00:00:21.000\n[music]\n2\r\n00:00:22.200 --> 00:00:26.600\nJust before lunch one day, a puppet show \nwas put on at school.\n\r\n3\r\n00:00:26.700 --> 00:00:31.500\nIt was called "Mister Bungle Goes to Lunch".\n\r\n4\r\n00:00:31.600 --> 00:00:34.500\nIt was fun to watch.\n\r\n5\r\n00:00:36.100 --> 00:00:41.300\nIn the puppet show, Mr. Bungle came to the \nboys\' room on his way to lunch.\n'; + const fetchSRT = jest.spyOn(global, 'fetch').mockResolvedValueOnce({ + status: 200, + headers: { get: jest.fn(() => 'application/x-subrip') }, + text: jest.fn(() => mockResponse), + }); + + const parsedData = [ + { end: 21, begin: 1.2, text: '[music]' }, + { + end: 26.6, + begin: 22.2, + text: 'Just before lunch one day, a puppet show was put on at school.', + }, + { + end: 31.5, + begin: 26.7, + text: 'It was called "Mister Bungle Goes to Lunch".', + }, + { + end: 34.5, + begin: 31.6, + text: 'It was fun to watch.', + }, + { + end: 41.3, + begin: 36.1, + text: "In the puppet show, Mr. Bungle came to the boys' room on his way to lunch.", + }, + ]; + + const response = await transcriptParser.parseTranscriptData( + 'https://example.com/transcript.srt', + 0 + ); + + expect(fetchSRT).toHaveBeenCalledTimes(1); + expect(response.tData).toEqual(parsedData); + expect(response.tUrl).toEqual('https://example.com/transcript.srt'); + expect(response.tFileExt).toEqual('srt'); + }); + + test('using comma as the decimal separator', async () => { + const mockResponse = + '1\r\n00:00:01,200 --> 00:00:21,000\n[music]\n2\r\n00:00:22,200 --> 00:00:26,600\nJust before lunch one day, a puppet show \nwas put on at school.\n\r\n3\r\n00:00:26,700 --> 00:00:31,500\nIt was called "Mister Bungle Goes to Lunch".\n\r\n4\r\n00:00:31,600 --> 00:00:34,500\nIt was fun to watch.\n\r\n5\r\n00:00:36,100 --> 00:00:41,300\nIn the puppet show, Mr. Bungle came to the \nboys\' room on his way to lunch.\n'; + const fetchSRT = jest.spyOn(global, 'fetch').mockResolvedValueOnce({ + status: 200, + headers: { get: jest.fn(() => 'application/x-subrip') }, + text: jest.fn(() => mockResponse), + }); + + const parsedData = [ + { end: 21, begin: 1.2, text: '[music]' }, + { + end: 26.6, + begin: 22.2, + text: 'Just before lunch one day, a puppet show was put on at school.', + }, + { + end: 31.5, + begin: 26.7, + text: 'It was called "Mister Bungle Goes to Lunch".', + }, + { + end: 34.5, + begin: 31.6, + text: 'It was fun to watch.', + }, + { + end: 41.3, + begin: 36.1, + text: "In the puppet show, Mr. Bungle came to the boys' room on his way to lunch.", + }, + ]; + + const response = await transcriptParser.parseTranscriptData( + 'https://example.com/transcript.srt', + 0 + ); + + expect(fetchSRT).toHaveBeenCalledTimes(1); + expect(response.tData).toEqual(parsedData); + expect(response.tUrl).toEqual('https://example.com/transcript.srt'); + expect(response.tFileExt).toEqual('srt'); + }); + }); + test('with unsupported transcript file type in URL: .png', async () => { const fetchImage = jest.spyOn(global, 'fetch').mockResolvedValueOnce({ status: 200, @@ -558,7 +648,7 @@ describe('transcript-parser', () => { const mockResponse = 'WEBVTT\r\n\r\n1\r\n00:00:01.200 --> 00:00:21.000\n[music]\n2\r\n00:00:22.200 --> 00:00:26.600\nJust before lunch one day, a puppet show \nwas put on at school.\n\r\n3\r\n00:00:26.700 --> 00:00:31.500\nIt was called "Mister Bungle Goes to Lunch".\n\r\n4\r\n00:00:31.600 --> 00:00:34.500\nIt was fun to watch.\r\n\r\n5\r\n00:00:36.100 --> 00:00:41.300\nIn the puppet show, Mr. Bungle came to the \nboys\' room on his way to lunch.\n'; - const tData = transcriptParser.parseWebVTT(mockResponse); + const tData = transcriptParser.parseTimedText(mockResponse); expect(tData).toHaveLength(5); expect(tData[0]).toEqual({ @@ -578,7 +668,7 @@ describe('transcript-parser', () => { const mockResponse = 'WEBVTT\r\n\r\n1\r\n00:01.200 --> 00:21.000\n[music]\n2\r\n00:22.200 --> 00:26.600\nJust before lunch one day, a puppet show \nwas put on at school.\n\r\n3\r\n00:26.700 --> 00:31.500\nIt was called "Mister Bungle Goes to Lunch".\n\r\n4\r\n00:31.600 --> 00:34.500\nIt was fun to watch.\r\n\r\n5\r\n00:36.100 --> 00:41.300\nIn the puppet show, Mr. Bungle came to the \nboys\' room on his way to lunch.\n'; - const tData = transcriptParser.parseWebVTT(mockResponse); + const tData = transcriptParser.parseTimedText(mockResponse); expect(tData).toHaveLength(5); expect(tData[0]).toEqual({ @@ -601,7 +691,7 @@ describe('transcript-parser', () => { const mockResponse = '1\r\n00:00:01.200 --> 00:00:21.000\n[music]\n2\r\n00:00:22.200 --> 00:00:26.600\nJust before lunch one day, a puppet show \nwas put on at school.\n\r\n3\r\n00:00:26.700 --> 00:00:31.500\nIt was called "Mister Bungle Goes to Lunch".\n\r\n4\r\n00:00:31.600 --> 00:00:34.500\nIt was fun to watch.\r\n\r\n5\r\n00:00:36.100 --> 00:00:41.300\nIn the puppet show, Mr. Bungle came to the \nboys\' room on his way to lunch.\n'; - const tData = transcriptParser.parseWebVTT(mockResponse); + const tData = transcriptParser.parseTimedText(mockResponse); expect(tData).toHaveLength(0); expect(console.error).toHaveBeenCalledTimes(1); @@ -614,7 +704,7 @@ describe('transcript-parser', () => { const mockResponse = 'WEBVTT\r\n\r\n1\r\n00:00:01.200 --> 00:00:.000\n[music]\n2\r\n00:00:22.200 --> 00:00:26.600\nJust before lunch one day, a puppet show \nwas put on at school.\n\r\n3\r\n00:00:26.700 --> 00:00:31.500\nIt was called "Mister Bungle Goes to Lunch".\n\r\n4\r\n00:00:31.600 --> 00:00:34.500\nIt was fun to watch.\r\n\r\n5\r\n00:00:36.100 --> 00:00:41.300\nIn the puppet show, Mr. Bungle came to the \nboys\' room on his way to lunch.\n'; - const tData = transcriptParser.parseWebVTT(mockResponse); + const tData = transcriptParser.parseTimedText(mockResponse); expect(tData).toHaveLength(4); expect(console.error).toHaveBeenCalledTimes(1); diff --git a/src/services/utility-helpers.js b/src/services/utility-helpers.js index 3064e0df..b8d677d3 100644 --- a/src/services/utility-helpers.js +++ b/src/services/utility-helpers.js @@ -91,7 +91,8 @@ export function timeToS(time) { let hoursInS = hours != undefined ? parseInt(hours) * 3600 : 0; let minutesInS = minutes != undefined ? parseInt(minutes) * 60 : 0; - let secondsNum = seconds === '' ? 0.0 : parseFloat(seconds); + // Replace decimal separator if it is a comma + let secondsNum = seconds === '' ? 0.0 : parseFloat(seconds.replace(',', '.')); let timeSeconds = hoursInS + minutesInS + secondsNum; return timeSeconds; } diff --git a/src/services/utility-helpers.test.js b/src/services/utility-helpers.test.js index 944d1037..91b746f8 100644 --- a/src/services/utility-helpers.test.js +++ b/src/services/utility-helpers.test.js @@ -11,6 +11,11 @@ describe('util helper', () => { const timeStr = '09:12.100'; expect(util.timeToS(timeStr)).toEqual(552.1); }); + + test('with format hhLmm:ss,ms', () => { + const timeStr = '00:09:17,600'; + expect(util.timeToS(timeStr)).toEqual(557.6); + }); }); describe('getCanvasTarget()', () => { From 540c08948dd2c797d85b1c2d3b4bdcb8434022d3 Mon Sep 17 00:00:00 2001 From: dwithana Date: Tue, 12 Mar 2024 13:25:19 -0400 Subject: [PATCH 2/2] Support bot MIME types application/x-subrip and text/srt for SRT transcripts --- public/manifests/dev/playlist-manifest.json | 4 +- public/manifests/dev/volleyball-for-boys.json | 4 +- src/components/Transcript/Transcript.js | 4 +- src/components/Transcript/Transcript.md | 6 +++ src/services/transcript-parser.js | 43 ++++++++++++------- src/services/transcript-parser.test.js | 13 ++++-- 6 files changed, 48 insertions(+), 26 deletions(-) diff --git a/public/manifests/dev/playlist-manifest.json b/public/manifests/dev/playlist-manifest.json index b62daecd..289dd968 100644 --- a/public/manifests/dev/playlist-manifest.json +++ b/public/manifests/dev/playlist-manifest.json @@ -287,7 +287,7 @@ "body": { "id": "http://localhost:3003/volleyball-for-boys/volleyball.txt", "type": "Text", - "format": "plain/txt", + "format": "text/plain", "label": { "en": [ "External Text Transcript (machine-generated)" @@ -356,4 +356,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/public/manifests/dev/volleyball-for-boys.json b/public/manifests/dev/volleyball-for-boys.json index 613f9e37..0de712e9 100644 --- a/public/manifests/dev/volleyball-for-boys.json +++ b/public/manifests/dev/volleyball-for-boys.json @@ -48,7 +48,7 @@ "body": { "id": "http://localhost:6060/volleyball-for-boys/volleyball.txt", "type": "Text", - "format": "plain/txt", + "format": "text/plain", "label": { "en": [ "External Text Transcript (machine-generated)" @@ -62,4 +62,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/components/Transcript/Transcript.js b/src/components/Transcript/Transcript.js index d29e03eb..d31d2f9e 100644 --- a/src/components/Transcript/Transcript.js +++ b/src/components/Transcript/Transcript.js @@ -206,7 +206,7 @@ const Transcript = ({ playerID, manifestUrl, transcripts = [] }) => { // set isEmpty flag to render transcripts UI setIsEmpty(false); - const { id, title, filename, url, isMachineGen } = transcript; + const { id, title, filename, url, isMachineGen, format } = transcript; // Check cached transcript data const cached = cachedTranscripts.filter( @@ -220,7 +220,7 @@ const Transcript = ({ playerID, manifestUrl, transcripts = [] }) => { } else { // Parse new transcript data from the given sources await Promise.resolve( - parseTranscriptData(url, canvasIndexRef.current) + parseTranscriptData(url, canvasIndexRef.current, format) ).then(function (value) { if (value != null) { const { tData, tUrl, tType, tFileExt } = value; diff --git a/src/components/Transcript/Transcript.md b/src/components/Transcript/Transcript.md index 2b1512b4..575dbcc1 100644 --- a/src/components/Transcript/Transcript.md +++ b/src/components/Transcript/Transcript.md @@ -17,6 +17,7 @@ Transcript component displays any available transcript data in a given IIIF mani - Word document (.docx) - Plain text file - WebVTT + - SRT `transcripts` prop has a default value of an empty array. @@ -68,6 +69,11 @@ import config from '../../../env.js'; title: 'Invalid transcript', url: `${config.url}/manifests/${config.env}/invalid-annotation.json`, // URL of the manifest }, + { + // SRT file + title: 'SRT Transcript', + url: `${config.url}/lunchroom_manners/lunchroom_manners.srt`, + }, ], }, ]} diff --git a/src/services/transcript-parser.js b/src/services/transcript-parser.js index a11cf628..61f7cdb2 100644 --- a/src/services/transcript-parser.js +++ b/src/services/transcript-parser.js @@ -14,11 +14,11 @@ import { // ENum for supported transcript MIME types const TRANSCRIPT_MIME_TYPES = { - webvtt: 'text/vtt', - srt: 'application/x-subrip', - text: 'text/plain', - json: 'application/json', - docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + webvtt: ['text/vtt'], + srt: ['application/x-subrip', 'text/srt'], + text: ['text/plain'], + json: ['application/json'], + docx: ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'] }; const TRANSCRIPT_MIME_EXTENSIONS = [ @@ -70,6 +70,7 @@ export async function readSupplementingAnnotations(manifestURL, title = '') { title: labelText, isMachineGen: isMachineGen, id: `${labelText}-${index}`, + format: '' }); } else { annotations.forEach((annotation, i) => { @@ -98,7 +99,8 @@ export async function readSupplementingAnnotations(manifestURL, title = '') { filename: filename, url: id, isMachineGen: isMachineGen, - id: `${labelText}-${index}-${i}` + id: `${labelText}-${index}-${i}`, + format: annotBody.getFormat() || '', }); } }); @@ -171,6 +173,7 @@ export async function sanitizeTranscripts(transcripts) { url: url, isMachineGen: isMachineGen, id: `${labelText}-${canvasId}-${index}`, + format: '' }; } else { return null; @@ -213,9 +216,10 @@ function groupByIndex(objectArray, indexKey, selectKey) { * within the manifest. * @param {String} url URL of the transcript file selected * @param {Number} canvasIndex Current canvas rendered in the player + * @param {String} format transcript file format read from Annotation * @returns {Object} Array of trancript data objects with download URL */ -export async function parseTranscriptData(url, canvasIndex) { +export async function parseTranscriptData(url, canvasIndex, format) { let tData = []; let tUrl = url; @@ -245,16 +249,23 @@ export async function parseTranscriptData(url, canvasIndex) { return { tData: [], tUrl, tType: TRANSCRIPT_TYPES.invalid }; } - // Use combination of the file extension and the Content-Type of - // the fetch request to determine the file type - let type = TRANSCRIPT_MIME_EXTENSIONS.filter(tt => tt.type == contentType.split(';')[0]); + /* + Use the Annotation format in the IIIF Manifest, file extension, and the + Content-Type in headers of the fetch request to determine the file type. + These are checked with priority descending in the order of Annotation format, + Content-Type in headers, and file extension in the resource URI. + */ + let fromContentType = TRANSCRIPT_MIME_EXTENSIONS.filter(tm => tm.type.includes(contentType.split(';')[0])); + let fromAnnotFormat = TRANSCRIPT_MIME_EXTENSIONS.filter(tm => tm.type.includes(format)); let fileType = ''; - if (type.length > 0) { - fileType = type[0].ext; + if (fromAnnotFormat?.length > 0) { + fileType = fromAnnotFormat[0].ext; + } else if (fromContentType.length > 0) { + fileType = fromContentType[0].ext; } else { let urlExt = url.split('.').reverse()[0]; // Only use this if it exists in the supported list of file types for the component - let filteredExt = TRANSCRIPT_MIME_EXTENSIONS.filter(tt => tt.ext === urlExt); + let filteredExt = TRANSCRIPT_MIME_EXTENSIONS.filter(tm => tm.ext === urlExt); fileType = filteredExt.length > 0 ? urlExt : ''; } @@ -429,10 +440,10 @@ async function parseExternalAnnotations(annotation) { .then(handleFetchErrors) .then((response) => response.text()) .then((data) => { - if (tFormat === TRANSCRIPT_MIME_TYPES.webvtt || tFormat === TRANSCRIPT_MIME_TYPES.srt) { - tData = parseTimedText(data, tFormat === TRANSCRIPT_MIME_TYPES.srt); + if (TRANSCRIPT_MIME_TYPES.webvtt.includes(tFormat) || TRANSCRIPT_MIME_TYPES.srt.includes(tFormat)) { + tData = parseTimedText(data, TRANSCRIPT_MIME_TYPES.srt.includes(tFormat)); type = TRANSCRIPT_TYPES.timedText; - tFileExt = TRANSCRIPT_MIME_EXTENSIONS.filter(tm => tm.type === tFormat)[0].ext; + tFileExt = TRANSCRIPT_MIME_EXTENSIONS.filter(tm => tm.type.includes(tFormat))[0].ext; } else { tData = data.replace(/\n/g, "
"); type = TRANSCRIPT_TYPES.plainText; diff --git a/src/services/transcript-parser.test.js b/src/services/transcript-parser.test.js index 8ef2a03e..99b7105b 100644 --- a/src/services/transcript-parser.test.js +++ b/src/services/transcript-parser.test.js @@ -77,7 +77,8 @@ describe('transcript-parser', () => { title: 'Canvas-0', id: 'Canvas-0-0', url: 'https://example.com/annotation-transcript/manifest.json', - isMachineGen: false + isMachineGen: false, + format: '' } ]); }); @@ -103,7 +104,8 @@ describe('transcript-parser', () => { filename: 'Captions in WebVTT format', id: 'Captions in WebVTT format-1-0', url: 'https://example.com/sample/subtitles.vtt', - isMachineGen: false + isMachineGen: false, + format: 'text/vtt' } ]); }); @@ -162,6 +164,7 @@ describe('transcript-parser', () => { id: 'Transcript 1-0-0', url: 'http://example.com/transcript-1.vtt', isMachineGen: false, + format: '', }, { title: 'Transcript 2', @@ -169,6 +172,7 @@ describe('transcript-parser', () => { id: 'Transcript 2-0-1', url: 'http://example.com/transcript-2.json', isMachineGen: true, + format: '', } ] }); @@ -440,7 +444,7 @@ describe('transcript-parser', () => { '1\r\n00:00:01,200 --> 00:00:21,000\n[music]\n2\r\n00:00:22,200 --> 00:00:26,600\nJust before lunch one day, a puppet show \nwas put on at school.\n\r\n3\r\n00:00:26,700 --> 00:00:31,500\nIt was called "Mister Bungle Goes to Lunch".\n\r\n4\r\n00:00:31,600 --> 00:00:34,500\nIt was fun to watch.\n\r\n5\r\n00:00:36,100 --> 00:00:41,300\nIn the puppet show, Mr. Bungle came to the \nboys\' room on his way to lunch.\n'; const fetchSRT = jest.spyOn(global, 'fetch').mockResolvedValueOnce({ status: 200, - headers: { get: jest.fn(() => 'application/x-subrip') }, + headers: { get: jest.fn(() => 'text/srt') }, text: jest.fn(() => mockResponse), }); @@ -533,7 +537,8 @@ describe('transcript-parser', () => { const response = await transcriptParser.parseTranscriptData( undefined, - 0 + 0, + '' ); expect(fetchSpy).not.toHaveBeenCalled();