【プログラム】私にもできた!ビデオテープをコンパクトにデジタル化<②変換編>


作成したプログラム

(1)フォルダ作成用初期処理
・初期フォルダ作成処理.bat
・初期フォルダ作成処理.ps1

→ 変換処理を行う前に、フォルダ構成を整えるために実行する処理

(2)実行プログラム
・H265_henkan.bat
・H265_henkan.ps1

→ 変換処理用のプログラム

ページのリンク元
私にもできた!ビデオテープをコンパクトにデジタル化<②変換編>

 (1)フォルダ作成用初期処理

 初期フォルダ作成処理.bat

echo off
echo.
echo "フォルダを作成します"
powershell -ExecutionPolicy RemoteSigned -command ./初期フォルダ作成処理.ps1

pause

 初期フォルダ作成処理.ps1

f
#本プログラムのカレントパス
$scriptPath = $MyInvocation.MyCommand.Path
$ParentPath = (Split-Path -Parent $scriptPath) + "\"

#write-host $scriptPath
write-host "-------$ParentPath 配下のフォルダをチェックします-------"
write-host ""

#変換データのフォルダ設定
$folder_01 = $ParentPath + "01_henkanmoto\"
$folder_02 = $ParentPath + "02_H265_file\"
$folder_03 = $ParentPath + "03_henkan_kanyrougo\"
$folder_04 = $ParentPath + "04_logfile\"
$folder_05 = $ParentPath + "05_report\"

$make_folder = @($folder_01,$folder_02,$folder_03,$folder_04,$folder_05)

$make_folder | %{
    if(!(test-path $_)){
    write-host ""
    write-host "$_ のフォルダを作成します"
    New-Item $_ -type directory | Out-Null

    }
    else{
 "$_ フォルダが存在します"

    }
}
write-host ""
write-host "---- 処理完了 ----"

(2)実行プログラム

 H265_henkan.bat

echo off
echo.
echo "処理を実行します"
echo "(Ctrl+j)で中断します"
echo "パラメータ①:100回連続、パラメータ②:23時00分まで"
timeout 5
powershell -ExecutionPolicy RemoteSigned -WindowStyle Minimized -command ./H265_henkan.ps1 100 2300
echo. 

 H265_henkan.ps1

<# ツール概要
■全体
・H.264のMpeg・MP4ファイル(6時間で約24GB)を、30分毎に1ファイルに分割して、H.265のMP4形式で10分の1サイズ近くまで圧縮変換
・「powercfg -l」コマンドで電源オプションを確認し、実行時には「常にON]、終了時には「バランス」にしておくと良い。★の箇所を確認
・バッチ画面上にログ情報を表示し、「04_logfile」フォルダ配下に同じ内容のログメッセージを出力。他のフォルダにもログを出力するには★の箇所を確認
・ffmegは日本語文字に非対応(パスに日本語文字があっても不可。@マークも不可)
 変換元ファイル名をRenameしてファイルリストをつくってから変換処理を行う仕様となっている
 例. ”2017年05月26日07時35分15秒.mpeg"を”20170526073515.mpeg"にRename
・30分毎に分割となるよう、開始時間を「0,1800,3600,・・・」と指定し、1800秒(30分)の変換処理を実行。枝番は最大22までとしている。
 開始時間と1800秒の値を変えることで、ビデオ変換の長さを変えることが可能
 ※PCの予期せぬシャットダウンなどが入るとやり直しとなるため、30分単位でこまめに分割している。
  後程、ブルーレイに焼く時も細かいほうが微調整ができて適度なサイズに収まる
  映像をサムネイル化する際も細かい単位の方がよりたくさんの画像が抽出できる
・720x480のサイズで変換。「05_report」にffmpegのレポート情報を出力。ファイルサイズが大きくなるので定期的に退避したほうが良い
・ソフトウェアエンコードは30fpsぐらいで、ビデオカードによるハードウェアエンコードは300fpsと10倍速くなるので、ビデオカード(GeForce GTX 1050 Ti)の利用を推奨
 187~191行目あたりの実行コマンドは次のようにしており、ビデオカード有のサンプル③をデフォルト設定としている。
 グラフィックカードが無い場合、サンプル①で処理を行うように、コメントを外して、③にコメントマークをつける

   #サンプル① グラフィックカード無し
    #★#$prm  = " -ss "  + "$start_time" + " -t 1800 -i " + $moto_file_full + " -s 720x480 -vcodec libx265 -threads 0 " + $saki_file_full + " -report"
    #サンプル② グラフィックカード有、レポートレベル エラー
    #★#$prm  = " -ss "  + "$start_time" + " -t 1800 -i " + $moto_file_full + " -s 720x480 -vcodec hevc_nvenc -threads 0 -loglevel error " + $saki_file_full + " -report"
    #サンプル③ グラフィックカード有、通常
    $prm  = " -ss "  + "$start_time" + " -t 1800 -i " + $moto_file_full + " -s 720x480 -vcodec hevc_nvenc -threads 0 " + $saki_file_full + " -report"

■バッチ
・バッチで指定している第1パラメータは、連続実行回数。第2パラメータは時刻指定で、過ぎたら次の処理には入らない。
 初回実行時に指定時刻が現在時刻より小さかったら、翌日の指定時刻までとなる。
 本ツールの実行フォルダに「日付判定.txt」が作成され、それをみて判定する
・バッチからPowershellを呼び出しており、第二パラメータの時刻を考慮しつつ、毎日タスクスケジューラでバッチを起動すると良い

■変換処理について
・変換元ファイルがいくつあるか確認してから処理を開始
・変換後ファイル格納先にある最新の枝番を確認して、その次の枝番から処理を開始している
・変換後のファイルが小さかったり短かったら、そのファイルを削除する
 (0MBより大きく1MB以下、長さが30分で35MB以下、5分以内で6MB以下)
  ※同じ長さでも音声などによりファイルサイズがかわる
・キーボードで「Ctrl + j」ボタンを押すと、次の処理には入らない
・1つ目の変換元ファイルの処理が終わったら、「03_henkan_kanyrougo」フォルダに変換元ファイルを移動し、「変換完了.txt」にファイル情報を記録する。
 2つ目の変換元ファイルの処理に移る

■注意点
・変換が終わったファイルは、再生ができるか、音声にずれがないかを確認すること。
・ビデオテープの再生が終わった状態でキャプチャが続いていた場合、何も映像が無いため青い画面の再生になる
 (30分青画面は変換処理後のファイルサイズが、長さ30分で25MBのため削除して、変換元ファイルは移動となって、次の変換元ファイルの処理に移る)
・本プログラムの利用および改変は自由に行っていただいて構いませんが、自己責任でお願いします。一切の責任は負いません。

■変更履歴(GiiQ)
2017/07/06 Ver1.0 新規公開
2017/09/21 Ver1.1 ファイル生成中が削除対象にならないように条件追加(0MBより大きい)
2017/10/02 Ver1.2 END処理の見直し

#>
#電源オプション 常にON
#powercfg -s ★568fcd4a-xxxx-44e4-b276-yyyyyyyyyyy★
 
#本プログラムのカレントパス
$scriptPath = $MyInvocation.MyCommand.Path
$ParentPath = (Split-Path -Parent $scriptPath) + "\"
 
#基本Path
$ffmpeg_folder = $ParentPath
 
#プログラム
$ffmpeg_bin_folder = $ffmpeg_folder + "ffmpeg\bin"
 
#変換データのフォルダ設定
$01_henkanmoto = $ffmpeg_folder + '01_henkanmoto'
$01_henkanmoto_file = $01_henkanmoto + '\*.*'
$02_H265_file  = $ffmpeg_folder + '02_H265_file'
$03_henkan_kanyrougo = $ffmpeg_folder + '03_henkan_kanyrougo'
$04_logfile = $ffmpeg_folder + '04_logfile'
$05_report = $ffmpeg_folder + '05_report'
 
cd $05_report
 
#ファイル名
$mpg_file = '*.mpg'
$mp4_file = '*.MP4'
 
 
#---------------------------------------------------------------
# 関数名 : PrintMsg
# 処理   : 日時を付与してメッセージを標準出力する。
#          第1引数が E の場合は、赤文字で標準出力する。
# 引数   : 第1引数 - I (情報), E (エラー),M(メッセージ)
#          第2引数 - メッセージ
# 戻り値 : なし
#---------------------------------------------------------------
Function PrintMsg ([string] $type, [string] $msg) {
 
    $now = Get-Date -Format "yyyy/MM/dd HH:mm:ss"
    $message = "$now $type $msg"
 
    if ($type -eq "I") {
        Write-Host $message
    } elseif($type -eq "M") {
        Write-Host $message -ForegroundColor yellow
    }else {
        Write-Host $message -ForegroundColor Red
    }

    # ログファイルに出力
    Write-Output $message | Out-File ${g_log_path} -Append -Encoding Default
    #Write-Output $message | Out-File $g_log_sub -Append -Encoding Default  #Dropboxなどのフォルダにもログを出力したい場合
}
 
# ログのファイル名
$g_script_base = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Name)
$g_yyyymmdd = Get-Date -Format "yyyyMMdd"
$g_log_path = "$04_logfile\${g_script_base}_${g_yyyymmdd}.log"
#$g_log_sub = "★パス★\${g_script_base}_${g_yyyymmdd}.log" #Dropboxなどのフォルダにもログを出力したい場合
 
#================================================================


#---------------------------------------------------------------
# 関数名 : Fnc_END
# 処理   : 処理終了
#---------------------------------------------------------------
Function Fnc_END {
 #電源オプション バランス
 #powercfg -s ★568fcd4a-xxxx-44e4-b276-yyyyyyyyyyy★
 PrintMsg "I"  "======================【END】=========================================" 
  exit
}

#================================================================
#---------------------------------------------------------------
# 関数名 : Make_FileList
#【処理1】 日本語に非対応のため、ファイル名を置換する
#【処理2】 名前の順(日付順)に並び替えたファイルリストを作成し、一番古いファイルを抽出する
# 引数   : なし
# 戻り値 : なし
#---------------------------------------------------------------
Function Make_FileList {
    #【処理1】 日本語に非対応のため、ファイル名を置換する。(年月日時分秒)を削除
    Get-ChildItem -path $01_henkanmoto_file -Include *年* | foreach-object { Rename-Item $_ -NewName ($_ -replace '年','')}
    Get-ChildItem -path $01_henkanmoto_file -Include *月* | foreach-object { Rename-Item $_ -NewName ($_ -replace '月','')}
    Get-ChildItem -path $01_henkanmoto_file -Include *日* | foreach-object { Rename-Item $_ -NewName ($_ -replace '日','')}
    Get-ChildItem -path $01_henkanmoto_file -Include *時* | foreach-object { Rename-Item $_ -NewName ($_ -replace '時','')}
    Get-ChildItem -path $01_henkanmoto_file -Include *分* | foreach-object { Rename-Item $_ -NewName ($_ -replace '分','')}
    Get-ChildItem -path $01_henkanmoto_file -Include *秒* | foreach-object { Rename-Item $_ -NewName ($_ -replace '秒','')}
    Get-ChildItem -path $01_henkanmoto_file | Rename-Item -newName {$_.Name -replace '\(','_'}
    Get-ChildItem -path $01_henkanmoto_file | Rename-Item -newName {$_.Name -replace '\)','_'}
 
    #--------------------------------------------------------
    #【処理2】 名前の順(日付順)に並び替えて一番古いファイルを取得
    if(test-path $01_henkanmoto_file -Include $mpg_file,$mp4_file){
        $Global:taisyo_file = ((Get-ChildItem -Path  $01_henkanmoto_file -Include $mpg_file,$mp4_file | Sort-Object name)[0].Name)
        $Global:taisyo_Length = ((Get-ChildItem -Path  $01_henkanmoto_file -Include $mpg_file,$mp4_file | Sort-Object name)[0].Length) / 1MB
    }
    else{ 
        PrintMsg "M"  "対象ファイルがありません。"
 Fnc_END
    }
 }
 
#================================================================
 
#================================================================
#---------------------------------------------------------------
# 関数名 : FFMPEG_START
#【処理】 ffmpegを実行
# 引数   : なし
# 戻り値 : なし
#---------------------------------------------------------------
Function FFMPEG_Start {
    cd $05_report
    
    PrintMsg "M"  "処理対象の枝番:  $edaban"
 
    $start_time_list = @(0,1800,3600,5400,7200,9000,10800,12600,14400,16200,18000,19800,21600,23400,25200,27000,28800,30600,32400,34200,36000,37800,39600)
 
    $start_time = $start_time_list[$edaban]
 
    PrintMsg  "I"  "開始時間:$start_time"
 
    $Global:moto_file_full = $01_henkanmoto + '\' +  $taisyo_file  #変換元の動画<Fullパス>
    #枝番が一桁台のときに頭に0をつける
    if ($edaban -le 9){
      if($taisyo_file.Contains(".MP4")){
        $Global:saki_file_1 =  ($taisyo_file.Replace(".MP4","")) + '_0' + $edaban + "_H265.mp4"
        }else{
        $Global:saki_file_1 =  ($taisyo_file.Replace(".mpg","")) + '_0' + $edaban + "_H265.mp4"
    }
        $Global:saki_file_full = $02_H265_file + '\' +   $saki_file_1
    }else{
      if($taisyo_file.Contains(".MP4")){
        $Global:saki_file_1 =  ($taisyo_file.Replace(".MP4","")) + '_' + $edaban + "_H265.mp4"
        }else{
            $Global:saki_file_1 =  ($taisyo_file.Replace(".mpg","")) + '_' + $edaban + "_H265.mp4"
    }
        $Global:saki_file_full = $02_H265_file + '\' +   $saki_file_1
    }
    #PrintMsg  "I" "変換元ファイル: $taisyo_file"
    PrintMsg  "M" "変換後ファイル: $saki_file_1"
 
    #ffmepegの実行コマンド
    $ffmpeg = $ffmpeg_bin_folder + "\ffmpeg.exe"

    #サンプル① グラフィックカード無し
    #★#$prm  = " -ss "  + "$start_time" + " -t 1800 -i " + $moto_file_full + " -s 720x480 -vcodec libx265 -threads 0 " + $saki_file_full + " -report"
    #サンプル② グラフィックカード有、レポートレベル エラー
    #★#$prm  = " -ss "  + "$start_time" + " -t 1800 -i " + $moto_file_full + " -s 720x480 -vcodec hevc_nvenc -threads 0 -loglevel error " + $saki_file_full + " -report"
    #サンプル③ グラフィックカード有、通常
    $prm  = " -ss "  + "$start_time" + " -t 1800 -i " + $moto_file_full + " -s 720x480 -vcodec hevc_nvenc -threads 0 " + $saki_file_full + " -report"
    
    PrintMsg  "I" "実行コマンド: $ffmpeg -ArgumentList $prm -wait -WindowStyle Minimized "
     
    write-host "実行します"
    timeout /T 5
    
    start-process $ffmpeg -ArgumentList $prm -wait -WindowStyle Minimized
 
    $Global:saki_Length = $(Get-ChildItem $saki_file_full).Length / 1MB
    PrintMsg "M"  "変換後ファイルのサイズ(MB) : $saki_Length "
 
    #ファイルの長さをチェック
    $sh = New-Object -ComObject Shell.Application
    $f = $sh.Namespace($02_H265_file)
    $fi = $f.ParseName($saki_file_1)
    
    $saki_nagasa = $f.GetDetailsOf($fi, 27)
    PrintMsg "M"  "変換後ファイルの長さ : $saki_nagasa "
 
  #0MBより大きく、1MB以下の場合は削除
  if(($saki_Length  -le 1) -And ($saki_Length  -gt 0)){
    PrintMsg "M"  "0MBより大きく、1MB以下のため削除します。$saki_file_full"    Remove-Item $saki_file_full -Force   
    #write-host "変換元ファイルを移動します。(30秒保留)"
        timeout /T 5
        File_Move
    }
 
  #長さが30分で35MB以下の場合は削除
    if($edaban -lt 22){
        if(($saki_nagasa -eq '00:30:00') -And  ($saki_Length -le 35) -And ($saki_Length -gt 0)){
            if(test-path $saki_file_full){
                       PrintMsg "M"  "30分で0MBより大きく35MB以下のため削除します。$saki_file_full"
                       Remove-Item $saki_file_full -Force   
            }
                   #write-host "変換元ファイルを移動します。(5秒保留)"
                timeout /T 5
                #30分の動画が35MBより小さいときは青画面が続いているので移動処理実行
                File_Move
            
        }
        elseif(($saki_nagasa -lt '00:05:00') -And ($saki_Length -le 5) -And ($saki_Length -gt 0)){
            if(test-path $saki_file_full){
                       PrintMsg "M"  "5分以内で0MBより大きく5MB以下のため削除します。$saki_file_full"
                       Remove-Item $saki_file_full -Force   
            }
                timeout /T 5
                #5分以内の動画が6MBより小さいときは青画面が続いているので移動処理実行
                #処理を途中で中断すると移動となる場合もあり注意!
                File_Move
            
        }
     }
 
}
 
#================================================================

#================================================================
#---------------------------------------------------------------
# 関数名 : File_Count
#【処理】 対象のファイル数をカウント
# 引数   : なし
# 戻り値 : なし
#---------------------------------------------------------------
Function File_Count {
        $count_mpg = (Get-ChildItem -Path $01_henkanmoto $mpg_file | Measure-Object).Count
        $count_mp4 = (Get-ChildItem -Path $01_henkanmoto $mp4_file | Measure-Object).Count
        PrintMsg  "M" "変換元のファイル数(mpegファイル): $count_mpg "
        PrintMsg  "M" "変換元のファイル数(MP4ファイル) : $count_mp4 "
 
}
#================================================================

#================================================================
#---------------------------------------------------------------
# 関数名 : File_Move
#【処理】 変換元ファイルを移動
# 引数   : なし
# 戻り値 : なし
#---------------------------------------------------------------
Function File_Move {
    if(test-path $moto_file_full){
        PrintMsg  "M" "変換元ファイルを移動します $taisyo_file "
        Move-Item $moto_file_full $03_henkan_kanyrougo  #-Confirm
 
 #対象ファイル数のカウント
 File_Count
 
        $kanyrougo_txt = $03_henkan_kanyrougo + '\変換完了.txt'
 
        $now2 = Get-Date -Format "yyyy/MM/dd HH:mm:ss"
        $message2 = "$now2 $taisyo_file $taisyo_nagasa $taisyo_Length MB"
        Write-Output $message2 | Out-File ${kanyrougo_txt} -Append -Encoding Default
   }
}
#================================================================
 
#================================================================
#--------------------------------------------------------
#【処理3】 変換先フォルダにあるファイルから最新の枝番を取得し、次の枝番から実行。
# 引数①連続回数
# 引数②指定時刻を超えたら、次の処理を行わない(1700) 。現在の時刻より小さい場合は翌日として基準日判定をする
 
 
#引数① 連続回数を指定(デフォルト1)
if ($args[0] -gt 0){ $renzoku = $args[0] }
else {$renzoku = 1}
 
#引数② 次の実行を中止する時刻を指定(Null=デフォルト9999)
if ($args[1] -ne $null){
    $time = [string]($args[1])
    $time1 = [int]($time)
}
else {$time1 = 9999}
 
#引数②、現在時刻よりも小さい時刻は翌日に設定
if(($args[1] -ne $null) -and ($args[1] -lt [int]((Get-Date).ToString("HHmm")))){
    $kijunbi = (Get-Date).AddDays(1).tostring("yyyyMMdd")
}
else {
    $kijunbi = (Get-Date).AddDays(0).tostring("yyyyMMdd")
}
$filename = $ffmpeg_folder + "\日付判定.txt"
Write-Output $kijunbi | Out-File $filename
 
 
 
 
PrintMsg "I"  "======================【START】========================================="
       
#対象ファイル数のカウント
File_Count

for ($renzoku_i=0 ; $renzoku_i -lt $renzoku ; $renzoku_i++ ) #連続回数を超えるまで実行
{
 
    #ファイルリスト作成
    Make_FileList
    
    # ログ関数実行
    write-host " "
    $num_i = $renzoku_i + 1
    PrintMsg "I"  "------------【処理  $num_i 回目/$renzoku 】 -----------------"
    PrintMsg  "I"  "対象ファイル : $taisyo_file"
    PrintMsg  "I"  "対象ファイルサイズ(MB) : $taisyo_Length"
 
    # 対象ファイルの長さ
    $sh_t = New-Object -ComObject Shell.Application
    $f_t = $sh_t.Namespace($01_henkanmoto)
    $fi_t = $f_t.ParseName($taisyo_file)
    $taisyo_nagasa = $f_t.GetDetailsOf($fi_t, 27)
    PrintMsg "M"  "対象ファイルの長さ : $taisyo_nagasa "
 
 
    #初期化
    $check_mp4_wk =""
    $check_mp4  =""
    $check_edaban =""
    $Global:edaban = 0
 
    #チェック用のファイル名
  if($taisyo_file.Contains(".MP4")){
        $check_mp4_wk = ($taisyo_file.Replace(".MP4","")) + "*"
  }else{
        $check_mp4_wk = ($taisyo_file.Replace(".mpg","")) + "*"
    }
 
    cd $02_H265_file
 
    if(test-path $check_mp4_wk){
        #Fullパス
        $check_mp4 = $(Get-ChildItem -path $02_H265_file $check_mp4_wk | Sort-Object name -Descending)[0].fullname
        #Fullパス名、後ろから10文字の1文字
        $check_edaban = [int]($check_mp4.Substring($check_mp4.Length -11,2))
        
        PrintMsg "I"  "枝番チェック対象:  $check_mp4"
        PrintMsg "I"  "一番大きな枝番:  $check_edaban"
 
        if (test-path $check_mp4 ){
            if($check_edaban -eq 21){
                #次の枝番で処理実行。枝番22で最後
                #PrintMsg "I"  "枝番21"
                $edaban = $check_edaban +1
 
                FFMPEG_Start
 
        write-host "ファイルを移動します。(5秒保留)"
        timeout /T 5
                File_Move
            }
            elseif($check_edaban -lt 21){
                #次の枝番で処理実行
                $edaban = $check_edaban +1
 
                FFMPEG_Start
                if( $saki_length -lt 0.3){  #MB
                    write-host "ファイルを移動します"
                    timeout /T 5
            #ファイルサイズが小さいときのみ移動処理実行
                    File_Move
                }
            }
      }
 
    }else{
        #初回処理になるので実行
        PrintMsg "M"  "****** 初回処理 ******"
        $edaban = 0
        FFMPEG_Start
    }
 
 
    #日付判定
    $setteibi = (Get-Content $filename) -as [string[]]
    $today = (Get-Date).AddDays(0).tostring("yyyyMMdd")
    if($setteibi -eq $today){
        #PrintMsg "I" "設定日($setteibi)と今日の日付($today)が同じです"
 
        #指定時間判定
        if([int]((Get-Date).ToString("HHmm")) -ge $time1){
            PrintMsg "M"  "指定時間( $time )を超えたため、ここで終了とします"
            $renzoku_i = 999
        }
    }
    else{
        #PrintMsg "I" "設定日($setteibi)と今日の日付($today)が異なります"
    }
 
    [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 
    #キー判定用設定
    $rawui = $Host.ui.rawui
    $keystates = [System.Management.Automation.Host.ControlKeyStates]
    $modifier = $keystates::LeftCtrlPressed -bor $keystates::RightCtrlPressed
    $keymap = [System.Windows.Forms.Keys]
 
    #キー判定
    while ($rawui.KeyAvailable)
    {
        $keyinput = $rawui.Readkey("NoEcho,IncludeKeyUp,IncludeKeyDown")
        if (($keyinput.VirtualKeycode -eq $keymap::J) -and `
            ($keyinput.ControlKeyState -band $modifier))
        {
        PrintMsg "M"  "キーボードで中断操作(Ctrl+j)が行われたため、ここで終了とします"
        $renzoku_i = 999
        break
        }
    }
}

#終了処理
Fnc_END