Last active
December 7, 2018 00:38
-
-
Save tillig/20f8656b9622f9dcd7ace41f3f55b9ed to your computer and use it in GitHub Desktop.
Download .m3u8 contents for ffmpeg concatenation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.Synopsis | |
Downloads an M3U8 playlist and the subsequent TS files, ready for combining. | |
.DESCRIPTION | |
Using the headers from an authenticated session with a video provider, download the | |
contents of a playlist and prepare the TS files in the playlist for merging. | |
Assuming you have your headers exported as JSON like this... | |
{ | |
"Host": "video.somesite.com", | |
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", | |
"Accept": "*/*", | |
"Referer": "https://course.somesite.com/play/1234", | |
"Accept-Encoding": "gzip, deflate, br", | |
"Accept-Language": "en-US,en;q=0.9", | |
"Cookie": "..." | |
} | |
...you can get your headers in hashtable format like: | |
$headers = @{} | |
(Get-Content .\request_headers.json | ConvertFrom-Json).PSObject.Properties | %{ $headers[$_.Name] = $_.Value } | |
You can use ffmpeg to merge the ts files later (https://trac.ffmpeg.org/wiki/Concatenate) with the | |
ffmpeg-input.txt that will also be generated as the download runs. | |
.EXAMPLE | |
.\Get-M3u8Content.ps1 -Headers $headers -PlaylistUrl https://video.somesite.com/1234/playlist.m3u8 -DestinationFolder .\destination | |
.EXAMPLE | |
ffmpeg -f concat -safe 0 -i ffmpeg-input.txt -c copy fullvideo.mp4 | |
#> | |
[CmdletBinding()] | |
Param( | |
[Parameter( | |
Mandatory=$True, | |
HelpMessage="The set of headers Chrome is using to request the M3U8 - get these out of dev tools.")] | |
[ValidateNotNull()] | |
[Hashtable] | |
$Headers, | |
[Parameter( | |
Mandatory=$True, | |
HelpMessage="Full URL to the .m3u8 playlist.")] | |
[ValidateNotNull()] | |
[System.Uri] | |
$PlaylistUrl, | |
[Parameter( | |
Mandatory=$True, | |
HelpMessage="The folder where the .ts files will end up.")] | |
[ValidateNotNullOrEmpty()] | |
[string] | |
$DestinationFolder | |
) | |
Begin | |
{ | |
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 | |
$m3u8Filename = $PlaylistUrl.Segments[$PlaylistUrl.Segments.Length - 1] | |
If (-Not (Test-Path $DestinationFolder -PathType Container)) | |
{ | |
Write-Verbose "Creating destination folder $DestinationFolder" | |
New-Item $DestinationFolder -ItemType Directory | |
} | |
$m3u8Filename = Join-Path -Path $DestinationFolder -ChildPath $PlaylistUrl.Segments[$PlaylistUrl.Segments.Length - 1] | |
Write-Verbose "M3U8 will be saved at $m3u8Filename" | |
$ffmpegPlaylist = Join-Path -Path $DestinationFolder -ChildPath "ffmpeg-input.txt" | |
# Gets the expected length of the file and double-checks that the result was correct. | |
# For auto-resume, double-checks the existing file is the expected length and re-downloads | |
# if not. | |
function DownloadFile([string]$uri, [string]$destination, [switch]$skipSizeCheck = $False) | |
{ | |
$OriginalProgressPreference = $ProgressPreference | |
If (-Not $skipSizeCheck) | |
{ | |
$expectedLength = 0 | |
Try | |
{ | |
# Disabling progress on Invoke-WebRequest makes it WAY faster. | |
$ProgressPreference = 'SilentlyContinue' | |
$head = Invoke-WebRequest -Headers $Headers -Method HEAD -Uri $uri | |
$expectedLength = [System.Int32]($head.Headers["Content-Length"]) | |
} | |
Finally | |
{ | |
$ProgressPreference = $OriginalProgressPreference | |
} | |
Write-Verbose "Expectd size of $destination is $expectedLength" | |
} | |
If (Test-Path $destination) | |
{ | |
If ($skipSizeCheck -Or ((Get-Item $destination).Length -Eq $expectedLength)) | |
{ | |
# We're not checking sizes and just trusting the downloaded file is good OR | |
# We ensure the expected size is the actual size. | |
# Either way, we call it good. | |
Write-Verbose "Bypassing re-download of $uri." | |
Return $False | |
} | |
# The file exists but we're re-downloading because it's not right. | |
Remove-Item $destination -Force | |
} | |
Write-Verbose "Downloading $uri to $destination" | |
Try | |
{ | |
# Disabling progress on Invoke-WebRequest makes it WAY faster. | |
$ProgressPreference = 'SilentlyContinue' | |
Invoke-WebRequest -Headers $Headers -Method GET -Uri $uri -OutFile $destination | |
} | |
Finally | |
{ | |
$ProgressPreference = $OriginalProgressPreference | |
} | |
If ((-Not $skipSizeCheck) -And ((Get-Item $destination).Length -Ne $expectedLength)) | |
{ | |
throw "Failed to download $uri" | |
} | |
Return $True | |
} | |
} | |
Process | |
{ | |
DownloadFile $PlaylistUrl $m3u8Filename -skipSizeCheck | Out-Null | |
$tsFiles = Get-Content $m3u8Filename | %{ $_.Trim() } | Where-Object { -Not $_.StartsWith('#') -And $_.Length -Gt 0 } | |
If ($tsFiles.Length -Eq 0) | |
{ | |
throw "No TS files found in the M3U8 file." | |
} | |
$downloadCount = 0; | |
$tsFiles | %{ | |
$baseTsFilename = $_ | |
#Ensure the local path written to the input file is absolute. | |
$localTsFilename = Join-Path -Path $DestinationFolder -ChildPath $baseTsFilename | |
$localTsFilename = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($localTsFilename) | |
$tsUrl = $Null | |
$downloadCount++ | |
If (-Not [System.Uri]::TryCreate($PlaylistUrl, [System.Uri]$baseTsFilename, [ref]$tsUrl)) | |
{ | |
throw "Unable to determine location of $baseTsFilename in relation to $PlaylistUrl" | |
} | |
Write-Progress -Activity "Downloading M3U8 contents" -Id 1 -CurrentOperation "Downloading $tsUrl" -PercentComplete (($downloadCount / $tsFiles.Length) * 100) | |
$downloaded = DownloadFile $tsUrl $localTsFilename | |
If ($downloaded) | |
{ | |
Add-Content -Value "file '$localTsFilename'" -Path $ffmpegPlaylist | |
} | |
} | |
} | |
End | |
{ | |
Write-Progress -Activity "Downloading M3U8 contents" -Id 1 -Completed | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment