MediaDuration.psm1


Function Get-MediaDuration {
  [CmdletBinding()]      
  param(
    [string[]] $Path,
    [string] $Filter,
    [switch] $Recurse,
    [switch] $Total,
    [switch] $Progress,
    [Parameter(ValueFromPipeline=$true)] [Object[]] $items
    ) 
  begin {
    $ffprobe = Get-Command -CommandType Application -Name 'ffprobe.exe' -ErrorAction SilentlyContinue
    if (-not $ffprobe) {
      Write-Warning "ffprobe.exe not found in PATH, grab it there: https://ffbinaries.com/downloads"
      return
    }
    $ffprobe = $ffprobe.Source
    $args = @{Recurse=$Recurse; Filter=$Filter; Path=$Path}
    if (-not $items) {
      $items = Get-ChildItem @args 
    }
    if ($Total) {
      $totalTime = [TimeSpan]::FromSeconds(0)
      $Progress = $true
      $sigma = [char] 0x03A3
    }
    if ($Progress) {
      $totalCount = $items.Count
      $i = 0
    }
  }
  process {
    $items `
    | ForEach-Object {
      & $ffprobe -v quiet -print_format json -show_format -show_streams -i $_.FullName 2>&1 `
      | ConvertFrom-Json 
    } `
    | Select-Object `
      @{Name='FullName';Expression={ $_.format.filename }},
      @{Name='BitRate';Expression={ $_.format.bit_rate }},
      @{Name='Duration';Expression={ [TimeSpan]::FromSeconds($_.format.duration -replace ('(\.\d{0,3})\d+','$1')) } } `
    | Where-Object { $_.Duration -gt [TimeSpan]::Zero } `
    | ForEach-Object {
      if ($Progress) {
        ++$i
        $percent = ($i / $totalCount * 100)
        $percentPretty = [math]::Round($percent, 1)
        Write-Progress -Activity "Calculating duration: $sigma $totalTime" -Status "Progress: ${i} of ${totalCount} $percentPretty%" -PercentComplete $percent
        if ($i -eq $totalCount) {
          $totalCount += 1  # when pipelining, process{} block is invoked per directory; counting won't work properly for recursive directories
        }
      }
      if ($Total) {
        $totalTime = $totalTime + $_.Duration 
        $_ = $null  # prevent writing item to terminal when aggregating
      }
      $_  # return item to pipeline
    }
  }
  end { 
    if ($Total) {
      $totalTime.ToString()
    }
  }

<#
.SYNOPSIS
 
Query duration time and other properties from video or audio files using ffprobe command.
 
.DESCRIPTION
 
You can generate the item list with Get-ChildItem command and pipe it into
Get-MediaDuration cmdlet. The resulting output is a list of objects and
can be pipelined as well into Sort-Object, Where-Object, Measure-Object, etc.
Mind that Measure-Object requires an integer as an input property, so you'll
have to convert the Duration field of type TimeSpan, to milliseconds
(see examples section).
 
.PARAMETER Progress
Show progress bar while calculating. Defaults to $true when the Total
parameter is $true.
 
.INPUTS
 
None or list of items from Get-ChildItem.
 
.OUTPUTS
 
Object[]. Returns the list of objects, use the Duration property to get the
time of media. When using -Total parameter, outputs System.String for convinience.
 
.EXAMPLE
 
Get-MediaDuration -Recurse -Total
 
.EXAMPLE
 
Get-MediaDuration -Recurse -Total -Path "Podcasts"
 
.EXAMPLE
 
Get-MediaDuration -Recurse -Total '.\Audiobook A','.\Audiobook B' '*.m4a'
 
.EXAMPLE
 
Get-MediaDuration -Recurse "Movies" '*.*'
 
.EXAMPLE
 
Get-MediaDuration -Recurse "Movies" '*.mp4' -Total
 
.EXAMPLE
 
Get-MediaDuration -Recurse "Movies" '*.mp4' | ForEach-Object { $_.Duration = $_.Duration.TotalMilliseconds ; $_ }
 
.EXAMPLE
 
Get-MediaDuration -Recurse `
  | ForEach-Object { $_.Duration = $_.Duration.TotalMilliseconds ; $_ } `
  | Measure-Object -Sum Duration `
  | % { [TimeSpan]::FromMilliseconds($_.Sum).ToString() }
 
.EXAMPLE
 
Get-ChildItem -Recurse | Get-MediaDuration
 
.EXAMPLE
 
Get-ChildItem -Recurse | Get-MediaDuration -Total
 
.EXAMPLE
 
Get-ChildItem -Recurse "Movies" | Get-MediaDuration | Sort Duration -Descending
 
.LINK
 
https://superuser.com/questions/650291/how-to-get-video-duration-in-seconds
 
#>

}