I am trying to make an automatic handout creator for my courses on edX. It took me a long time to do it...
(If you don't know edX: https://www.edx.org/)
I wrote a program that adds to each slide provided by edX the coressponding subtitiles that were spoken in the video File.
How: I extract the subscripts timestamps -> I use the timestamps to extract a set of frames from the video file (the frame that is shown in the middle of a subscript time range) -> then I compare the frames with the provided slides to find the position of the slides in the video -> then I use this information to add under each slide the coressponding subtitle text.
In the end, each page should look like this: (the grey border, is only placed here to distinguish the page from the white website background)
Now to the code:
I download the videos as ".mov" into a video folder, the subscript text file into a subscript folder and the slides (converted to imgaes ".jpg" ) into a slides folder.
Then I run the following code: (I added comments)
I import the subrib files (subtitiles)
Source : Merge transcript and corresponding slides from video
(**Import SubRib File**)
(*File Path*)
subRibFile =
"file_path" <> ".srt";
(*Import as string*)
subRip = ReadList[subRibFile, String];
(*Get Time Intervals [h:min:sec:milliosec]*)
subtitleTimings =
StringSplit[
StringReplace[
Select[subRip,
StringMatchQ[#, {__ ~~ Whitespace ~~ "-->" ~~
Whitespace ~~ __}] &], "," -> "."],
Whitespace ~~ "-->" ~~ Whitespace];
(*Convert Time Intervals to seconds*)
timesSeconds =
Partition[
UnitConvert[
Total[DateValue[
DateObject[#], {"Hour", "Minute", "Second", "Millisecond"},
Quantity]] & /@ Flatten[subtitleTimings], "Seconds"], {2}];
Then I import the video file:
videoFile =
"file_path" <> ".mov";
(*Get Frame Rate*)
frameRate = Quantity[Import[videoFile, "FrameRate"], 1/"Seconds"];
(*Get frame number of the start and end of a subtitile *)
framesStartEnd = Round[timesSeconds*frameRate];
(*Get Frmae Numbers in between start and end of each subtitile*)
frameList =
Round[(First /@ framesStartEnd + Last /@ framesStartEnd)/2];
(*Import those frames*)
frames = Import[videoFile, {"ImageList", frameList}];
Then I Import the images of the slides:
SetDirectory["path to folder of images"];
(* Extract all images from folder*)
images = FileNames["*.jpg"];
slides = Import[#] & /@ images;
Print["Got all slides!"];
Now I find the position of each slide within the frames of the video -> Recognizing the slides in the video frame. Source:Extract timestamp of specific frames in video
(**Analyze the frames to find position of the slides in the video**)
(*Scale Down the images for faster image analysis*)
res = 48; smallframes =
Flatten[ImageData[ImageResize[#, {res, res}]], 1] & /@
frames; smallslides =
Flatten[ImageData[ImageResize[#, {res, res}]], 1] & /@ slides;
Print["Resized all images for immage analysis!"];
(*Create Comparaison (slides/frames) metrics*)
SimilarColor[a_, b_] := If[And @@ ((# < 0.05) & /@ ((a - b)^2)), 1, 0];
(*Label each frame according to the most likly slide number*)
Print["Comparing all frames with all slides: "];
labels = Monitor[Table[
With[
{score =
Total@MapThread[SimilarColor, {#, smallframes[[i]]}] & /@
smallslides},
Position[score, Max[score]]][[1, 1]], {i, Length[frames]}], i];
Now I find the range of frames which show the same slide -> To know the range of subtiles to add to the specific slide. Source: List of same images -> Find intervals of same images
similarFrames =
SplitBy[Transpose[{#, Range@Length@#}], First][[;; , {1, -1}, 2]] &@
labels;
I remove slides which were not shown in the video (can happen):
(**Remove Unseen Slides**)
(*Sometimes there are more slides given than do appear in the video, \
therefore one makes a new list of slides with only the slides which \
appear in the video*)
slidesToKeep = DeleteDuplicates[labels];
(*Extracts only those slides*)
slidesKeeped = Part[slides, slidesToKeep ];
I extract the text blocks for each slide from the subtitles:
(**Extract Text for each slide**)
(*Get Timestaps of the beginning of each subscript*)
timeStamps = First /@ timesSeconds;
(*Extract the transcript \[Rule] {{slideNumber,text}}*)
transcripts =
Split[Select[
subRip, !
StringMatchQ[#, {__ ~~ Whitespace ~~ "-->" ~~
Whitespace ~~ __}] &], DigitQ[#] &];
(*Extract all text for each slide*)
onlyText = transcripts[[All, 2]];
text = Map[ToString,
onlyText[[#[[1]] ;; # [[2]] ]] & /@ similarFrames];
I filter a bit the text:
(**Clean Text**)
filter1 = StringReplace[text, "," -> ""];
filter2 = StringReplace[filter1, "<i>" -> ""];
textProccessed = StringReplace[filter2, "</i>" -> ""];
textSlide =
StringReplace[StringReplace[#, "{" -> ""], "}" -> ""] & /@
textProccessed;
I create two lists.
The images: the slides
The text: which goes with every slide.
images = Image[#, ImageSize -> Full] & /@ slidesKeeped;
text = textSlide;
I create the Layout. Source: Layout: Images and Text
exportIm = Panel[Style[Grid[{
{images[[#]], SpanFromLeft},
{TextCell[Row[{text[[#]]}], TextJustification -> 1,
Hyphenation -> False], SpanFromLeft}, {}, {"Notes: "}},
Frame -> {{False, False}, {True, True, True, False}, {False}},
BaseStyle -> ImageSizeMultipliers -> 1], 7,
FontFamily -> "Helvetica", Background -> White],
Background -> White, ImageSize -> {210, 297}*2] & /@
Range[Length[images]];
Finally, I export the individual slides (I do not merge them into one PDF because MA is quiet slow with that -> do it with external software):
SetDirectory[
"path_to_export"];
Export[ToString[#] <> ".pdf", exportIm[[#]]] & /@
Range[Length[exportIm]];
The program works, but is still not reliable. Sometimes it does not identify the slides in the correct order or there is text missing.
Therefore, I would be awesome, if you could help me to make it more robust !
Here are two MOOC courses to test the program and improve it: https://www.dropbox.com/sh/raajt9qetqnj7p9/AACEV3PhjhRmwTDjvdtcnEc3a?dl=0
The first one (2) has the tricky part, that within the mooc video they show not only slides, but also at a certain time a short video, which messes up the result.
There are also some difficulties with the second (3), as it fails to indentify the slides in the correct order and the text also has some problems.
I looking forward seeing your suggestions and improvements ! :)
DOWNLOAD complete code (thanks to @jjc385) here: https://github.com/jjc385/mmase-159065_annotated-lecture-slides/blob/master/pasted-OP-code.m
Check Your Code
To check that your code works correctly, I have created a pdf for video 2, which shows the slide number, the slide and the start and end subtitles for each slide. Please download it here: https://www.dropbox.com/s/hdxajnfve0nzf5i/Slides%20and%20Subtitles%20Start_End%20for%202.pdf?dl=0
The correct identification of the slides of the later video should be: 1,2,3,4,5,4,6,7,8,9,10,11,12,13,12,13,14,13,15,16,17,18
Be careful: There are repeating slides !