Posts

Create a Slide Video from a Mediasite Export

Here’s a quick bit of PowerShell that will let you take the slides from a Mediasite Presentation Export and convert them into a playable MP4. No warranties or promises that this makes any sense!

# your ffmpeg path
$ffmpeg = "your\path\to\ffmpeg.exe"

# this is assuming you're running in the path where you extracted the zip file
$presentation = Get-Content .\MediasitePresentation_70.xml
$presentation_properties = $presentation.LocalPresentationManifest.Properties
$slide_stream = $presentation_properties.ContentRevisions.ContentRevision.Streams.ContentStream | ? StreamType -EQ 2
$duration = $presentation_properties.Properties.Presentation.Duration

# make an array of the start times for each slide, plus the total duration of the video
$times = $slide_stream.SlideContent.Slides.Slide.Time + $duration

# make a array of the distances between each of those times
$i = 1
$durations = do { $times[$i] - $times[$i-1]; $i++ }
until ($i -gt ($times.Length - 1))

# make an empty string
$input_file = ""

# for each slide in the presentation
$slide_stream.SlideContent.Slides.Slide | % {
# get the slide file
$slide_file = Get-ChildItem .\Content\$($slide_stream.SlideContent.FileName -f [int]($_.Number))
# add the file and its duration to the output
$input_file += "file '{0}'
duration {1}
" -f $slide_file.FullName, ($durations[$_.Number - 1] / 1000)
}
# From https://trac.ffmpeg.org/wiki/Slideshow#Concatdemuxer
# (Due to a quirk, the last image has to be specified twice - the 2nd time without any duration directive)
$input_file += "file '{0}'" -f $slide_file.FullName

# write that out to a text file
$input_file | Out-File slides.txt -Encoding ascii

# run it through FFMpeg, outputting to slides.mp4
& $ffmpeg -y -f concat -safe 0 -i slides.txt -vsync vfr -pix_fmt yuv420p slides.mp4

Terms on my own Terms

You’re probably most familiar seeing university Term codes written out as something fancy like “Autumn 2023”

Internally, our systems are more likely to use things like “au2023” or “1238”. The last one is top-notch because it’s sortable and you can store it as an integer. What’s not to love?

Oh right. It’s not readily apparent.

1238. Let’s break that down.

1  – Century marker. 0 = 1900, 1 = 2000

23 – Year in the century (i.e. 2023)

8 – Autumn semester. Spring = 2, Summer = 4, Autumn = 8

Often we have to convert between the forms, and there are some straightforward approaches. You can split the string “1238” apart and do some basic replacement.

>>> termcode = "1238"
>>> year = (int(termcode[0])*100)+1900+int(termcode[1:3])
>>> terms = {'2':'sp','4':'su','8':'au'}
>>> f'{terms[termcode[3]]}{year}'
'au2023'

Very nice. Nothing too silly. Reversing it?

>> termcode = "au2023"
>>> year = str(21-int(termcode[2:4]))+termcode[4:6]
>>> terms = {'sp':'2','su':'4','au':'8'}
>>> f'{year}{terms[termcode[0:2]]}'
'1238'

Downright sensible! One thing, though. I don’t like that dictionary lookup. I don’t know why, but I don’t.

I will not rest until I find a sillier way!

>>> termcode = "1238"
>>> termcode = int(termcode)
>>> t1 = lambda t : chr(119 - t * (t % 3) - (( (t % 5) % 2) * ( t % 5 + t % 6 + t % 7)))
>>> t2 = lambda t : chr(int(117 - (5 * (t % 4) / 2 )))
>>> term = termcode % 10
>>> year = int(1900 + (termcode - (termcode % 10)) / 10)
>>> f'{t1(term)}{t2(term)}{year}'
'au2023'

“But WHY??? That’s SO CONVOLUTED AND OPAQUE!” Yes, and so is the reverse!

>>> termcode = "au2023"
>>> t = lambda t : (t % 10 - t % 8) * (t % 2)
>>> year = str(21-int(termcode[2:4]))+termcode[4:6]
>>> f'{year}{t(ord(termcode[0]))+t(ord(termcode[1]))}'
'1238'

So there you have it! Two different ways to convert term codes in Python!

Converting from DFXP

DFXP is an XML-based caption format originally used with Flash Videos. It’s the future, and we ain’t got Flash, but we do have DFXP!

Unsupported-ish Solution Time

So, Mediasite will let you download captions in DFXP, but not something more interchangeable like SRT or VTT. The fun part about caption formats though? They’re pretty much all the same thing, just bits of information are moved around! If there’s one thing I love doing, it’s slightly rearranging the content of well-structured text files!

Let’s take a look at a very simple DFXP file:

<?xml version="1.0" encoding="utf-8"?>
<tt xmlns:tts="http://www.w3.org/2006/04/ttaf1#styling" xml:lang="en-US" xmlns="http://www.w3.org/2006/04/ttaf1">
  <body>
    <div xml:lang="en-US">
      <p end="00:00:07.65" begin="00:00:00.05">The first caption.</p>
      <p end="00:00:09.93" begin="00:00:07.65">The second caption.</p>
    </div>
  </body>
</tt>

That’s a whole lot just to say “A single caption” is displayed from 0.05 seconds to 7.65 seconds. Since it’s in XML, though, we can rip this apart with a quick bit of PowerShell.

First, we’ll bring in the file:

PS > $dfxp = Get-Content somecaptions.dfxp

Next, let’s dive down to the “p”s inside that div tag:

PS > $dfxp.tt.body.div.p
end         begin       #text              
---         -----       -----              
00:00:07.65 00:00:00.05 The first caption. 
00:00:09.93 00:00:07.65 The second caption.

Cool! So we know that we can get our information as “#text”, “begin”, and “end”

So how do we make that into an SRT? It helps if we know what an SRT file should look like:

1                                (just a count of the caption line, starts with 1)
00:00:00,050 --> 00:00:07,650    (time range, note the Euro-spec comma)
A single caption                 (the text)

Much cleaner! WebVTT is very similar, except:

  • Starts with a header that says “WEBVTT”
  • Uses a period as the decimal separator
  • Doesn’t have that count/index line

So, to make an SRT file, we need to loop through all the p elements in $dfxp.tt.body.div, and cajole their pieces into the right format. We’ll use ForEach-Object, Multi-Line Strings, String Formatting, and some String.Replace method (for turning periods into commas). We’ll also need to make that index line, and add 1 to it for each subsequent row.

PS > $count = 1
PS > $dfxp.tt.body.div.p | ForEach-Object {
@"
{0}
{1}0 --> {2}0
{3}

"@ -f $count, $_.begin.Replace(".",","), $_.end.Replace(".",","), $_.'#text'
$count++
}

1
00:00:00,050 --> 00:00:07,650
The first caption.

2
00:00:07,650 --> 00:00:09,930
The second caption.

Looks right! Just write that puppy out to a file, and you’ve got yourself an SRT file!

Alright, but what if you didn’t just come here to read a poorly written introduction to PowerShell? You just want to convert some dang DFXP files!
To use these as-is, you’ll need to be on Windows (unless you’re handy with PowerShell on Mac or Linux), and you’ll have to rename them after you download Right-Click/Save As them.

https://u.osu.edu/wade.199/files/2022/09/dfxp_converter.bat_.txt

https://u.osu.edu/wade.199/files/2022/09/dfxp_converter.ps1_.txt

Put those in the same folder, take the “_.txt” off, and you’ll be able to just drop a DFXP on the .bat file and it’ll spit out

  • Transcript
  • SRT
  • VTT

PowerShell Tip: Tables for Teams

Sometimes I have tabular data in PowerShell, and I like to toss it into Teams in a somewhat clean format. Though some trial and error, I’ve discovered that
| ConvertTo-Html | Set-Clipboard -AsHtml
was what I’d needed.

ConvertTo-Html | Set-Clipboard on it’s own will give you Unicode text, which is fine if you’re pasting into a .htm file, but Teams will interpret it literally.

Adding -AsHtml will set the clipboard format to HTML, and Teams will style it accordingly. It looks like WordPress likes it, too!

n
0 0
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81

(output from (0..9) | % { [PSCustomObject]@{n=$_;"n²"=[math]::Pow($_,2)} } | ConvertTo-Html | Set-Clipboard -AsHtml )
NirSoft’s InsideClipboard helped me figure out what was going on under-the-hood, and helped me get a better understanding of the Clipboard.

Searching for Chungus

What the heck are you supposed to do when you know in your heart-of-hearts that somewhere lurking in your Mediasite instance is a horrendously high-bitrate MP4 file, just waiting to make somebody’s life a living hell when they go to watch it on a less-than-optimal connection (not to mention chewing up your storage)? Let’s find ’em!

Check the Media Server logs

Using an IIS log parser/indexer (we use Splunk, but other tools may offer similar capabilities), you’re going to:

  1. Find MP4 video segment downloads

    host={your_streaming_server} http_port=443 segmentLength=6 video="*" uri_path="/MediasiteDeliver/OnDemand/MP4Video/*.mp4*"  

  2. Extract the stream quality

    | rex field=uri_path "/MediasiteDeliver/OnDemand/MP4Video/(?<mp4>.+)/QualityLevels\((?<quality>\d+)\)"

  3. Set a threshold (in bits/second). I’m using 6 Mbps

    | where quality>6000000

  4. Only unique videos

    | dedup mp4 | table mp4, quality

  5. Toss ’em in a table

    | table mp4, quality

Now we should have a (hopefully short)…

List of Files:

mp4 quality
48529917-1114-457d-a818-14e3a551de2e.mp4 15404000
65845145-35ee-4dca-b193-0956843998b0.mp4 12030000
ab5984ee-a952-4bcf-a2d9-f3709974e194.mp4 11964000

Oh my. Those are healthy videos. Now we’ve got to track down which presentations they belong to.

Find the presentations in the database

Connect to your Mediasite site database, and pop open a new query. I realize that this has a gross pile of subqueries, but such-is-life:

SELECT PresentationContextId FROM ContentRevisions
	WHERE Id IN (
SELECT ContentRevisionId FROM ContentRevisionStreams
	WHERE StreamId IN (
SELECT StreamId FROM StreamPresentationContent
	WHERE PresentationContentId IN (
SELECT Id FROM PresentationContent
	WHERE FileName IN (
'48529917-1114-457d-a818-14e3a551de2e.mp4',
'65845145-35ee-4dca-b193-0956843998b0.mp4',
'ab5984ee-a952-4bcf-a2d9-f3709974e194.mp4'
))))

That’ll bring you back a list of…

Presentation (Context) Ids

b98eccb0-235a-4803-a109-448068f60e0d
0a295662-0cbf-48be-a4aa-d20954abe11a
920b3e71-c4da-4e51-a5f2-ddc151784f9b

You can pop these right into the search box in your Mediasite Management Portal, and track down the presentations. From there, you can view the streams and develop a plan of action. Add variable bitrate? Re-encode the original? You’ve got tons of options. Just remember to work with presentation owners, and let them know why action needs to be taken.

Converting your Zoom transcript (VTT) to something Mediasite can use (SRT)

Unsupported solution time!

If you, like many others, are embarking on recording content, holed up in your house, exhausted from stress cleaning and worrying about loved ones, know this:

YOUR CONTENT MUST MEET ACCESSIBILITY GUIDELINES

Mediasite (at this point) has no built-in captioning of its own. It sounds like something that should be trivial in this day and age, but Consider The Following:

  • Do you occasionally have a hard time understanding what someone said?
  • Are you smarter than a computer?
  • Does captioning and real-time transcription seem like a low-skilled job to you?

Well, it ain’t easy, but somehow Zoom provides accurate-ish Timed Transcripts! The problem is that they are in a format (VTT) that Mediasite can’t use (for some reason). You can convert them though!

This one requires your command line / terminal / BASH shell, whatever it is you kids use these days.

You need ffmpeg for this. Almost all my solutions are built on top of it. Heck, so are many of the major video platforms you interact with, including Mediasite. It’s the free Swiss Army knife of audio and video. Consider this trick akin to the little tweezers you’ll inevitably lose. Ephemeral, but not without whimsy.

ffmpeg -i {yourfilename}.vtt output.srt

Source

That’s it. That “output.srt” file be be uploaded right into Mediasite. From there, you can use Mediasite’s Caption Editor to fix the inevitable Missed Steaks from the Zoomed Rants Crypt.

Querying APIs from Microsoft Excel

A few weeks ago, a colleague was faced with a spreadsheet full of MAC addresses, and we were discussing ways to identify the kinds of devices represented in that list. My first thought was to use Powershell’s Invoke-RestMethod cmdlet to query MACVendorLookup.com’s REST API. This would have involved a bit of work getting the data into the right format to feed to the script. Don’t get me wrong: I absolutely adore this sort of thing, but I also wanted to be able to hand it to someone else.

That’s when I went poking around in Excel 2016’s formula list (a fabulous place to explore) and discovered WEBSERVICE, which promptly blew my mind. I can use Excel as an HTTP Client for text?

Whoa. Get out of town.

=WEBSERVICE("http://www.macvendorlookup.com/api/v2/00:23:AB:7B:58:99")

[{"startHex":"0023AB000000","endHex":"0023ABFFFFFF","startDec":"153192759296","endDec":"153209536511","company":"CISCO SYSTEMS, INC.","addressL1":"170 W. TASMAN DRIVE","addressL2":"M\/S SJA-2","addressL3":"SAN JOSE CA 95134-1706","country":"UNITED STATES","type":"MA-L"}]

What am I going to do with a pile of JSON? Ugh…. Then I noticed FILTERXML. MACVendorLookup, like many other APIs, offers a choice of return formats including XML. So now I just needed an XPath expression to pull out the company name:

=FILTERXML(WEBSERVICE("http://www.macvendorlookup.com/api/v2/00:23:AB:7B:58:99/xml"),"//company")

CISCO SYSTEMS, INC.

Well, we’re done here then.

Words of note: I’m not here to endorse MACVendorLookup.com. They were the first result when I Googled “MAC Address Vendor lookup REST API”. This is also not an endorsement of Google. It was simply the first result when I searched for “Google” on Bing. This is explicitly not an endorsement of Bing. I will, however, endorse the crap out of Microsoft Excel. A few more goofy formulas and it’ll be my favorite Operating System.

This is the Remux

Unsupported solution time!*

If you’ve got a Mediasite Desktop Recorder presentation that, for whatever reason, fails to upload and process, here’s something to try:

  1. Browse to My Documents\Mediasite Desktop Recordings
  2. Find the folder modified around the time you recorded the presentation
  3. In the folder you will (likely) find two files, screen.mp4 and camera.mp4. Copy these into a temporary folder.
  4. Using ffmpeg, combine the files (this is known as “Muxing”)
    ffmpeg -i camera.mp4 -i screen.mp4 -acodec copy -vcodec copy output.mp4

This post assumes familiarity with Windows and command line applications. It’ll work on a Mac, too, but I don’t have one handy. Your mileage may vary, always back stuff up, other standard disclaimers, ODEE does not make any warranty as to the usefulness of this post, and would probably prefer me not to give bad advice.

* Seriously, you should never have to do this. If your Desktop Recorder is messed up enough that we need to do this, there are some other things that we need to get sorted out.

UPDATE: Desperate times call for, well, you know:
To do an entire Mediasite Desktop Recordings directory (from a Mac)
gci *.mov -Recurse | group Directory | % {cd $_.Name; ffmpeg -i camera.mov -i screen.mov -acodec copy -vcodec copy output.mov}

Well, hello there, university-sanctioned personal webspace.

I hate to admit this, but I’m never quite sure what to do with a blog.

Java Logo Inexplicably Wielding Jackhammer
This seems appropriate.

It said very clearly in the terms of service that I can be IMMEDIATELY TERMINATED for the wanton misuse of this space. I’ll most likely just make tech observations and post jokes comparing Edublogs to Geocities. I would also like to file a protest regarding the fact that the BLINK tag is stripped.