https://github.com/zephinzer/blogpost-linkedin-video-downloader
This repository contains a script that I've used to download a LinkedIn Live video
https://github.com/zephinzer/blogpost-linkedin-video-downloader
Last synced: 3 months ago
JSON representation
This repository contains a script that I've used to download a LinkedIn Live video
- Host: GitHub
- URL: https://github.com/zephinzer/blogpost-linkedin-video-downloader
- Owner: zephinzer
- Created: 2020-01-24T09:22:55.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2023-08-08T15:27:31.000Z (almost 2 years ago)
- Last Synced: 2024-12-31T06:30:36.694Z (5 months ago)
- Language: Shell
- Size: 2.93 KB
- Stars: 12
- Watchers: 4
- Forks: 3
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# LinkedIn Live Video Downloader
This repository contains a script that I've used to download LinkedIn Live videos.
To use the script, modify the `PUBLIC_URL` and `VIDEO_ID` variables in `linkedin.sh`.
---
title: Downloading LinkedIn Live Videos
published: false
description:
tags: linkedin,hack,download
---I [gave a talk recently at a community meetup](https://www.linkedin.com/video/live/urn:li:ugcPost:6608309192438710272/) where they were using the fancy new [LinkedIn Live](https://www.linkedin.com/help/linkedin/answer/100225/getting-started-with-linkedin-live?lang=en) to stream the event. Post-event, I decided I wanted a copy of the video but LinkedIn didn't seem to agree with me that I should have a copy of my video. Also, none of the existing *video downloader* websites worked, and none of the advice given by other folks online did too. Apparently LinkedIn had changed the way they did videos. So. The network tab it was!
> This short post documents how I managed to download a LinkedIn video with a little hacking on the Chrome Console and shell scripts. You should be comfortable with the command line for things to work out.
## Getting to the Target
So you've seen a LinkedIn post with a video that you want locally available. We begin by clicking on the video so that it opens up in a theatre-like mode. For my video, the URL looked like:
```
https://www.linkedin.com/video/live/urn:li:ugcPost:%UGC_POST_ID%/
```## Observing the Target
Once the page is loaded, **do not hit play** - open the Chrome Console and go to the Network tab. Now hit play and look out on the Network tab for a `manifest(format=m3u8-aapl-v3)` call. This call seems to be making a request to retrieve the possible variants of the video and the response should look similar to:
```
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=351536,RESOLUTION=340x192,CODECS="avc1.64000d,mp4a.40.5"
QualityLevels(200000)/Manifest(video,format=m3u8-aapl-v3,audiotrack=audio_und)
#EXT-X-STREAM-INF:BANDWIDTH=709236,RESOLUTION=384x216,CODECS="avc1.640015,mp4a.40.5"
QualityLevels(550000)/Manifest(video,format=m3u8-aapl-v3,audiotrack=audio_und)
#EXT-X-STREAM-INF:BANDWIDTH=1015836,RESOLUTION=512x288,CODECS="avc1.640015,mp4a.40.5"
QualityLevels(850000)/Manifest(video,format=m3u8-aapl-v3,audiotrack=audio_und)
#EXT-X-STREAM-INF:BANDWIDTH=1526836,RESOLUTION=704x396,CODECS="avc1.64001e,mp4a.40.5"
QualityLevels(1350000)/Manifest(video,format=m3u8-aapl-v3,audiotrack=audio_und)
#EXT-X-STREAM-INF:BANDWIDTH=2395536,RESOLUTION=960x540,CODECS="avc1.64001f,mp4a.40.5"
QualityLevels(2200000)/Manifest(video,format=m3u8-aapl-v3,audiotrack=audio_und)
#EXT-X-STREAM-INF:BANDWIDTH=3724136,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.5"
QualityLevels(3500000)/Manifest(video,format=m3u8-aapl-v3,audiotrack=audio_und)
#EXT-X-STREAM-INF:BANDWIDTH=138976,CODECS="mp4a.40.5"
QualityLevels(128000)/Manifest(audio_und,format=m3u8-aapl-v3)
```The above manifest is used to determine what `QualityLevel` should be streamed when the `BANDWIDTH` is above a certain rate. We'll see how that is used later.
## Meeting the Target
The next request of interest which should come shortly after the above request is completed should have the name `Manifest(video,format=m3u8-aapl-v3,audiotrack=audio_und)`. Clicking on it and going to the Response tab, you should see a huge file that consists of lines like these:
```
...
#EXTINF:2.000000,no-desc
Fragments(video=%FRAGMENT_ID_1%,format=m3u8-aapl-v3,audiotrack=audio_und)
#EXTINF:2.000000,no-desc
Fragments(video=%FRAGMENT_ID_2%,format=m3u8-aapl-v3,audiotrack=audio_und)
#EXTINF:2.000000,no-desc
Fragments(video=%FRAGMENT_ID_3%,format=m3u8-aapl-v3,audiotrack=audio_und)
...
```These apparently define the list of video fragments that stream through your browsers to form the full video.
Looking slightly below the above request, you'll find network calls named `Fragments(video=%FRAGMENT_ID%,format=m3u8-aapl-v3,audiotrack=audio_und)`. Well, doesn't that look familiar?
Check out the full URL of the call (right click on row, copy as cURL) of those requests and you should find it resembles the following format:
```
https://streamwus2-livectorprodmedia11-usw22.licdn.com/%UUID%/%SOME_VIDEO_ID%-livemanifest.ism/QualityLevels(2200000)/Fragments(video=%FRAGMENT_ID%,format=m3u8-aapl-v3,audiotrack=audio_und)
```Notice the `QualityLevels` and `Fragments`. This is the call that retrieves the fragments of data that are collated to form your video.
## Obtaining the Target
So we now know how the video is being streamed, we can replicate this minimally by:
1. Visit the video page, open network tab
2. Look out for call to `manifest...`, note the `QualityLevel`
3. Look out for the subsequent call to `Manifest...`, copy the URL as a cURL command
4. Referencing the `QualityLevel`s from step 2, modify the URL in the cURL request so that it's of the highest quality
5. Run the cURL command and pipe it to a `manifest` file.
6. You can extract all the fragment IDs using `cat ./manifest | grep -v '#' | cut -f 2 -d '=' | cut -f 1 -d ',' > ./fragment_ids`
7. Iterate through the list of fragments and issue a cURL request for each, piping the output to a file incrementally. That would look like:```sh
touch video.mp4
while read FRAGMENT_ID; do
echo 'processing ${FRAGMENT_ID}';
curl ... >> video.mp4
done <./fragment_ids;
```