destreamer
destreamer copied to clipboard
Microsoft teams?
Are you going to implement in the near future the option to download videos from Microsoft Teams too? (Is it somewhat possible?) Thanks in advance!
Aren't those recordings already on MS Stream?
Aren't those recordings already on MS Stream?
No, it's actually not. Some videos can't be played in Microsoft Stream. I've tried a couple of times before, no success. I only have the Microsoft Team Link to the video, and even changing it to a Microsoft Stream URL (with the ID) won't let me download the video aka gives me a 404 error.
How can I download videos from Microsoft Teams? (if this is already possible)
Exactly, not all videos are available in Microsoft Stream. https://teams.microsoft.com/l/meetup-join/19%3Ameeting_YWJiZGY1ODYtNDE1Ny00MDM5LWJmMTYtNDA3Y2FiZTgxYTMz%40thread.v2/0?context=%7B%22Tid%22%3A%224f0132f7-dd79-424c-9089-b22764c40ebd%22%2C%22Oid%22%3A%2264979a43-443e-4edc-87b2-74dd8ff11f2d%22%2C%22IsBroadcastMeeting%22%3Atrue%7D This video is an example.
Sorry, i don't know how that works, could be MS Teams hosting property. This is out of scope for destreamer.
@alessandrotodisco Did you by any chance find a solution on how to download those kinds of videos? I'm having the same problem now and I can't figure anything out other than using a screen recorder :/
@Olgabrezel Unfortunately no. The only boring solution I found is by using a screen recorder..
@Olgabrezel @alessandrotodisco I'm sorry that I can't help you but I have no such video at my disposal to do some reverse engineering and tell you if and how to download them :/
@lukaarma It would be so awesome if you could try that! For such a video - the link that alessandro posted above works for me. I just logged in on Microsoft and chose to participate anonymously and the video loaded. So perhaps you could use that for testing?
Edit: I just tried again and for that video it seems logging in is not even required. In my case, however, it is only available for members of my organization after a login.
@Olgabrezel @alessandrotodisco did the deed, I reversed the platform logic and lo and behold I have the url of the video and the auth token
BUT
the video is streamed using SmoothStreamingMedia, proprietary format of Microsoft (@snobu I know you said it was out of scope, and I wont implement this in destreamer, but if you have any idea how to do this without a custom program please let me know if you have time) I have to do some more digging to find a good and working downloader for that but in the meantime I leave these here
SOURCE => videojs.getPlayers().teamsDefaultPlayer.options_.sourceList.slice(-1).pop().src
AUTH => document.cookie.match(/TSAUTHCOOKIE=(.*?);/)[1]
leave me more time to find a suitable downloader, or in the worst case scenario I have to write something custom for you but that would require a lot of time
FFmpeg may be able to download SmoothStreaming as is. If it's not i believe we can form an HLS URL out of it, i highly doubt they only publish a SS stream. Can you post the manifest URL (video GUID redacted) so i can have a look?
@snobu
Ffmpeg doesn't parse SS playlists
I tried getting the hls as we do in Destreamer with (format= hls.codeword.thatIdontRemember) at the end of the URL instead of (format=SS.thatIdontRemember) but no dice the server doesn't have the encoding
As soon as I'm on my pc I'll send you an email with links and the SS playlist I was able to download
As per converting to HLS it should be quite easy with a smal script so I'll look into that
Keeping this open so that maybe we don't get duplicates
@snobu You said that this is out of scope for Destreamer, so if Iwanted to implement this I should do it in another tool? Or can I try and implement it here?
@lukaarma I would never say no to a PR if you implement that. However i would probably wait until we learn more about the SharePoint/OneDrive MSStream vNext thing, since it may affect how Teams saves recordings as well. I know how the house operates :)
@lukaarma Have you abandoned this idea?
Responding to the musings above about someone muddling through this process by hand and describing what they found:
I played one of these public Microsoft-Teams-hosted videos in a web browser while capturing packets and eventually figured out how to get a plain .mp4 of the stream contents (no screen-recording or re-encoding). Steps:
- The root of most of this is the 'manifest' request. It had a URL like
https://endpoint1-blah.blah.blah.blah.office.net/11111111-2222-3333-4444-555555555555/66666666-7777-8888-9999-aaaaaaaaaaaa.ism/manifest(format=mpd-time-csf)
- Per Den Delimarsky's advice, you can change the
format=mpd-time-csf
part toformat=m3u8-aapl-v3
to get the same stream metadata in HLS/m3u8 format instead of the MPEG-DASH XML format. I don't think this is necessary, but this is the path I took, so I mention it here in case it was important. ffmpeg supports both formats. This change also had the effect of giving me one mp4 stream with both audio and video instead of separate audio and video streams. - The top-level manifest offers multiple URLs for different quality levels. They're something like
https://endpoint1-blah.blah.blah.blah.office.net/11111111-2222-3333-4444-555555555555/66666666-7777-8888-9999-aaaaaaaaaaaa.ism/QualityLevels(128000)/Manifest(audio,format=m3u8-aapl-v3)
and they're full of entries likeFragments(audio=12345678999,format=m3u8-aapl-v3)
. Here's where the problems start:- Problem: Tools that read this fragment-list combine the fragment names and the base URL in a different way than the server expects. ffmpeg looks for the fragments at the URL
-
https://endpoint1-blah.blah.blah.blah.office.net/11111111-2222-3333-4444-555555555555/66666666-7777-8888-9999-aaaaaaaaaaaa.ism/QualityLevels(128000)/Manifest(audio,format=m3u8-aapl-v3)/Fragments(audio=12345678999,format=m3u8-aapl-v3)
but the server serves the file at the URL -
https://endpoint1-blah.blah.blah.blah.office.net/11111111-2222-3333-4444-555555555555/66666666-7777-8888-9999-aaaaaaaaaaaa.ism/QualityLevels(128000)/Fragments(audio=12345678999,format=m3u8-aapl-v3)
Note the missingManifest(...)
penultimate path component.
-
- Problem: The line breaks in the fragments list are
\r\n
and sometimes ffmpeg breaks lines by\n
, leaving the\r
on the end of the URL, which the server rejects as 400. - Problem: The server responses with the fragments of video data have a
content-type: video/mp2t
(oraudio/mp4
andvideo/mp4
for the mpd-time-csf/MPEG-DASH streams) header and no Content-Encoding header. But the contents are actually AES-128 encrypted. ffmpeg attempts to support this! Invoking ffmpeg on the manifest URL directly results in it muttering aboutcrypto+https://...
URLs, so it knows it needs to decrypt something, but it isn't able to find the key. Even after I separately extracted the key and was able to provide it to ffmpeg directly on the command line, I was unable to get ffmpeg to play the encrypted fragments, sometimes with silly results like ffmpeg trying to aes-decrypt the text file that lists the fragment names and then failing to parse it.
- Problem: Tools that read this fragment-list combine the fragment names and the base URL in a different way than the server expects. ffmpeg looks for the fragments at the URL
- Anyway, I just fetched and then concatenated all the Fragments. (Concatenate first. Concatenate-then-decrypt worked. Decrypt-then-concatenate did not work.)
- From the packet capture, I observe that the browser got the decryption key from a no-body
POST
tohttps://blah.keydelivery.blah.media.azure.net/?KID=bbbbbbbb-cccc-dddd-eeee-ffffffffffff
, which returns a 16-byte body that is the decryption key. This link is in the MPEG-DASH manifest askeyUriTemplate
. The browser sends aHMACSHA256
Bearer
Authorization
header, and I get 401 if I don't send one.- It looks like this token comes from an earlier request to
https://prd.attend.teams.microsoft.com/teams/66666666-7777-8888-9999-aaaaaaaaaaaa/bbbbbbbb-cccc-dddd-eeee-ffffffffffff/19:meeting_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxxxxxx@thread.v2/0?role=Attendee
, and all those fields were in the stream-viewing/'meeting-invite' link.
- It looks like this token comes from an earlier request to
- The MPEG-DASH manifest also has the decryption IV.
- With the key, iv, and concatenated fragments, I can now decrypt the stream:
openssl enc -d -aes128 -iv 00000000111111112222222233333333 -K 44444444555555556666666677777777 -in allthefragments -out stream.mp4
Again, these are public videos—nothing here bypasses any access controls. These are just the details of how Microsoft Teams implements public videos: By serving them encrypted and then offering the key to anyone that asks.
Responding to the musings above about someone muddling through this process by hand and describing what they found:
I played one of these public Microsoft-Teams-hosted videos in a web browser while capturing packets and eventually figured out how to get a plain .mp4 of the stream contents (no screen-recording or re-encoding). Steps:
The root of most of this is the 'manifest' request. It had a URL like
https://endpoint1-blah.blah.blah.blah.office.net/11111111-2222-3333-4444-555555555555/66666666-7777-8888-9999-aaaaaaaaaaaa.ism/manifest(format=mpd-time-csf)
Per Den Delimarsky's advice, you can change the
format=mpd-time-csf
part toformat=m3u8-aapl-v3
to get the same stream metadata in HLS/m3u8 format instead of the MPEG-DASH XML format. I don't think this is necessary, but this is the path I took, so I mention it here in case it was important. ffmpeg supports both formats. This change also had the effect of giving me one mp4 stream with both audio and video instead of separate audio and video streams.The top-level manifest offers multiple URLs for different quality levels. They're something like
https://endpoint1-blah.blah.blah.blah.office.net/11111111-2222-3333-4444-555555555555/66666666-7777-8888-9999-aaaaaaaaaaaa.ism/QualityLevels(128000)/Manifest(audio,format=m3u8-aapl-v3)
and they're full of entries likeFragments(audio=12345678999,format=m3u8-aapl-v3)
. Here's where the problems start:
Problem: Tools that read this fragment-list combine the fragment names and the base URL in a different way than the server expects. ffmpeg looks for the fragments at the URL
https://endpoint1-blah.blah.blah.blah.office.net/11111111-2222-3333-4444-555555555555/66666666-7777-8888-9999-aaaaaaaaaaaa.ism/QualityLevels(128000)/Manifest(audio,format=m3u8-aapl-v3)/Fragments(audio=12345678999,format=m3u8-aapl-v3)
but the server serves the file at the URLhttps://endpoint1-blah.blah.blah.blah.office.net/11111111-2222-3333-4444-555555555555/66666666-7777-8888-9999-aaaaaaaaaaaa.ism/QualityLevels(128000)/Fragments(audio=12345678999,format=m3u8-aapl-v3)
Note the missingManifest(...)
penultimate path component.Problem: The line breaks in the fragments list are
\r\n
and sometimes ffmpeg breaks lines by\n
, leaving the\r
on the end of the URL, which the server rejects as 400.Problem: The server responses with the fragments of video data have a
content-type: video/mp2t
(oraudio/mp4
andvideo/mp4
for the mpd-time-csf/MPEG-DASH streams) header and no Content-Encoding header. But the contents are actually AES-128 encrypted. ffmpeg attempts to support this! Invoking ffmpeg on the manifest URL directly results in it muttering aboutcrypto+https://...
URLs, so it knows it needs to decrypt something, but it isn't able to find the key. Even after I separately extracted the key and was able to provide it to ffmpeg directly on the command line, I was unable to get ffmpeg to play the encrypted fragments, sometimes with silly results like ffmpeg trying to aes-decrypt the text file that lists the fragment names and then failing to parse it.Anyway, I just fetched and then concatenated all the Fragments. (Concatenate first. Concatenate-then-decrypt worked. Decrypt-then-concatenate did not work.)
From the packet capture, I observe that the browser got the decryption key from a no-body
POST
tohttps://blah.keydelivery.blah.media.azure.net/?KID=bbbbbbbb-cccc-dddd-eeee-ffffffffffff
, which returns a 16-byte body that is the decryption key. This link is in the MPEG-DASH manifest askeyUriTemplate
. The browser sends aHMACSHA256
Bearer
Authorization
header, and I get 401 if I don't send one.
- It looks like this token comes from an earlier request to
https://prd.attend.teams.microsoft.com/teams/66666666-7777-8888-9999-aaaaaaaaaaaa/bbbbbbbb-cccc-dddd-eeee-ffffffffffff/19:meeting_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXxxxxxxxxxxxxxxxxxx@thread.v2/0?role=Attendee
, and all those fields were in the stream-viewing/'meeting-invite' link.The MPEG-DASH manifest also has the decryption IV.
With the key, iv, and concatenated fragments, I can now decrypt the stream:
openssl enc -d -aes128 -iv 00000000111111112222222233333333 -K 44444444555555556666666677777777 -in allthefragments -out stream.mp4
Again, these are public videos—nothing here bypasses any access controls. These are just the details of how Microsoft Teams implements public videos: By serving them encrypted and then offering the key to anyone that asks.
Could you please elaborate what you did from step 3 and forward? I have the KID, Bearer, Issuer, HMACSHA256, response from the keydelivery and I think all the components but I can't get it to work; how do I concatenate the streams for example? ffmpeg refuses to do anything with the crypto+https-URLs and Google is worthless when trying to find anything with a plus sign :/
how do I concatenate the streams for example?
cat * > allthefragments
@chkno thanks, what worked for me is changing the format of manifest, then i downloaded encrypted parts via yt-dlp and aria2c, then used openssl with AES and IV on each encrypted part, then used ffmpeg to combine decrypted parts them into one file
I was trying to automate it, but I couldn't figure out the way how to obtain the HMACSHA256 in automated way to be used for auth... any ideas?