Recovering a lost Red vs. Blue PSA

First posted by @NocontextRvB on Twitter, they asked if anyone who used Halo Waypoint remembers seeing this Red vs. Blue PSA.

In the comments user @reqsihw replies with some information. When I saw that video streams were still up I was interesting in seeing if I can get them.

So that is where we begin, with the two files initially referenced. http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism/manifest and http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ismv

I downloaded each file with curl.

curl "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ismv" --output "2060770-3.ismv"
curl "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism/manifest" --output "manifest"

But I also noticed manifest is after a .ism file, so I grabbed that as well.

curl "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism" --output "2060770-3.ism"

I also generated a sha256 hash of each file (for others to confirm they have the same file).

Get-FileHash -Algorithm SHA256 *

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          D26C730B590BEFA9EC9A4B85FF855E4A3BC6D781AAC9675EF05AF72BFF28BAB4       2060770-3.ism
SHA256          118D1365B7CC90E8EC165AC826BE3918B0B4061386513FF5BC2445856746EFB4       2060770-3.ismv
SHA256          9045C71B59DB43444D8B0D2647E01199B1C471F261521D4D5EB744820A6778F7       manifest

I also wanted to see the file size.

Get-ChildItem *

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           9/01/2026 12:06 PM           4768 2060770-3.ism
-a---           9/01/2026 12:05 PM      148467964 2060770-3.ismv
-a---           9/01/2026 12:05 PM          15034 manifest

Looking inside 2060770-3.ism I can see it also references 2060770-3.ismc so I took a stab at where it would be located and downloaded it.

curl "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ismc" --output "2060770-3.ismc"

I then also run the commands as I did before so we can keep a refernece of them.

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          9045C71B59DB43444D8B0D2647E01199B1C471F261521D4D5EB744820A6778F7       2060770-3.ismc
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           9/01/2026 12:10 PM          15034 2060770-3.ismc

This means that attempting to download the .ismc/manifest file results in the exact same file as just the .ismc. So going forward we will ignore the one that says manifest and just refer to it as the .ismc file.

For reference these three files have been uploaded to a GitHub repository associated with this post.

Looking up online what these files even mean I found some documentation that says there may or may not be a .isma file which would contain audio streams. I had a look for 2060770-3.isma but that was a 404. This does not mean there is no audio, it just means it is bundled in another file.

Opening up the .ism and .ismc files I can see they are both xml files. .ism referneces the .ismc and .ismv files. They also both have information about what is contained in the .ismv file and both indicate there is video and audio.

Using MediaInfo we can also extract the metadata of the this .ismv file (see 2060770-3-ismv-metadata.txt). But if we look at just a summary of that,

Stream 1 - Audio
Format                    : AAC LC
Format/Info               : Advanced Audio Codec
Codec ID                  : mp4a-40-2
Channel(s)                : 2 channels
Channel layout            : L R
Sampling rate             : 44.1 kHz

Stream 2 - Video 
Bit rate                  : 3768 kb/s
Width                     : 1280 pixels
Height                    : 720 pixels

Stream 3 - Video
Bit rate                  : 2 229 kb/s
Width                     : 960 pixels
Height                    : 540 pixels

Stream 4 - Video
Bit rate                  : 1 553 kb/s
Width                     : 796 pixels
Height                    : 448 pixels

Stream 5 - Video
Bit rate                  : 1 122 kb/s
Width                     : 716 pixels
Height                    : 404 pixels

Stream 6 - Video
Bit rate                  : 974 kb/s
Width                     : 640 pixels
Height                    : 360 pixels

Stream 7 - Video
Bit rate                  : 658 kb/s
Width                     : 512 pixels
Height                    : 288 pixels

Stream 8 - Video
Bit rate                  : 520 kb/s
Width                     : 512 pixels
Height                    : 288 pixels

Stream 9 - Video 
Bit rate                  : 392 kb/s
Width                     : 512 pixels
Height                    : 288 pixels

Stream 10 - Video
Bit rate                  : 212 kb/s
Width                     : 320 pixels
Height                    : 180 pixels

Stream 11 - Video
Bit rate                  : 133 kb/s
Width                     : 240 pixels
Height                    : 136 pixels

So this single file contains 10 video tracks and 1 audio track.

Attempting to play these files in VLC or Media Player Classic result in nothing. However you can right mouse click and look at audio track and video track information to see it matches above.

Converting the files with FFMpeg

Next I used FFMpeg to try convert the file to an mp4.

ffmpeg -i 2060770-3.ismv output.mp4

Which produces a 0kb file and errors with this,

[vf#0:0 @ 000002d939415b80] Cannot determine format of input 0:1 after EOF
[vf#0:0 @ 000002d939415b80] Task finished with error code: -1094995529 (Invalid data found when processing input)
[vf#0:0 @ 000002d939415b80] Terminating thread with return code -1094995529 (Invalid data found when processing input)
[af#0:1 @ 000002d93b6b6d80] No filtered frames for output stream, trying to initialize anyway.
[vost#0:0/libx264 @ 000002d9393ff100] [enc:libx264 @ 000002d93b4b9900] Could not open encoder before EOF
[vost#0:0/libx264 @ 000002d9393ff100] Task finished with error code: -22 (Invalid argument)
[vost#0:0/libx264 @ 000002d9393ff100] Terminating thread with return code -22 (Invalid argument)
[out#0/mp4 @ 000002d939404540] Nothing was written into output file, because at least one of its streams received no packets.

I tried many things to get any data out. This may be a skill issue. I tried to force the input a/v codecs.

ffmpeg -acodec aac -vcodec h264 -i "2060770-3.ismv" -c copy -an output.mp4

I tried to ignore the audio and just get video

ffmpeg -i "2060770-3.ismv" -c:v copy -an output.mp4

I tried to ignore the video and just get the audio.

ffmpeg -i "2060770-3.ismv" -vn -c:a copy output.m4a

I tried to map the input a/v streams to pick a specific one.

ffmpeg -i "2060770-3.ismv" -map 0:v:1 -map 0:a:0 -c:v copy -c:a copy output.mp4

Some of these errored with "track 1: codec frame size is not set" so I tried to set the frame size for aac input.

ffmpeg -c:a aac -frame_size 1024 -i "2060770-3.ismv" -vn -c:a copy output.m4a

Almost all of the above still errored with "Output file is empty, nothing was encoded". I even tried various combinations of the above and I got nada.

Tryin yt-dlp instead

Going back to the start urls I had a look at using yt-dlp instead. First up was to just list the streams it can see by using the -F argument.

yt-dlp -F "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism/manifest"

[generic] Extracting URL: http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism/manifest
[generic] manifest: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] manifest: Extracting information
WARNING: [generic] None is not a supported codec
[info] Available formats for manifest:
ID   EXT  RESOLUTION │   TBR PROTO │ VCODEC   VBR ACODEC     MORE INFO
──────────────────────────────────────────────────────────────────────
150  ismv 240x136    │  150k ism   │ H264    150k video only [und]
240  ismv 320x180    │  240k ism   │ H264    240k video only [und]
440  ismv 512x288    │  440k ism   │ H264    440k video only [und]
640  ismv 512x288    │  640k ism   │ H264    640k video only [und]
840  ismv 512x288    │  840k ism   │ H264    840k video only [und]
1240 ismv 640x360    │ 1240k ism   │ H264   1240k video only [und]
1440 ismv 716x404    │ 1440k ism   │ H264   1440k video only [und]
2056 ismv 796x448    │ 2056k ism   │ H264   2056k video only [und]
2962 ismv 960x540    │ 2962k ism   │ H264   2962k video only [und]
5027 ismv 1280x720   │ 5027k ism   │ H264   5027k video only [und]

Oddly enough without /manifest we get a much different result.

yt-dlp -F "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism"

[generic] Extracting URL: http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism
[generic] 2060770-3: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] 2060770-3: Extracting information
[info] Available formats for 2060770-3:
ID        EXT  RESOLUTION │   TBR PROTO │ VCODEC  ACODEC
─────────────────────────────────────────────────────────
http-5027 ismv unknown    │ 5027k http  │ unknown unknown
yt-dlp -F "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ismv"

[generic] Extracting URL: http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ismv
[generic] 2060770-3: Downloading webpage
[info] Available formats for 2060770-3:
ID   EXT  RESOLUTION │ PROTO │ VCODEC  ACODEC
──────────────────────────────────────────────
fmp4 ismv unknown    │ http  │ unknown unknown
yt-dlp -F "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ismc"

[generic] Extracting URL: http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ismc
[generic] 2060770-3: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] 2060770-3: Extracting information
WARNING: [generic] None is not a supported codec
[info] Available formats for 2060770-3:
ID   EXT  RESOLUTION │   TBR PROTO │ VCODEC   VBR ACODEC     MORE INFO
──────────────────────────────────────────────────────────────────────
150  ismv 240x136    │  150k ism   │ H264    150k video only [und]
240  ismv 320x180    │  240k ism   │ H264    240k video only [und]
440  ismv 512x288    │  440k ism   │ H264    440k video only [und]
640  ismv 512x288    │  640k ism   │ H264    640k video only [und]
840  ismv 512x288    │  840k ism   │ H264    840k video only [und]
1240 ismv 640x360    │ 1240k ism   │ H264   1240k video only [und]
1440 ismv 716x404    │ 1440k ism   │ H264   1440k video only [und]
2056 ismv 796x448    │ 2056k ism   │ H264   2056k video only [und]
2962 ismv 960x540    │ 2962k ism   │ H264   2962k video only [und]
5027 ismv 1280x720   │ 5027k ism   │ H264   5027k video only [und]

So yt-dlp doesn't explicitly say that it has found audio in any of these streams. So I let it download each of the above with default settings (which is to fetch the highest quality of each stream it could find) and see what the result is.

This first result downloaded a 45MiB file.

yt-dlp "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism/manifest"

[generic] Extracting URL: http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism/manifest
[generic] manifest: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] manifest: Extracting information
WARNING: [generic] None is not a supported codec
[info] manifest: Downloading 1 format(s): 5027
[ism] Total fragments: 51
[download] Destination: manifest [manifest].ismv
[download] 100% of   45.68MiB in 00:00:03 at 14.38MiB/s

The second without /manifest resulted in a 404.

yt-dlp "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism"

[generic] Extracting URL: http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism
[generic] 2060770-3: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] 2060770-3: Extracting information
[info] 2060770-3: Downloading 1 format(s): http-5027
ERROR: unable to download video data: HTTP Error 404: Not Found

Third resulted in a 141MiB file.

yt-dlp "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ismv"

[generic] Extracting URL: http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ismv
[generic] 2060770-3: Downloading webpage
[info] 2060770-3: Downloading 1 format(s): fmp4
[download] Destination: 2060770-3 [2060770-3].ismv
[download] 100% of  141.59MiB in 00:00:03 at 35.94MiB/s

Trying the fourth it kept saying it was getting 404s while trying to get all 51 fragments.

yt-dlp "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ismc"

[generic] Extracting URL: http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ismc
[generic] 2060770-3: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] 2060770-3: Extracting information
WARNING: [generic] None is not a supported codec
[info] 2060770-3: Downloading 1 format(s): 5027
[ism] Total fragments: 51
[download] Destination: 2060770-3 [2060770-3].ismv
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 1 (1/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 1 (2/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 1 (3/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 1 (4/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 1 (5/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 1 (6/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 1 (7/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 1 (8/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 1 (9/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 1 (10/10)...
[download] Skipping fragment 1 ...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 2 (1/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 2 (2/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 2 (3/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 2 (4/10)...
[download] Got error: HTTP Error 404: Not Found. Retrying fragment 2 (5/10)...

So after all of that I have 2 files. 2060770-3 [2060770-3].ismv (141.59MiB) and manifest [manifest].ismv (45.68MiB). This file CAN be converted to an mp4 with ffmpeg.

ffmpeg -i "manifest [manifest].ismv" -an -c:v libx264 "manifest [manifest].mp4"

The 2060770-3 [2060770-3].ismv does not play and extracting all its data with MediaInfo seems to say it is the same data. Checking the file hashes we can actually just confirm it is an identical file to what we had previously.

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          118D1365B7CC90E8EC165AC826BE3918B0B4061386513FF5BC2445856746EFB4       2060770-3 [2060770-3].ismv
SHA256          36CCC5665CED8AE6D854EBE02DC7D0163BB3C2D832ABE3357BCA43C28CD5FB7E       manifest [manifest].ismv

So now that we know manifest [manifest].ismv it plays we can also confirm it is a 1:41 long video, but unfortunately it has no audio. Fun fact - if you go look at the streams in the manifest file you can also see that the first video stream was actually 45.6 MiB. So this IS the first video stream yoinked as its own file.

So then what did yt-dlp do differently that I did with FFMpeg, we lets have a look with the -vvv verbose argument.

yt-dlp -vvv "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism/manifest"
[debug] Command-line config: ['-vvv', 'http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism/manifest']
[debug] Encodings: locale cp1252, fs utf-8, pref cp1252, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version stable@2025.12.08 from yt-dlp/yt-dlp [7a52ff29d] (win_exe)
[debug] Python 3.10.11 (CPython AMD64 64bit) - Windows-10-10.0.26200-SP0 (OpenSSL 1.1.1t  7 Feb 2023)
[debug] exe versions: ffmpeg N-121938-g2456a39581-20251130 (setts), ffprobe N-121938-g2456a39581-20251130
[debug] Optional libraries: Cryptodome-3.23.0, brotli-1.2.0, certifi-2025.11.12, curl_cffi-0.13.0, mutagen-1.47.0, requests-2.32.5, sqlite3-3.40.1, urllib3-2.6.0, websockets-15.0.1, yt_dlp_ejs-0.3.2
[debug] JS runtimes: deno-2.6.4
[debug] Proxy map: {}
[debug] Request Handlers: urllib, requests, websockets, curl_cffi
[debug] Plugin directories: none
[debug] Loaded 1854 extractors
[generic] Extracting URL: http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism/manifest
[generic] manifest: Downloading webpage
WARNING: [generic] Falling back on generic information extractor
[generic] manifest: Extracting information
WARNING: [generic] None is not a supported codec
[debug] Identified a ISM manifest
[debug] Formats sorted by: hasvid, ie_pref, lang, quality, res, fps, hdr:12(7), vcodec, channels, acodec, size, br, asr, proto, vext, aext, hasaud, source, id
[debug] Default format spec: bestvideo*+bestaudio/best
[info] manifest: Downloading 1 format(s): 5027
[debug] Invoking ism downloader on "http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism/manifest"
[ism] Total fragments: 51
[download] Destination: manifest [manifest].ismv
[debug] File locking is not supported. Proceeding without locking
[download] 100% of   45.68MiB in 00:00:02 at 19.54MiB/s

Having a look through this I think the important part is "[debug] Invoking ism downloader on". We can look at the source code for yt-dlp, and specifically the ism downloader, ism.py. Looking at it, it seems it is using fragments to download the file which would imply it is not using a large .ismv file.

But where are the fragments?

And what even are fragments? Video streaming services typically split a file into many small parts. Depending on the file thousands. This particilar 1:41 video has 51 fragments. This means that if you start streaming a video it will download the first few fragments so you can start playing. If you leave you won't waste bandwidth getting videos chunks you won't see. Additionally it allows you (or the player automatically) to change the quality you are seeing.

If your internet speed is slow it will get the fragments that are a lower resolution. If your internet speed is higher, then it can get the larger fragments that are a higher resolution. This is how YouTubes quality picker works.

If we look back at the .ismc file we can see that it has an attribute in the StreamIndex called URL, "QualityLevels({bitrate})/Fragments(video={start time})". Having a poke around I was able to confirm that this is contains some placeholder text which can be replaced b a video bitrate and current position to return a particula fragment. For example, http://videos.halowaypoint.com/videos1/db6723e6038f41a1819e8e71be7776a0/2060770-3.ism/QualityLevels(5027000)/Fragments(video=0) is the first video fragment of the PSA.

fragDownloader

To help download all of these video (and audio) fragments I created a tool called fragDownloader.

In its current state it can download all the fragments... and that is about where I am up to. It downloads about 45MB of video fragments and about 1MB of audio fragments. These fragments need to be joined together and then merged into a single file which contains the audio and video. It is the first half of that sentence which is the tricky bit.

I did try to join the files by creating video_appended and audio_appended files. It just slaps them together one after another. This does not work as I had hoped.

What is next?

I don't know when I'll get time to jump back in, so others are more than welcome to continue where I left off.

I believe what needs to happen is that ism.py from the yt-dlp repository contains the missing piece. It can handle the individual moov, traf, and whatever else happens inside the fragments and moves them, or removes them, or whatever it is doing to then concatinate the files into a single resource.

Because we have the correct amount of video and audio data I believe that it is all there and not corrupted.

What is exciting is that we may be able to not just recover this missing RvB PSA, we may be able to recover and archive A LOT more of these videos from halo.xbox.com that appear to still exist but are unreacahble from any front facing web player.