clippy.ps1

<#PSScriptInfo
 
.VERSION 1.9
 
.GUID dce65b3a-d917-425b-9090-d82b368e12fa
 
.AUTHOR Read Stanton (Decimation)
 
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS Downloader yt-dlp ffmpeg clip youtube media
 
.LICENSEURI
 
.PROJECTURI https://github.com/Decimation/PowerShell-Library
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES PSKantan,RoughDraft
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
.PRIVATEDATA
 
#>


<#
.DESCRIPTION
Downloads clips from sites supported by yt-dlp
.PARAMETER Url
Url
.PARAMETER Start
Start time
.PARAMETER End
End time
.PARAMETER Args1
Extra yt-dlp args
.PARAMETER Args2
Extra ffmpeg args
.LINK
https://github.com/Decimation/PowerShell-Library
.EXAMPLE
& "clippy.ps1" -Url "https://www.youtube.com/watch?v=SNgNBsCI4EA" -Start '1:10' -End "1:26"
.EXAMPLE
& "clippy.ps1" -Url "https://youtu.be/YPqYvll6XD0" -Start '10:52' -End "11:38" -Args2 @('-preset','veryfast')
.EXAMPLE
& "clippy.ps1" -u "https://youtu.be/YPqYvll6XD0" -s '1:00' -e "2:00" -Args2 @('-preset','ultrafast')
.EXAMPLE
& "clippy.ps1" -Url "https://www.youtube.com/watch?v=lGJBUauU-CE" -Start 1:00 -End 3:00 -Args2 @('-c','copy')
 
#>

param (
    [Parameter(Mandatory)]
    [alias('u')]
    $Url, 
    
    [Parameter(Mandatory)]
    [Alias('s')]
    $Start = '0:0:0', 
    
    [Parameter(Mandatory = $false, ParameterSetName = 'TimeAbsolute')]
    [alias('e')]
    $End,

    [Parameter(Mandatory = $false, ParameterSetName = 'TimeDuration')]
    [alias('d')]
    $Duration,

    [Parameter(Mandatory = $false)]
    $Output = $null,

    # yt-dlp args
    [Parameter(Mandatory = $false)]
    $Args1,

    # ffmpeg args
    [Parameter(Mandatory = $false)]
    $Args2 = @('-y', '-preset', 'veryfast'),

    [Alias('cf')]
    [switch]$Confirm,

    [switch]
    $IgnoreConfig
)

#$ErrorActionPreference = 'Abort'
# region Functions

function private:Get-SanitizedFilename {
    param (
        $origFileName,
        $repl = ''
    )

    $invalids = [System.IO.Path]::GetInvalidFileNameChars()
    $newName = [String]::Join($repl, $origFileName.Split($invalids, 
            [System.StringSplitOptions]::RemoveEmptyEntries)).TrimEnd('.')
    
    return $newName
}

function script:Get-ParsedTime {
    [Outputtype([timespan])]
    param ([string]$t)

    $st = $t.Split(':')

    switch ($st.Length) {
        1 {
            $t = [TimeSpan]::FromSeconds($t)
            return $t;
        }
        2 {
            $t = "0:" + $t
        }
        
    }
    
    try {
        $t = [timespan]::ParseExact($t, "g", [cultureinfo]::CurrentCulture)    
    }
    catch {
        $t = ([timespan]::Parse($t))
    }

    return [timespan] $t
}


function script:Read-Confirmation {
    
    if ($Confirm) {
        switch (Read-Host "Continue? [y/n] (or press enter)") {
            default {
                
            }
            'y' {
                
            }
            'n' {
                exit
            }
        }
    }

}
function script:Find-MediaCommand {
    
    param($c)
    $c2 = (Get-Command $c -ErrorAction SilentlyContinue -CommandType All)
    if (-not $c2) {
        throw "$c not found"
        return
    }
    Write-Host "$c : $($c2.Path)" -ForegroundColor 'DarkGray'
    return $c2
}

# endregion

Write-Debug "$Url $Start $End $Duration"

$e_ffmpeg = 'ffmpeg'
$e_ytdlp = 'yt-dlp'

$c_ytdlp = script:Find-MediaCommand $e_ytdlp
$c_ffmpeg = script:Find-MediaCommand $e_ffmpeg

if (-not $End -and -not $Duration) {
    $targs = @('--print', 'duration_string', $Url)
    $End = & $c_ytdlp @targs
    Write-Host "Automatically retrieved end time: $End"
}

$Start = [timespan] (Get-ParsedTime($Start))
if ($End) {
    $End = [timespan] (Get-ParsedTime($End))
}
elseif ($Duration -and -not $End) {
    $End = $Start + ([timespan] (Get-ParsedTime($Duration)))
    
}
Write-Host "Start: $Start" -ForegroundColor 'Cyan'
Write-Host "End: $End" -ForegroundColor 'Cyan'


# $tf = "hh\.mm\.ss"
$tf = "hh\hmm\mss\s"

$fs = $Start.ToString($tf)
$fe = $End.ToString($tf)

# $arg2 = @($Url, '--print', 'id')
# $arg2 = @($Url, '--print', "%(title)s.%(ext)s") + $Args1

<# $il = [System.IO.Path]::GetInvalidFileNameChars()
$il | ForEach-Object { $s2 = $s -replace ([regex]::Escape($_)), '' } #>


# $n1 = "$(& $c_ytdlp @arg2) ($fs - $fe)"
# $n1 = "$(& $c_ytdlp @arg2)"

$title = &$c_ytdlp $(@($Url, '--print', '%(title)s') + $Args1)
$ext = &$c_ytdlp $(@($Url, '--print', '%(ext)s') + $Args1)

if ($Output) {
    if ([System.IO.Path]::HasExtension($Output)) {
        # $Output = [System.IO.Path]::GetFileNameWithoutExtension($Output)
        $ext=[System.IO.Path]::GetExtension($Output)
    }else{
        $Output="$Output.$ext"
    }
}
else{
    $Output="$title ($fs - $fe).$ext"
}

Write-Debug "$title $ext $Output"

# $Output ??= $n1
#$Output = Get-SanitizedFilename $Output

Write-Host "Output filename: $Output" -ForegroundColor 'Green'

if (Test-Path $Output) {
    $yn = Read-Host -Prompt "$Output already exists. Remove? [y/n/a]"
    switch ($yn) {
        'y' {
            Remove-Item $Output
        }
        'n' {
            
        }
        'a' {
            return
        }
    }
}


script:Read-Confirmation

# yt-dlp args

if ($End) {
    $duration1 = $End - $Start
}
elseif ($Duration) {
    $duration1 = $Start + $Duration
}

Write-Host "Duration: ($duration1)" -ForegroundColor 'DarkGray'

$ts = "*$Start-$End"
$x2Args += $Args1 + @($Url, `
        '--force-keyframes-at-cuts', `
        '--download-sections', $ts, `
        '--postprocessor-args', "ffmpeg:$Args2") `
    + @('-o', "$Output")

if ($IgnoreConfig) {
    $x2Args += @('--ignore-config')
}

Write-Host "final args: $($x2Args -join ' ')" -ForegroundColor 'Cyan'

script:Read-Confirmation

$o_ytdlp = & $c_ytdlp @x2Args

Write-Host "$o_ytdlp"
Write-Host "Output: $Output" -ForegroundColor 'Green'

<# $p = Start-Process -FilePath 'ffmpeg.exe' -RedirectStandardOutput:$true `
    -ArgumentList $ffArgs -NoNewWindow -PassThru
 
return $p #>