Powershell で Media Player を作る

プログラミング

windows付属のMedia playerが何だか重たく、使いづらいと思う今日この頃です。そこで、Powershell で手軽なmedia playerを作ってみようと試行錯誤の結果、先ずは、console で、続いて windows.Formを使って、更にWPF を取り入れて 数種のMedia Playerを作ってみました。ここには先ずコンソール・ベースで作った Media Playerをご紹介します。

PowerShellでMP3を鳴らすには

先ず最初は、powershell でどうやって MP3 file等を再生するのかということですが。それには以下のコードでMediaplayer をpowershellに組み込みます。

add-type -assemblyname presentationcore
$mediaplayer = new-object system.windows.media.mediaplayer

次はmp3 fileを$mediaplayer.open()でopenします。続いて $mediaplayer.play() で再生を始めます。

$mediaplayer.open("I:\MultiMedia\CLIMAX Best 歌謡曲  Disk 2\キャンディーズ - 年下の男の子.mp3")
$mediaplayer.play()

ここまでのコードをpowershell_ise のスクリプト画面に貼り付けて実行すると、難なくMP3が再生されます。簡単ですね。

FileDialogで曲を選択

mp3 fileの再生方法は、わかりました。次はOpenするファイルをどうやって選ぶかです。曲Fileをコンソール窓にリストして、そこから選ぶ事にします。先ずは、FileDialog を利用してfileを選びます。

cd i:\multimedia    
Add-Type -assemblyName System.Windows.Forms
$FileDialog = New-Object System.Windows.Forms.OpenFileDialog
$FileDialog.Filter = "音楽ファイル|*.mp3;*.m4a|All Files|*.*"

$FileDialog.InitialDirectory = "I:\Multimedia"

$FileDialog.Multiselect = $true

$FileDialog.Title = "演奏するファイルを選択してください"

if ($FileDialog.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) {
   Write-Output "処理をキャンセルしました"
   $FileDialog.Dispose()
            
}
 # ファイル名のセット(複数ファイル指定にした場合は FileNames)
 $TergetFileFullPath = $FileDialog.FileNames
 # オブジェクトの後始末
 $FileDialog.Dispose()
 $TergetFileFullPath | out-file playlist.txt 

上記のコードでは、先ず MP3 file等を全て格納している “I:¥multimedia”へ current directory を移動して、FileDialog を表示しています。FileDialog で選んだFileを playlist.txt に出力しています。

実行すると次のようなFileDialogが出ます。

ここで再生したいMP3 Fileを選択して開くと、playlist.txt は次の様になっています。

PS >cat playlist.txt
I:\MultiMedia\Beatles\Abbey Road\01 COME TOGETHER.mp3
I:\MultiMedia\Beatles\Abbey Road\02 SOMETHING.mp3
I:\MultiMedia\Beatles\Abbey Road\03 MAXWELL'S SILVER HAMMER.mp3
I:\MultiMedia\Beatles\Abbey Road\04 Oh! Darling.mp3
I:\MultiMedia\Beatles\Abbey Road\05 OCTOPUS'S GARDEN.mp3
I:\MultiMedia\Beatles\Abbey Road\06 I Want You.mp3
I:\MultiMedia\Beatles\Abbey Road\07 HERE COMES THE SUN.mp3
I:\MultiMedia\Beatles\Abbey Road\08 Because.mp3
I:\MultiMedia\Beatles\Abbey Road\09 You Never Give Your Money.mp3
I:\MultiMedia\Beatles\Abbey Road\10 Sun King.mp3
I:\MultiMedia\Beatles\Abbey Road\11 Mean Mr Mustard.mp3
I:\MultiMedia\Beatles\Abbey Road\12 Polythene Pam.mp3
I:\MultiMedia\Beatles\Abbey Road\12 Yellow Submarine In Pepperland.mp3
I:\MultiMedia\Beatles\Abbey Road\13 She Came In Through The Window.mp3
I:\MultiMedia\Beatles\Abbey Road\13 There's A Place.mp3
I:\MultiMedia\Beatles\Abbey Road\14 Gold Slumbers.mp3
I:\MultiMedia\Beatles\Abbey Road\15 Carry That Weight.mp3
I:\MultiMedia\Beatles\Abbey Road\16 The End.mp3
I:\MultiMedia\Beatles\Abbey Road\17 Her Majesty.mp3

Playlist.txt をコンソールに出力

次にplaylist.txtの内容を配列に読み込んで、それをコンソールに表示します。

$Yy = 7
 $II = 0
 $Lines = cat playlist.txt |  ? { $_ -ne '' }                        #すべてを $Lines 配列に格納
 $E = $Lines.Count
 cls
 [Console]::SetCursorPosition(  0  ,  $Yy  )
 foreach ( $Index in $II..$E) { 
   write-host $Lines[$Index] 
 }

ここまで実行すると以下の画面が現れます。








I:\MultiMedia\Beatles\Abbey Road\01 COME TOGETHER.mp3
I:\MultiMedia\Beatles\Abbey Road\02 SOMETHING.mp3
I:\MultiMedia\Beatles\Abbey Road\03 MAXWELL'S SILVER HAMMER.mp3
I:\MultiMedia\Beatles\Abbey Road\04 Oh! Darling.mp3
I:\MultiMedia\Beatles\Abbey Road\05 OCTOPUS'S GARDEN.mp3
I:\MultiMedia\Beatles\Abbey Road\06 I Want You.mp3
I:\MultiMedia\Beatles\Abbey Road\07 HERE COMES THE SUN.mp3
I:\MultiMedia\Beatles\Abbey Road\08 Because.mp3
I:\MultiMedia\Beatles\Abbey Road\09 You Never Give Your Money.mp3
I:\MultiMedia\Beatles\Abbey Road\10 Sun King.mp3
I:\MultiMedia\Beatles\Abbey Road\11 Mean Mr Mustard.mp3
I:\MultiMedia\Beatles\Abbey Road\12 Polythene Pam.mp3
I:\MultiMedia\Beatles\Abbey Road\12 Yellow Submarine In Pepperland.mp3
I:\MultiMedia\Beatles\Abbey Road\13 She Came In Through The Window.mp3
I:\MultiMedia\Beatles\Abbey Road\13 There's A Place.mp3
I:\MultiMedia\Beatles\Abbey Road\14 Gold Slumbers.mp3
I:\MultiMedia\Beatles\Abbey Road\15 Carry That Weight.mp3
I:\MultiMedia\Beatles\Abbey Road\16 The End.mp3
I:\MultiMedia\Beatles\Abbey Road\17 Her Majesty.mp3

PS >

Playlist.txt の曲を再生する

読み込んだPlaylist.txtの内容は、$Lines 配列変数に入っていますから、これを順番に再生します。

以下のコードで $i を変えて順番に再生させればいいのです。

$i = 0
$mediaplayer.open($lines[$i]) 
$mediaplayer.play()

このままでは、最初の曲の再生が始まってプログラムは終了してしまいます。

曲の長さを取得し、その時間ループして待つ

次の曲を再生させるためには、現在再生中の曲が終わるのをループして待つ必要があります。再生する曲の長さは、$mediaplayer.NaturalDuration.TimeSpan で知ることができます。

PS >add-type -assemblyname presentationcore
>> $mediaplayer = new-object system.windows.media.mediaplayer
PS >$mediaplayer.open("I:\MultiMedia\Beatles\Abbey Road\01 COME TOGETHER.mp3")
PS >$mediaplayer.NaturalDuration.TimeSpan


Days              : 0
Hours             : 0
Minutes           : 4
Seconds           : 20
Milliseconds      : 623
Ticks             : 2606236250
TotalDays         : 0.0030164771412037
TotalHours        : 0.0723954513888889
TotalMinutes      : 4.34372708333333
TotalSeconds      : 260.623625
TotalMilliseconds : 260623.625

この曲の場合は、約260秒待てばいい訳です。これを実現するために、曲の再生開始と同時に stopwatchを開始します。

PS >$clock = [diagnostics.stopwatch]::StartNew()
PS >$clock.Elapsed


Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 15
Milliseconds      : 375
Ticks             : 153757580
TotalDays         : 0.000177960162037037
TotalHours        : 0.00427104388888889
TotalMinutes      : 0.256262633333333
TotalSeconds      : 15.375758
TotalMilliseconds : 15375.758

$clock.Elapsed でstopwatch開始からの経過時間が分かります。

for ( $i = 0 ; $i -lt $Lines.count ; $i++ ) {
    $mediaplayer.open($lines[$i])
    $timer = $mediaplayer.NaturalDuration.TimeSpan
    $clock = [diagnostics.stopwatch]::StartNew()
    $mediaplayer.play()
  write-host "Playing " $lines[$i]    
    while ($clock.elapsed -lt $timer){
         [Console]::SetCursorPosition(  0  ,  $Lines.count  )
          Write-Host "Playing "  $clock.Elapsed.TotalSeconds -noNewLine
    }
}

while ($clock.elapsed -lt $timer) でループさせて、待ちます。上記で実行すると・・・








I:\MultiMedia\Beatles\Abbey Road\01 COME TOGETHER.mp3
I:\MultiMedia\Beatles\Abbey Road\02 SOMETHING.mp3
I:\MultiMedia\Beatles\Abbey Road\03 MAXWELL'S SILVER HAMMER.mp3
I:\MultiMedia\Beatles\Abbey Road\04 Oh! Darling.mp3
I:\MultiMedia\Beatles\Abbey Road\05 OCTOPUS'S GARDEN.mp3
I:\MultiMedia\Beatles\Abbey Road\06 I Want You.mp3
I:\MultiMedia\Beatles\Abbey Road\07 HERE COMES THE SUN.mp3
I:\MultiMedia\Beatles\Abbey Road\08 Because.mp3
I:\MultiMedia\Beatles\Abbey Road\09 You Never Give Your Money.mp3
I:\MultiMedia\Beatles\Abbey Road\10 Sun King.mp3
I:\MultiMedia\Beatles\Abbey Road\11 Mean Mr Mustard.mp3
I:\MultiMedia\Beatles\Abbey Road\12 Polythene Pam.mp3
I:\MultiMedia\Beatles\Abbey Road\12 Yellow Submarine In Pepperland.mp3
I:\MultiMedia\Beatles\Abbey Road\13 She Came In Through The Window.mp3
I:\MultiMedia\Beatles\Abbey Road\13 There's A Place.mp3
I:\MultiMedia\Beatles\Abbey Road\14 Gold Slumbers.mp3
I:\MultiMedia\Beatles\Abbey Road\15 Carry That Weight.mp3
I:\MultiMedia\Beatles\Abbey Road\16 The End.mp3
I:\MultiMedia\Beatles\Abbey Road\17 Her Majesty.mp3

Playing  I:\MultiMedia\Beatles\Abbey Road\01 COME TOGETHER.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\02 SOMETHING.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\03 MAXWELL'S SILVER HAMMER.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\04 Oh! Darling.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\05 OCTOPUS'S GARDEN.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\06 I Want You.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\07 HERE COMES THE SUN.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\08 Because.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\09 You Never Give Your Money.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\10 Sun King.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\11 Mean Mr Mustard.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\12 Polythene Pam.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\12 Yellow Submarine In Pepperland.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\13 She Came In Through The Window.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\13 There's A Place.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\14 Gold Slumbers.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\15 Carry That Weight.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\16 The End.mp3
Playing  I:\MultiMedia\Beatles\Abbey Road\17 Her Majesty.mp3
PS >

あれ~、あっという間に、最後の曲まで行って最後の曲だけが再生され終わってしまいました。$timer が正常にセットされていないようです。$mediaplayer.open の後、すぐに $timer = $mediaplayer.NaturalDuration.TimeSpan をやるとダメなようなので、start-sleep -s 1 でopenのあと1秒待つことにします。

for ( $i = 0 ; $i -lt $Lines.count ; $i++ ) {
    $Kyoku = $lines[$i]
    $mediaplayer.open($Kyoku)
    start-sleep -s 1
    $timer = $mediaplayer.NaturalDuration.TimeSpan
    $clock = [diagnostics.stopwatch]::StartNew()
    $mediaplayer.play()
    [Console]::SetCursorPosition(  0  ,  $Lines.count + $Yy )
    write-host "Playing " $lines[$i] 
    while ($clock.elapsed -lt $timer){
          [Console]::SetCursorPosition(  0  ,  $Lines.count + $Yy + 1 )
          Write-Host "Playing "  $clock.Elapsed.TotalSeconds -noNewLine
    }
}

最終的に上記コードで実行した結果です。

次の曲への切り替わり

キーのインプットを検知する

順番に再生させるようにはできましたが、次の曲へのスキップとか、再生を止めさせることが出来ません。そこでキーボードのキー操作を検知させるようにします。例えば Ctl + alt + “N” を押せば次の曲へスキップするとか、Ctl+alt + “E” で Exit するようにします。

$keyN  = [Byte][Char]'N' ## Letter
$keyE = [Byte][Char]'E' ## Letter
$keyCtrl = '0x11' ## Ctrl
$keyAlt = '0x12' ## Alt

$Signature = @'
    [DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)] 
    public static extern short GetAsyncKeyState(int virtualKeyCode); 
'@
Add-Type -MemberDefinition $Signature -Name Keyboard -Namespace PsOneApi

上記コードを冒頭に記述しておきます。検知したいキーを定義し、コードを組み込んでいます。

Ctrl +Alt +N で次の曲へ

While ($clock.elapsed -lt $timer){ のループの中に以下のコードを記述します。Ctrl + Alt + N キーが押されると、 mediaplayer を stop して、 $Timer を 0 にすることにより While ループを抜けさせます。

         If( [bool]([PsOneApi.Keyboard]::GetAsyncKeyState($keyN) -eq -32767 -and                               # N キー 次の曲へスキップ 
              [PsOneApi.Keyboard]::GetAsyncKeyState($keyCtrl) -eq -32767 -and 
              [PsOneApi.Keyboard]::GetAsyncKeyState($keyAlt) -eq -32767)) 
              {      
                 $Mediaplayer.stop()
                 $Timer = 0
              }

Ctrl + Alt + E で EXIT

同様に While ($clock.elapsed -lt $timer){ のループの中に以下のコードを記述します。Ctrl + Alt + E キーが押されると、 mediaplayer を stop して、 スクリプトを Exit します。

 If( [bool]([PsOneApi.Keyboard]::GetAsyncKeyState($keyE) -eq -32767 -and                    # Exit
               [PsOneApi.Keyboard]::GetAsyncKeyState($keyCtrl) -eq -32767 -and 
               [PsOneApi.Keyboard]::GetAsyncKeyState($keyAlt) -eq -32767)) 
                    
                    { 
                            $mediaplayer.stop()
                            Exit
                    }

演奏中の曲をハイライト

演奏中の曲を表示しているリストの中でハイライト表示して見栄えを良くします。

$mediaplayer.play() の前にハイライトします。 

[Console]::SetCursorPosition(  0  ,  $Yy + $i  )
write-host $lines[$i] -backgroundColor white -foreGroundColor black -noNewLine

While ループを抜けた後に演奏が終わったので、現在 ハイライトしている行を普通の表示にします。

[Console]::SetCursorPosition(  0  ,  $Yy + $i  )
write-host $lines[$i]  -noNewLine

スクリプトの中に記述すると、次のような感じです。

    [Console]::SetCursorPosition(  0  ,  $Yy + $i  )
    write-host $lines[$i] -backgroundColor white -foreGroundColor black -noNewLine
    $mediaplayer.play()
    while ($clock.elapsed -lt $timer){
          start-sleep -milli 100
          [Console]::SetCursorPosition(  0  ,  $Lines.count + $Yy + 1 )
    :::::::::::::::::::
       }
    [Console]::SetCursorPosition(  0  ,  $Yy + $i  )
    write-host $lines[$i]  -noNewLine

実行するとこんな様になります。

曲の進捗をProgress Bar で表示

次は曲の進捗状況をProgress Bar で表示します。while ループの中に次のコードを記述します。

  $P = ($clock.Elapsed.TotalSeconds / $timer.TotalSeconds) * 100
  $PrMessage = "Playing " + $lines[$i] 
  Write-Progress -id 0 -Activity $PrMessage   -PercentComplete $P 

$timer.TotalSeconds は、現在プレイ中の曲の長さです。$clock.Elapsed.TotalSeconds はプレイ開始してからの経過時間です。何れも秒数ですが、経過秒/曲の長さ * 100 で何パーセント経過したかを求めます。

実行すると、こんな感じになります。

曲選びの操作感を上げる

ここまででは、順番に再生、次の曲へスキップは出来る様になりましたが、もう少し曲選びの操作感を上げたいと思います。カーソルのアップ・ダウンでリストの中を移動してエンター・キーでその曲を演奏開始するようにします。

カーソル アップ・ダウン

キーの定義に $KeyUp , $KeyDown を追加します。

$keyUp = '0x26' 
$KeyDown = '0x28'

先ずカーソル・ダウンを押した時にカーソルが下に移動しその行を黄色で表示する様にします。

If( [bool]([PsOneApi.Keyboard]::GetAsyncKeyState($keyDown) -eq -32767 ))          #  カーソルキー [↓]
     
         { if ( $Cursor -eq $null ) { $Cursor = $Yy + $i }                        # 変数 $Cursor が初期値なら演奏している行にセット
           if ($Cursor - 7 -lt $Lines.count - 1)                        
           {  $Cursor++                                   # 一段下がった
              [Console]::SetCursorPosition(0,$Cursor)                            # 行へカーソル移動
              write-host $Lines[$Cursor - 7] -noNewLine -ForeGroundColor Yellow  # 黄色で表示               
           }                                                                      # 最下位置なら no action
        }   

実行してみました。

下に移動して黄色で表示したのはいいのですが、移動前に元の行を元の色に戻すのを忘れています。

次のコードを $Cursor をインクリメントする前に追記します。

[Console]::SetCursorPosition(0,$Cursor)                            # 移動前に行の先頭へカーソル移動
if ( $Cursor -eq $Yy + $i ) { 
   write-host $lines[$i] -backgroundColor white -foreGroundColor black -noNewLine
   }else{
   write-host $Lines[$Cursor - 7] -noNewLine
   } 

追記のコードでは、移動前の行が再生されている場合は、 backcolor white で、そうでない場合は、nomal な表示に戻しています。

実行してみます。

カーソル・アップのコードを記述します。

If( [bool]([PsOneApi.Keyboard]::GetAsyncKeyState($keyUp) -eq -32767 ))            #  カーソルキー [↑]
     
         { if ( $Cursor -eq $null ) { $Cursor = $Yy + $i }                        # 変数 $Cursor が初期値なら演奏している行にセット
           if ($Cursor - 7 -gt 0)                        
           {  [Console]::SetCursorPosition(0,$Cursor)                            # 移動前に行の先頭へカーソル移動
              if ( $Cursor -eq $Yy + $i ) { 
                 write-host $lines[$i] -backgroundColor white -foreGroundColor black -noNewLine
                 }else{
                 write-host $Lines[$Cursor - 7] -noNewLine
                 } 
              $Cursor--                                   # 一段あがった
              [Console]::SetCursorPosition(0,$Cursor)                            # 行へカーソル移動
              write-host $Lines[$Cursor - 7] -noNewLine -ForeGroundColor Yellow  # 黄色で表示               
           }                                                                      # 最上位置なら no action
        }   

ここまでで実行してみます。

エンターキーでカーソル位置の曲を再生

カーソルのアップ・ダウンでリストの行間を移動出来るようになりました。次はそこでエンター・キーを叩くと、その曲が再生されるようにします。

キーの定義に エンター・キーを追加します。

$KeyEnter = '0x0d'

While ループの中に次のコードを記述します。

If( [bool]([PsOneApi.Keyboard]::GetAsyncKeyState($keyEnter) -eq -32767 ))           # Enter キー
    { 
     if ( $Cursor -ne $null ) 
       {
         $Timer = 0 
         $CursorEnt = $Cursor
         [Console]::SetCursorPosition(0,$Cursor) 
         write-host $lines[$Cursor - 7] -backgroundColor white -foreGroundColor black -noNewLine
       }
      
    } 

エンター・キーが押されると $Timer を 0 にすることにより while ループをぬけるようにします。続いて $CursorEnt = $Cursor を$Null値以外にセットしておきます。続いて現カーソル行をハイライト表示します。

While ループを抜けたところに次のコードを記述します。

if ( $CursorEnt -ne $null ) { $i = $Cursor - 8 ; $CursorEnt = $null }

$CursorEnt が $null 以外だと、while ループ内でエンターが押されたことを意味します。$i は曲が入っている配列のIndexですが、$Cursorの値と 7 のずれがあります。 for ループで +1 されることを見越して、$i を $Cursor – 8 の値にします。

では、ここまでで実行した結果です。

Get-ChildItem で曲リストを取得する

曲の選択にFileDialogを使っていましたが、若干手間がかかります。そこで引数を検索文字として渡して、Get-childItem *引数* のような形で曲リストを作ることにします。

PS >Get-ChildItem *井上陽水* -r | % { $_ -Replace('','') } >testfile.txt
PS >cat testfile.txt
I:\multimedia\井上陽水\井上陽水 - 5月の別れ.mp3
I:\multimedia\井上陽水\井上陽水 - Just Fit.mp3
I:\multimedia\井上陽水\井上陽水 - Make-Up Shadow.mp3
I:\multimedia\井上陽水\井上陽水 - TEENAGER.mp3
I:\multimedia\井上陽水\井上陽水 - Tokyo.mp3
I:\multimedia\井上陽水\井上陽水 - ありがとう.mp3
I:\multimedia\井上陽水\井上陽水 - いっそセレナーデ.mp3
I:\multimedia\井上陽水\井上陽水 - いつのまにか少女は.mp3
I:\multimedia\井上陽水\井上陽水 - とまどうペリカン.mp3
I:\multimedia\井上陽水\井上陽水 - なぜか上海.mp3

FileDialogをやめて、代わりに次のコードを記述します。

$Sw = $Args[0]
$Sw = "*" + $Sw + "*"
ls $Sw -r | % { $_ -Replace('','') } | sls .mp3,.m4a > playlist.txt
if ( (Get-Item Playlist.txt).Length -eq 0 ) { Write-host "No Result" ; Exit } 

最後の if 文は、結果が何もなかった場合に Exitするためのものです。以下のように 無題3.ps1 に argument として検索文字 “松任谷由実” を渡します。

PS >./無題3 松任谷由実

結果 リストする曲が多いと以下のように よろしくありません。

Get-Content で読み込む行の範囲を指定する

以下に変更し、取り敢えず 1-30行のみを配列に入れる事にします。

 $lines = (get-content playlist.txt )[1..30]                        #1 -30 を $Lines 配列に格納
 

これでもスクリプト開始時のpowershell console が小さかったりすると以下のようないただけない結果になります。

スクリプト開始時に画面の大きさを変える

スクリプト開始時に一定の画面の大きさにするため、以下を先頭に記述します。

$pshost = get-host
$pswindow = $pshost.ui.rawui
$newsize = $pswindow.buffersize
$newsize.height = 500
$newsize.width = 145
$pswindow.buffersize = $newsize
$newsize.height = 40
$newsize.width = 140
$pswindow.windowsize = $newsize

この様に実行すると先ず画面の大きさを変更します。

曲リストの取得時間を早くする

Get-ChildItem -Recurse では、若干時間がかかります。そこで、予め全ての曲の絶対パスをリストにしておき、そのリストの内容を Get-Content し Select-stringで探して出力する様に変更します。

絶対パスのリスト作成

次の様なスクリプトを記述し、Multimedia を格納しているDirectory に曲を追加した後、または定期的に このスクリプトを流して ALLMP3M4A.txt を更新しておきます。

$CurDir = PWD
CD i:\Multimedia
Get-childItem *.mp3,*.m4a -r | % { $_ -Replace('','') } > ALLMP3M4A.txt
CD $CurDir

私の場合、現在 Multimedia Directory には 11000曲あまりがありますが、このスクリプトを流すと十数秒で完了します。

リストは以下の様な形で出来ています。

PS >(Get-Content AllMP3M4A.txt).length
11187

PS >(get-content AllMP3M4a.txt)[500..520]
I:\Multimedia\Beatles\The Beatles (White Album) [Disc 2]\The Beatles - Revolution 9.mp3
I:\Multimedia\Beatles\The Beatles (White Album) [Disc 2]\The Beatles - Savoy Truffle.mp3
I:\Multimedia\Beatles\The Beatles (White Album) [Disc 2]\The Beatles - Sexy Sadie.mp3
I:\Multimedia\Beatles\The Beatles (White Album) [Disc 2]\The Beatles - You've Got To Hide Your Love Away.mp3
I:\Multimedia\Beatles\With The Beatles\01 It Won't Be Long (2).mp3
I:\Multimedia\Beatles\With The Beatles\01 IT WON'T BE LONG.mp3
I:\Multimedia\Beatles\With The Beatles\02 All I've Got To Do.mp3
I:\Multimedia\Beatles\With The Beatles\03 All My Loving.mp3
I:\Multimedia\Beatles\With The Beatles\04 Don't Bother Me.mp3
I:\Multimedia\Beatles\With The Beatles\05 Little Child (2).mp3
I:\Multimedia\Beatles\With The Beatles\05 LITTLE CHILD.mp3
I:\Multimedia\Beatles\With The Beatles\06 Till There Was You (2).mp3
I:\Multimedia\Beatles\With The Beatles\06 TILL THERE WAS YOU.mp3
I:\Multimedia\Beatles\With The Beatles\07 Please Mister Postman (2).mp3
I:\Multimedia\Beatles\With The Beatles\07 PLEASE MISTER POSTMAN.mp3
I:\Multimedia\Beatles\With The Beatles\08 Roll Over Beethoven (2).mp3
I:\Multimedia\Beatles\With The Beatles\08 ROLL OVER BEETHOVEN.mp3
I:\Multimedia\Beatles\With The Beatles\09 Hold Me Tight.mp3
I:\Multimedia\Beatles\With The Beatles\10 YOU REALLY GOT A HOLD ON ME (2).mp3
I:\Multimedia\Beatles\With The Beatles\10 YOU REALLY GOT A HOLD ON ME.mp3
I:\Multimedia\Beatles\With The Beatles\11 I Wanna Be Your Man.mp3

All リストから get-content

Playlist.txt の作成を以下の様に変更します。

$Sw = $Args[0]
# $Sw = "*" + $Sw + "*"
# ls $Sw -r | % { $_ -Replace('','') } | sls .mp3,.m4a > playlist.txt
Get-Content AllMP3M4A.txt | sls $Sw > playlist.txt

Active Window の時だけKey検知する様にする

ここまで作ってきて、BackGround でこのMedia Player で音楽を聴きながら作業をしていると、意図せず次の曲へスキップしたり、最初から再生されたりしてしまいます。それは、WindowがActiveでないときも エンターキーやカーソル移動キーを検知しているからです。これを防ぐには、Media PlayerのwindowがActiveな時だけキーを検知するように変更する必要があります。

Active Window かどうか判断する

次のコードをスクリプトの先頭に記述します。

$code = @'
    [DllImport("user32.dll")]
     public static extern IntPtr GetForegroundWindow();
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out int ProcessId);
'@

Add-Type $code -Name Utils -Namespace Win32
$MyPid = [IntPtr]::Zero;
$ActPid = [IntPtr]::Zero;
$hwnd = [Win32.Utils]::GetForegroundWindow()
$null = [Win32.Utils]::GetWindowThreadProcessId($hwnd, [ref] $MyPid)

上記で$MyPid に起動時の自身のPidがセットされます。

次の2行をWhile ループ内の先頭に記述します。

    while ($clock.elapsed -lt $timer){
           $hwnd = [Win32.Utils]::GetForegroundWindow()
           $null = [Win32.Utils]::GetWindowThreadProcessId($hwnd, [ref] $ActPid)

上記のコードで $ActPid には、その時点でActiveになっているWindow のPidが入ります。

次のIf文でキー検知を行うコード群を囲みます。

if ($MyPid -eq $ActPid ) {       # Active Window ?


      # キーを検知するコード

}

これでWindowがActiveな時だけキーを検知する様になります。

PageUp PageDown で表示ページを変える

今のままでは、いくら多くの曲がリストされても最初の30曲しか画面に表示されません。そこでPageDown / PageUpで30曲づつページに表示するようにします。

キーの定義に次の二つを追記します。

$KeyPageUp = '0x21' 
$KeyPageDown = '0x22'

PageDown で次の30曲を表示

30曲を1ページとします。最初のページと、リストした曲数から Total のページ数を把握するため、Playlist.txt を作った後に以下を記述します。$PLCount は Playlist.txt に記述されている曲の数です。[Math]::Ceiling($PLCount / 30) で、$PLCount を 30 で割り、小数点以下を切り上げることで Total のPage数を求めています。

$PLCount = (Get-content playlist.txt).length - 3
$TotalPage = [Math]::Ceiling($PLCount / 30)
$Page = 1

While ループの中に、$KeyPageDown を検知するために以下のコードを記述します。

            If( [bool]([PsOneApi.Keyboard]::GetAsyncKeyState($keyPageDown) -eq -32767 ))                       # PageDown キー  
                   {
                    if ( $Page -lt $TotalPage )
                       { $Page++ ; $Cursor = $Yy
                         $st = ($Page - 1) * 30 + 1 ; $en = $st + 29
                         $lines = ( Get-Content Playlist.txt )[$st..$en] 
                         cls
                         [Console]::SetCursorPosition(  0  ,  $Yy  )
                         for ( $Index = 0; $Index -le 29 ; $Index++) { 
                               if ( $Kyoku -eq $Lines[$Index] ) {
                                 write-host $Lines[$Index] -backgroundColor white -foreGroundColor black  
                                }else{
                                 write-host $Lines[$Index] 
                                } 
                         }
                         [Console]::SetCursorPosition(  0  ,  $Yy  ) ; write-host $Lines[0] -foreGroundColor Yellow -noNewLine  
                       }    
                   }

PageUp で前の30曲を表示

While ループの中に、$KeyPageUp を検知するために以下のコードを記述します。

             If( [bool]([PsOneApi.Keyboard]::GetAsyncKeyState($keyPageUp) -eq -32767 ))                       # PageUp キー  
                   {
                    if ( $Page -gt 1 )
                       { $Page-- ; $Cursor = $Yy
                         $st = ($Page - 1) * 30 + 1 ; $en = $st + 29
                         $lines = ( Get-Content Playlist.txt )[$st..$en] 
                         cls
                         [Console]::SetCursorPosition(  0  ,  $Yy  )
                         for ( $Index = 0; $Index -le 29 ; $Index++) { 
                                if ( $Kyoku -eq $Lines[$Index] ) {
                                 write-host $Lines[$Index] -backgroundColor white -foreGroundColor black  
                                }else{
                                write-host $Lines[$Index] 
                                } 
                         } 
                          [Console]::SetCursorPosition(  0  ,  $Yy  ) ; write-host $Lines[0] -foreGroundColor Yellow -noNewLine 
                       }    
                   }

現ページとTotal ページ数を Progress Bar に表示

Progress Bar に表示する $PrMessage を以下のように変更します。

$PrMessage = "Playing " + $lines[$i] + "     " + $Page + "/" + $TotalPage

以下の様に Progress Bar に Page/TotalPage が表示されます。

再生中のタイトル表示

再生中のタイトルや、ページの表示を調整します。

次のコードで、絶対パスからFile名のみを取り出します。

    $Fname = ls -literalpath $mediaplayer.Source.LocalPath | select-object name | oss | sls .mp3,.m4a
    $Fname = [String]$Fname
    $Fname = $Fname.Substring(0,$Fname.length - 4)
    $IIp = ($page - 1) * 30 + $i + 1

$IIp は、リストの何番目の曲を演奏しているかを求めます。

Progress Bar に表示するメッセージを次の様に編集します。

$PrMessage = $Fname + "     Playing[$IIp/$PLCount]      Page[$Page/$TotalPage}"

Progress Bar の表示は、こんな感じになります。

曲の再生時間、経過時間の表示

Porgress Bar に曲の長さとどれくらい経過したかを表示します。

[String]$mediaplayer.NaturalDuration.TimeSpan で曲の長さを得られます。

PS >add-type -assemblyname presentationcore
PS >$mediaplayer = new-object system.windows.media.mediaplayer
PS >$mediaplayer.open("I:\Multimedia\五木ひろし\五木ひろし -  夜汽車の女.mp3")
PS >[String]$mediaplayer.NaturalDuration.TimeSpan
00:03:32.4073000

次のコードで MM:SS の形で得られます。

$mmss = ([String]$mediaplayer.NaturalDuration.TimeSpan).substring(3,5)

同様に経過時間も $Clock の elapsed で得られます。

$mmsskeika = ([String]$Clock.Elapsed).substring(3,5)

Write-Progress を次の様に書き換えます。

Write-Progress -id 0 -Activity $PrMessage -Status "$mmsskeika          .            .            .            .            .           .            .            .            .            $mmss"  -PercentComplete $P 

こんな感じになります。右は曲の長さ、左は再生時間(経過時間)です。

タイトルとURLをコピーしました