Skip to content

Instantly share code, notes, and snippets.

@A2va
Created January 19, 2022 18:19
Show Gist options
  • Save A2va/305e050bfb4d9e734bcf793391f96213 to your computer and use it in GitHub Desktop.
Save A2va/305e050bfb4d9e734bcf793391f96213 to your computer and use it in GitHub Desktop.
Convert media file to a chromecast compatible
# PowerShell version 7 and higher, the default intalled on Windows is 5.1
# https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.1
# on Windows Terminal a profiles for PowerShell 7 is directly created.
# ffmpeg and ffprobe must be on PATH.
<#
.DESCRIPTION
Convert a media file to format compatible with chromecast.
PowerShell version 7 and higher is needed, the default intalled on Windows is 5.1.
.PARAMETER i
Input file to be converted.
.PARAMETER remove-karaoke
Remove karaoke effect from subtitle
.PARAMETER burn-sub
Burn subtitle on video stream.
.PARAMETER force-search-replace
Force srt subtitle. If a subtitle is already compatible with Chromecast, convert it to srt.
.PARAMETER search
Search a text in subtitle.
.PARAMETER replace
Replace search text by replace text.
.EXAMPLE
chromecast.ps1 -i test.mp4 -remove-karaoke
.LINK
Install powershell 7: https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.1
#>
param (
[Alias('input')]
# Validate file path
[ValidateScript({
if (-Not ($_ | Test-Path) ) {
throw "File or folder does not exist"
}
if (-Not ($_ | Test-Path -PathType Leaf) ) {
throw "The Path argument must be a file. Folder paths are not allowed."
}
return $true
})] [string] $i,
[switch]${remove-karaoke} = $false,
[switch]${burn-sub} = $false,
[string[]] $search = @(),
[string[]] $replace = @()
)
function Write-ColorOutput($ForegroundColor) {
# Save the current color
$fc = $host.UI.RawUI.ForegroundColor
# Set the new color
$host.UI.RawUI.ForegroundColor = $ForegroundColor
# Output
if ($args) {
Write-Output $args
}
else {
$input | Write-Output
}
# restore the original color
$host.UI.RawUI.ForegroundColor = $fc
}
#####################################
# Check ffmpeg and ffprobe available
#####################################
ffprobe.exe
if ($LastExitCode -ne 0) {
Write-ColorOutput red ("ffprobe error.Maybe the executable is not on path")
}
ffmpeg.exe
if ($LastExitCode -ne 0) {
Write-ColorOutput red ("ffprobe error.Maybe the executable is not on path")
}
#######################
# Check nvenc available
#######################
$hwaccel_available = $false
# $list = "encoders","decoders","filters"
$list = "encoders"
foreach ($i in $list) {
$output = ffmpeg -hide_banner -$i
if ($output -match "nvenc") {
$hwaccel_available = $true
}
}
$filename = Convert-Path $i
if (Test-Path -Path $filename) {
Write-ColorOutput red ("No file provided")
Exit
}
if($search.length -ne $replace.length) {
Write-ColorOutput red ("No search/replace arguments have not the same length")
Exit
}
# Final ffmpeg cmd
$ffmpeg_input = ""
$pre_input = "" # Pre input cmd
$post_input = "" # Post input cmd
$remux = $false
# Move flag value to better variable name
$burn_sub = ${burn-sub}
$remove_karaoke = ${remove-karaoke}
# The reaseon of 2>&1 in ffprobe cmd it's because ffprobe and ffmpeg
# output on stderr and not stdout. The 2>&1 operator redirect stderr to stdout.
#######################
# Check subtitle codec
#######################
# Select first subtitle stream
$subtitle_probe = ffprobe.exe -v error -show_format -show_streams -select_streams s:0 $filename 2>&1
# Test ffprobe error
if ($LastExitCode -ne 0) {
Write-Output "ffprobe error: $audio_probe. Error code: $LastExitCode"
Exit
}
# Codec name
$subtile_name = ($subtitle_probe[2] -split "=")[1]
$supported_subtitle = "ttml", "webvtt", "srt", "wav", "vorbis"
# TODO Check more sub codec
if ($subtile_name -ne "srt") {
$remux = $true
# The subtitle codec might be not work for chromecast
Write-Output $subtitle_probe[2]
# Get subtitle stream index
$index = ($subtitle_probe[1] -split "=")[1]
# Extract subtitle
ffmpeg -y -i $filename -map 0:$index original.$subtile_name
# If ass, ssa format and burn flag write to video stream
if ($burn_sub) {
$ffmpeg_input = "-i original.$subtile_name" # Set subtitle input
$post_input = "-vf subtitles=original.$subtile_name" # Flag to burn on video
}
else {
# Remove karaoke
if ($remove_karaoke) {
# TODO Search/Replace
# Improve subs rewriting
$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'
$replace = @()
$remove_style = "karaoke"
foreach ($line in Get-Content .\original.$subtile_name) {
if (-Not ($line | Select-String -pattern $remove_style)) {
$replace += $line
}
}
$replace | Out-File -Append ./out.ass -Encoding utf8
# Convert to new format
ffmpeg -y -i out.ass -scodec srt cache.srt
}
else {
# Convert to new format
ffmpeg -y -i original.$subtile_name -scodec srt cache.srt
}
$ffmpeg_input = "-i cache.srt"
$post_input = "-scodec srt"
}
}
else {
# Placeholder for encode audio or video (maybe not needed)
$post_input = "-scodec copy"
}
#######################
# Check audio codec
#######################
# Select first audio stream
$audio_probe = ffprobe.exe -v error -show_format -show_streams -select_streams a:0 $filename 2>&1
# Test ffprobe error
if ($LastExitCode -ne 0) {
Write-Output "ffprobe error: $audio_probe. Error code: $LastExitCode"
Exit
}
# Codec name
$codec_name = ($audio_probe[2] -split "=")[1]
# Chromecast supported codec
# https://support.google.com/chromecast/answer/6279377?hl=en
$supported_codec = "flac", "aac", "mp3", "wav", "vorbis"
# Wrong codec for chromecast
if (-Not $supported_codec.Contains($codec_name)) {
$remux = $true
$post_input = "$post_input -acodec mp3" # Set to mp3 by default
}
else {
# In case of flac
if ($codec_name -eq "flac") {
$sample_rate = [int]($audio_probe[9] -split "=")[1]
$bits_per_raw_sample = [int]($audio_probe[23] -split "=")[1]
# Set max sample rate
if (($sample_rate -ne -1) -and ($sample_rate -gt 96000)) {
$post_input = "$post_input -sample_rate 96000"
}
# Set bits per raw sample
if ($bits_per_raw_sample -gt 24) {
$post_input = "$post_input -bits_per_raw_sample 24"
}
}
}
#######################
# Check video codec
#######################
$video_probe = ffprobe.exe -v error -show_format -show_streams -select_streams v:0 $filename 2>&1
# Test ffprobe error
if ($LastExitCode -ne 0) {
Write-Output "ffprobe error: $video_probe. Error code: $LastExitCode"
Exit
}
# Codec name
$codec_name = ($video_probe[2] -split "=")[1]
# Chrome cast support only h264 or vp8
# vp8 is ignored in this case
# https://developers.google.com/cast/docs/media
if ($codec_name -ne "h264") {
$remux = $true
$post_input = $post_input + $(if ($hwaccel_available) { " -vcodec h264_nvenc" } else { " -vcodec h264" })
$post_input = "$post_input -pix_fmt yuv420p"
}
else {
# TODO Check if it's needed
$bad_fmt = ($video_probe[17] -split "=")[1] -ne "yuv420p"
$codec = if ($hwaccel_available) { " -vcodec h264_nvenc" } else { " -vcodec h264" }
# Encode must needed for burn sub or bad fmt
$post_input = $post_input + $(if ($burn_sub) { $codec } else { " -vcodec copy" })
# Check pix fmt
if ($bad_fmt) {
$post_input = "$post_input -pix_fmt yuv420p"
}
}
#######################
# Final cmd
#######################
if ($remux) {
$file = [System.IO.Path]::GetFileNameWithoutExtension($filename)
$ext = [System.IO.Path]::GetExtension($filename)
# Add flag for harware acceleration
if ($hwaccel_available) {
$pre_input = "-init_hw_device cuda=cuda:0 -hwaccel cuda -filter_hw_device cuda"
}
$ffmpeg_input = "$ffmpeg_input -i $filename" # Add video file to input
$directory = [System.IO.Path]::GetDirectoryName($filename)
$output_file = "$directory/$($file)_converted$($ext)" # Concat filename converted and extension
$cmd_str = "ffmpeg $pre_input $ffmpeg_input $post_input $output_file"
Invoke-Expression $cmd_str
if ($LastExitCode -ne 0) {
Write-Output "ffmpeg error: $out. Error code: $LastExitCode"
Write-Output "Maybe ffmpeg nvenc is enable but you doesn't have a nvidia gpu"
Exit
}
}
Remove-Item -Path ./out.ass
Remove-Item -Path ./cache.srt
Remove-Item -Path "original.$subtile_name"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment