ささみの甘辛炒め

調理道具

  • フライパン大
  • 小皿(タレを混ぜておく)

材料(3人分)

【鶏肉】

  • 鶏ささみ   450~500g
  • 薄力粉    大さじ2
  • サラダ油   大さじ1

【タレ】

  • しょうゆ   大さじ2
  • みりん    大さじ2
  • 料理酒    大さじ2
  • 砂糖     大さじ1.5
  • コチュジャン 大さじ1
  • 白いりごま  大さじ2
  • 七味唐辛子  小さじ1

下準備

【鶏肉】

  • 鶏肉の筋を取る(ここを参照)
    • 鶏肉の筋に沿って切れ目を入れる
    • 鶏肉を裏返す
    • 筋を持って筋から鶏肉を削ぎ落とすように包丁を当て、筋を引っ張って筋を取る
  • 2cm厚程度に鶏肉を削ぎ切る
  • まな板の上に鶏肉を並べ、小麦粉を塗す

【タレを用意する】

  • しょうゆ大さじ2、みりん大さじ2、料理酒大さじ2、砂糖大さじ1.5を小皿に入れて混ぜる

調理

【鶏肉を炒める】

  1. フライパンにサラダ油大さじ1をいれ、中火で温める
  2. 鶏肉を入れてフライパンに並べ、表面が少し焦げるまで(2分程度)焼く
  3. 菜箸で鶏肉をひっくり返し、同じように表面が少し焦げるまで焼く

【タレをからめる】

  1. 弱火にして、小皿のタレをかける
  2. 七味を小さじ1程度少なめに振りかける
  3. コチュジャン大さじ1をフライパンに入れる。鶏肉の隙間にいれること。
  4. 一混ぜしてタレを絡める
  5. そのまま放置して水分を飛ばし、粘度が出るまで待つ
  6. 逆さまにしてさらに絡める
  7. 味見をして七味を追加して混ぜる
  8. 火を止めて冷めるまで待つ
  9. 盛り付ける

メモ

  • 鶏肉を炒めすぎると固くなって美味しくないので、中火でさっと焼くのが食感のいい鶏肉にするコツです
  • マヨネーズをつけると美味しいです

参考レシピ

クラシル:https://www.kurashiru.com/recipes/8c7b7149-0d77-41d2-abd3-3dca57c189aa

至高のガーリックライス

調理道具

  • 深鍋フライパン大
  • アルミホイルとタオル(牛肉を包む)

材料(3人分)

  • ごはん     600g(3膳分)
  • 牛サーロイン肉 400g~450g ※ステーキ用(他の牛肉で代用も可)
  • 牛脂      2個
  • 塩       ひとつまみ
  • 黒こしょう   少々(9捻りくらい)
  • ニンニク    6片 ※3個はガーリックチップ、3個はご飯と炒める
  • 醤油      大さじ1.5
  • コンソメ顆粒  5g(1個分)
  • 有塩バター   30g
  • 小ネギ     3本

下準備

  • 牛肉を常温に戻す
  • 牛肉の筋を切っておく
  • 牛肉に塩・こしょうを塗しておく
  • ご飯を温めておく
  • にんにく3片を輪切りにする
  • にんにく3片をみじん切りにする
  • 小ネギを輪切りにする

調理

【ガーリックチップを作る】

  1. フライパンに牛脂を入れて温めて溶かす
  2. ガーリックスライスを入れて、弱火で狐色になる寸前まで炒めて火を止める
    ※焦がさないように注意すること
  3. 箸で小皿に移す ※剥がれ落ちた芯の部分はきちんと取り切ること

【牛肉を焼く】

  1. フライパンに牛肉を入れて強火にする
  2. 焦げ目がつく寸前で、ひっくり返して両面を焼く。
  3. 牛肉をアルミホイルで包み、布で包んでおく

【ライスを炒める】

  1. 弱火にして、ニンニクのみじん切りを入れて香りが立つ(色が少し変わる程度)までじっくり炒める
  2. 中火にしてフライパンにご飯を入れて炒める
  3. フライパンに塩ひとつまみ、醤油大さじ1.5、バター30g、コンソメ顆粒1個を入れて炒める
  4. 黒胡椒を入れて炒める
  5. 火を止める

【盛り付ける】

  1. ガーリックライス皿に盛り付ける
  2. 牛肉をスライスして盛り付ける
  3. ガーリックチップと小ネギを散らす

メモ

  • ニンニクは焦げやすいので、炒まっている寸前くらいで次の工程に進むようにするといいです。
  • 牛肉とガーリックチップを抜くと、ガーリックライスだけになります
  • 小ネギの代わりに乾燥パセリでもいいです

参考レシピ

至高のさつまいもサラダ

調理道具

  • ボウル(さつまいもを蒸す)
  • フライパン小(ベーコンと卵を焼く)

材料(3人分)

【さつまいも】

  • さつまいも   280g ※店頭で売っているさつまいも一本分程度
  • 水       大さじ1
  • ベーコン    40g
  • 卵       2個
  • マヨネーズ   40g
  • 創味シャンタン 小さじ1
  • カレー粉    小さじ1/3
  • 塩       適量
  • 黒コショウ   適量

下準備

  • さつまいもを1cm角の角切りにする
  • ベーコンを5mm程度の細さの短冊で切る

調理

【さつまいもを蒸す】

  1. さつまいもをボウル小に入れて、600Wで6分蒸す
  2. 粗熱をとる

【卵とベーコンを焼く】※さつまいもの粗熱をとっている間に行う

  1. フライパンを中火で温めて、ベーコンを焼く
  2. 卵2個をフライパンに割り入れて白身が固まるまで焼く
  3. 目玉焼きをひっくり返して、黄身を8割程度固まるまで焼く

【混ぜる】

  1. さつまいもをフォークで軽く崩す
  2. ボウルにベーコン・卵を入れて混ぜる
  3. 粗熱をとる
  4. マヨネーズ40g、創味シャンタン小さじ1、カレー粉小さじ1/3、黒胡椒を入れて混ぜる
  5. 塩で味を整えて盛り付ける

メモ

  • ベーコンの塩味や創味シャンタンの味が結構濃いので、塩は最後に味調整で入れる方がいいです。

参考レシピ

Amatsukazeを利用してCM抜きMP4を自動生成する

※2025/04/29 スクリプトのリファクタリングを行いました

TS抜きしたM2TSファイルは1時間番組でもファイルサイズが7GBを超えるため、録画後にバックアップしたりコピーしたりする際に時間がかかる上にストレージも圧迫します。さらに再生用ソフトも限定されるため、視聴にも制限がかかります。この問題はMP4へ変換してファイルサイズを落とすことで解決できます。

MP4へ変換したときのファイルサイズは、スポーツ番組や旅番組など画像の変化の激しいものや古い映画などのようにノイズの多いものはM2TSの半分程度までしか落ちないこともありますが、アニメなどの変化の少ないものは10分の1程度まで落とせます。概ね2-3割程度まで落とせると考えればいいでしょう。

また、変換の際にCMを抜くとさらにファイルサイズを落とせます。通常2割弱程度がCMですので、圧縮後のファイルからさらに2割サイズを落とすことができます。

しかし、録画した番組を保存する際に必要とされる機能はファイルの変換だけでなく、変換したファイルをNASへコピーしたり、古いファイルを削除してHDDの容量を確保したりするなど、自動化に際して様々な要求が出てきます。

そこで、これらの作業の自動化には、CMカットとM2TS→MP4変換にWindows上で動作するAmatsukazeを利用し、Powershellでファイルのコピー・削除処理を行う一連の処理を自動化します。

なお、PowershellはWindowsのデフォルトでインストールされているシェルなので、Pythonのように別途ソフトをインストールす必要がなく、設定フォルダを丸ごとコピー&ペーストするだけで簡単に実行環境をセットアップできる利点があります。

スクリプトはコピー・変換・削除の機能をそれぞれスクリプトファイルを分けて実装し、バッチファイル側で機能を選択する形で様々なパターンに対応できるようにしています。これらのスクリプトは以下の処理を自動化しています。

  1. M2TSファイルのコピーをとる
  2. Amatsukazeを使ってCM抜き処理とmp4変換を実行する
  3. 最終書込日時が古いファイルを削除する

このスクリプト群は下記の機能も実装してあります。

  • すでに変換・コピー済みのm2tsファイルの処理はスキップする
  • コピー・変換中にスケジューラーなどで変換・コピー処理を再実行した場合は処理をスキップする
  • 録画中のM2TSはバックアップしない
  • ファイルコピー途中に強制終了した場合には次回実行時に再度コピーを行う
  • 変換途中に強制終了させた場合は次回実行時に再度変換処理を行う
  • ファイル名の先頭に’_’をつけると、変換・コピーの対象外になる
  • ファイル削除後にフォルダが空になった場合はフォルダも削除する
  • 指定したフォルダサイズが指定容量を超えた場合は最終書き込み日時の古いファイルから順番に削除して、自動的に指定したフォルダサイズをキープする

ファイルは必ずBOM付きUTF-8で保存してください。BOMなしの場合、PowerShell内の日本語がShift JISとして認識されてしまい、PowerShellの処理文字コードであるUTF-8と食い違いが発生して正しく処理されません。

参考までに、RTX 4070TiでCM抜きとmp4へのエンコードにかかる時間は、CM付きの2時間映画で12分~13分程度です。N100の場合は40分程度です。

AmatsukazeCli.exeの実行中のメッセージが文字化けして読めない場合は、下記のシステムロケール設定を日本語にすると正しく表示されるようになります。

参考になれば幸いです。

@echo off
chcp 65001

@REM Initialize batch
SET CURRENT_DIR_CONVERT_BAT=%~dp0
cd /d %CURRENT_DIR_CONVERT_BAT%
SET BATCH_NAME_CONVERT_BAT=%~n0%~x0

@REM Get date/time string
for /f %%a in ('wmic os get LocalDateTime ^| findstr \.') DO SET LDT=%%a
SET CUR_DATE=%LDT:~0,8%
SET CUR_TIME=%LDT:~8,6%

@REM Constant
SET POWERSHELL_SCRIPT_DIR=%CURRENT_DIR_CONVERT_BAT%
SET LOG_FILE_PATH=S:\log\%CUR_DATE%_%CUR_TIME%_%~n0.log
SET POWERSHELL_SCRIPT_BACKUP=%POWERSHELL_SCRIPT_DIR%Backup.ps1
SET POWERSHELL_SCRIPT_DELETE_DOT_UNDERSCORE_FILES=%POWERSHELL_SCRIPT_DIR%DeleteDotUnderScoreFiles.ps1
SET POWERSHELL_SCRIPT_COPY_M2TS=%POWERSHELL_SCRIPT_DIR%CopyM2ts.ps1
SET POWERSHELL_SCRIPT_COPY_CONVERT=%POWERSHELL_SCRIPT_DIR%Convert.ps1
SET POWERSHELL_SCRIPT_COPY_MP4=%POWERSHELL_SCRIPT_DIR%CopyMp4.ps1
SET POWERSHELL_SCRIPT_DELETE_FILES=%POWERSHELL_SCRIPT_DIR%DeleteFile.ps1

@REM Copy Record files to NAS
SET POWERSHELL_SCRIPT=%POWERSHELL_SCRIPT_COPY_M2TS%
SET ARGUMENT=
SET ARGUMENT= %ARGUMENT% -log_file "%LOG_FILE_PATH%"
SET ARGUMENT= %ARGUMENT% -source "\\192.168.1.6\tv-recorder\recorded_files\delete"
SET ARGUMENT= %ARGUMENT% -destination "\\bluesky-nas\tv_program_backup\m2ts\delete"
SET ARGUMENT= %ARGUMENT% -quiet
@REM SET ARGUMENT= %ARGUMENT% -delete
@REM SET ARGUMENT= %ARGUMENT% -shutdown
@echo [%BATCH_NAME_CONVERT_BAT%] powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%
call powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%

@REM Copy Record files to NAS
SET POWERSHELL_SCRIPT=%POWERSHELL_SCRIPT_COPY_M2TS%
SET ARGUMENT=
SET ARGUMENT= %ARGUMENT% -log_file "%LOG_FILE_PATH%"
SET ARGUMENT= %ARGUMENT% -source "\\192.168.1.6\tv-recorder\recorded_files\keep"
SET ARGUMENT= %ARGUMENT% -destination "\\bluesky-nas\tv_program_backup\m2ts\keep"
SET ARGUMENT= %ARGUMENT% -quiet
@REM SET ARGUMENT= %ARGUMENT% -delete
@REM SET ARGUMENT= %ARGUMENT% -shutdown
@echo [%BATCH_NAME_CONVERT_BAT%] powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%
call powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%

@REM Convert files
SET POWERSHELL_SCRIPT=%POWERSHELL_SCRIPT_COPY_CONVERT%
SET ARGUMENT=
SET ARGUMENT= %ARGUMENT% -log_file "%LOG_FILE_PATH%"
SET ARGUMENT= %ARGUMENT% -source "\\bluesky-nas\tv_program_backup\m2ts"
SET ARGUMENT= %ARGUMENT% -destination "\\bluesky-nas\tv_program\converted_files"
@REM SET ARGUMENT= %ARGUMENT% -nvenc
SET ARGUMENT= %ARGUMENT% -quiet
@REM SET ARGUMENT= %ARGUMENT% -shutdown
@echo [%BATCH_NAME_CONVERT_BAT%] powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%
call powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%

@REM Delete old converted files from NAS
SET POWERSHELL_SCRIPT=%POWERSHELL_SCRIPT_DELETE_FILES%
SET ARGUMENT=
SET ARGUMENT= %ARGUMENT% -log_file "%LOG_FILE_PATH%"
SET ARGUMENT= %ARGUMENT% -source "\\bluesky-nas\tv_program\converted_files\delete"
SET ARGUMENT= %ARGUMENT% -day 365
SET ARGUMENT= %ARGUMENT% -totalsize 2000
SET ARGUMENT= %ARGUMENT% -quiet
@REM SET ARGUMENT= %ARGUMENT% -shutdown
@echo [%BATCH_NAME_CONVERT_BAT%] powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%
call powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%

@REM Delete old recorded files from NAS
SET POWERSHELL_SCRIPT=%POWERSHELL_SCRIPT_DELETE_FILES%
SET ARGUMENT=
SET ARGUMENT= %ARGUMENT% -log_file "%LOG_FILE_PATH%"
SET ARGUMENT= %ARGUMENT% -source "\\bluesky-nas\tv_program_backup\m2ts\delete"
SET ARGUMENT= %ARGUMENT% -day 365
SET ARGUMENT= %ARGUMENT% -totalsize 3000
SET ARGUMENT= %ARGUMENT% -quiet
@REM SET ARGUMENT= %ARGUMENT% -shutdown
@echo [%BATCH_NAME_CONVERT_BAT%] powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%
call powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%

@REM Delete unnecessary files/folders(tv_program_backup)
SET POWERSHELL_SCRIPT=%POWERSHELL_SCRIPT_DELETE_FILES%
SET ARGUMENT=
SET ARGUMENT= %ARGUMENT% -log_file "%LOG_FILE_PATH%"
SET ARGUMENT= %ARGUMENT% -source "\\bluesky-nas\tv_program_backup\m2ts"
SET ARGUMENT= %ARGUMENT% -quiet
@REM SET ARGUMENT= %ARGUMENT% -shutdown
@echo [%BATCH_NAME_CONVERT_BAT%] powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%
call powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%

@REM Delete unnecessary files/folders(tv_program_backup)
SET POWERSHELL_SCRIPT=%POWERSHELL_SCRIPT_DELETE_FILES%
SET ARGUMENT=
SET ARGUMENT= %ARGUMENT% -log_file "%LOG_FILE_PATH%"
SET ARGUMENT= %ARGUMENT% -source "\\bluesky-nas\tv_program\converted_files"
SET ARGUMENT= %ARGUMENT% -quiet
@REM SET ARGUMENT= %ARGUMENT% -shutdown
@echo [%BATCH_NAME_CONVERT_BAT%] powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%
call powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%

@REM Delete old recorded files from Media-player
SET POWERSHELL_SCRIPT=%POWERSHELL_SCRIPT_DELETE_FILES%
SET ARGUMENT=
SET ARGUMENT= %ARGUMENT% -log_file "%LOG_FILE_PATH%"
SET ARGUMENT= %ARGUMENT% -source "S:\recorded_files\delete"
SET ARGUMENT= %ARGUMENT% -day 180
SET ARGUMENT= %ARGUMENT% -totalsize 600
SET ARGUMENT= %ARGUMENT% -quiet
@REM SET ARGUMENT= %ARGUMENT% -shutdown
@echo [%BATCH_NAME_CONVERT_BAT%] powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%
call powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%

@REM Backup to NAS(TV recorder)
SET POWERSHELL_SCRIPT=%POWERSHELL_SCRIPT_BACKUP%
SET ARGUMENT=
SET ARGUMENT= %ARGUMENT% -log_file "%LOG_FILE_PATH%"
SET ARGUMENT= %ARGUMENT% -source "\\192.168.1.6\tv-recorder\backup"
SET ARGUMENT= %ARGUMENT% -destination "\\bluesky-nas\pc_backup\tv-recorder1"
SET ARGUMENT= %ARGUMENT% -quiet
@REM SET ARGUMENT= %ARGUMENT% -shutdown
@echo [%BATCH_NAME_CONVERT_BAT%] powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%
call powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%

@REM Backup to NAS(Media-player)
SET POWERSHELL_SCRIPT=%POWERSHELL_SCRIPT_BACKUP%
SET ARGUMENT=
SET ARGUMENT= %ARGUMENT% -log_file "%LOG_FILE_PATH%"
SET ARGUMENT= %ARGUMENT% -source "D:\\"
SET ARGUMENT= %ARGUMENT% -destination "\\bluesky-nas\pc_backup\media-player\D_Drive"
SET ARGUMENT= %ARGUMENT% -quiet
@REM SET ARGUMENT= %ARGUMENT% -shutdown
@echo [%BATCH_NAME_CONVERT_BAT%] powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%
call powershell -NoProfile -ExecutionPolicy Bypass -file %POWERSHELL_SCRIPT% -ArgumentList %ARGUMENT%

@REM pause
###################
# Enumuration
###################
enum OutputLogType {
    Info
    Warning
    Error
    Debug
    NoType
}

###################
# Class
###################
class Entry{

    [string]$value
    [string]$comment

    Entry( [string]$value ){
        $this.value = $value
        $this.comment = ""
    }

    Entry( [string]$value, [string]$comment ){
        $this.value = $value
        $this.comment = $comment
    }

    UpdateValue( [string]$value ){
        $this.value = $value
    }

    UpdateComment( [string]$comment ){
        $this.comment = $comment
    }
}

class IniFileManager {

    [System.Collections.Specialized.OrderedDictionary]$ini_contents = [System.Collections.Specialized.OrderedDictionary]@{}
    [string]$ini_file_path = ""

    IniFileManager($ini_file_path) {

        $this.ini_file_path = $ini_file_path

        [IniFileManager]::ReadFromIniFile($this.ini_file_path,$this.ini_contents)
    }

    static ReadFromIniFile( [string]$ini_file_path, [System.Collections.Specialized.OrderedDictionary]$ini_contents ){
        [int]$comment_count = 0
        [string]$section=""

        if( Test-Path "${ini_file_path}" ){
            switch -regex -file $ini_file_path {
                "^(\s*;.*)$" {
                    $value = $matches[1]
                    $comment_count = $comment_count + 1
                    $entry = "=Comment" + $comment_count + "="
                    if( [string]::IsNullOrEmpty($section) ) {
                        $section = "NoSection"
                        $ini_contents[$section] = [System.Collections.Specialized.OrderedDictionary]@{}
                    }
                    $ini_contents[$section][$entry] = [Entry]::new($value)
                    continue
                }
                "^\[(.+)\]" {
                    $section = $matches[1]
                    $ini_contents[$section] = [System.Collections.Specialized.OrderedDictionary]@{}
                    $comment_count = 0
                    continue
                }
                "(.+?)\s*=(.*)" {
                    $entry = $matches[1].Trim()
                    $data = $matches[2]
                    $value = ""
                    $comment = ""
                    $data_without_string_in_double_quatation = ( $data.Replace("\","\\") -replace "`"((?:[^\\`"]+|\\.)*)`"","")
                    if( $data_without_string_in_double_quatation.IndexOf(";") -eq -1 ){
                        # No comment in entry
                        $value = $data.Trim()
                    } else {
                        $comment = ($data_without_string_in_double_quatation -replace "^.*?(?=;)","" )
                        $value = $data.Replace( $comment,"" ).Trim()
                    }
                    if( [string]::IsNullOrEmpty($section) ) {
                        $section = "NoSection"
                        $ini_contents[$section] = [System.Collections.Specialized.OrderedDictionary]@{}
                    }
                    $ini_contents[$section][$entry] = [Entry]::new($value,$comment)
                    continue
                }
            }
        }
    }

    Merge( [string]$ini_file_path ){

        [System.Collections.Specialized.OrderedDictionary]$ini_contents_merge = [System.Collections.Specialized.OrderedDictionary]@{}

        [IniFileManager]::ReadFromIniFile($ini_file_path,$ini_contents_merge)

        foreach( $section in $ini_contents_merge.Keys ){
            foreach( $entry in $ini_contents_merge[$section].Keys ){
                if( $null -eq $this.ini_contents[$section] ){
                    $this.ini_contents[$section] += [System.Collections.Specialized.OrderedDictionary]@{}
                }
        
                $data=$ini_contents_merge[$section][$entry]

                if( $null -eq $this.ini_contents[$section][$entry] ){
                    $this.ini_contents[$section].Add($entry,$data)
                } else {
                    $this.ini_contents[$section][$entry].UpdateValue($data.value)
                }
            }
        }
    }

    [bool] IsValidSection( [string]$section ) {
        return ( $null -ne $this.ini_contents[$section] )
    }

    [bool] IsValidEntry( [string]$section, [string]$entry ) {
        return $this.IsValidEntry($section, $entry, [ValueType]::String )
    }

    [bool] IsValidEntry( [string]$section, [string]$entry, [ValueType]$value_type ) {
        if( $null -ne $this.ini_contents[$section] ){
            if( $null -ne $this.ini_contents[$section][$entry] ){
                switch($value_type){
                    [ValueType]::Long {
                        return [long]::TryParse($this.ini_contents[$section][$entry].value,[ref]$null) 
                    }
                    [ValueType]::Double {
                        return [Double]::TryParse($this.ini_contents[$section][$entry].value,[ref]$null) 
                    }
                    [ValueType]::Bool {
                        return ( $this.ini_contents[$section][$entry].value.Equals("false") -or $this.ini_contents[$section][$entry].value.Equals("true") )
                    }
                    default {#[ValueType]::string
                        return $true 
                    }
                }
            }
        }
        return $false
    }

    [bool] GetValueBool( [string]$section, [string]$entry ) {
        return $this.GetValueBool($section, $entry, $false)
    }

    [bool] GetValueBool( [string]$section, [string]$entry, [bool]$default ) {
        if( $null -ne $this.ini_contents[$section] ){
            if( $null -ne $this.ini_contents[$section][$entry] ){
                return $this.ini_contents[$section][$entry].value.Equals("true")
            }
        }

        Write-Host "[IniFileManager::GetValueBool] Forbidden route : Undefined section/entry (Section:'${section}'/Entry:'${entry}')"

        return $default
    }

    [string] GetValueString( [string]$section, [string]$entry) {
        return $this.GetValueString($section, $entry, "")
    }

    [string] GetValueString( [string]$section, [string]$entry, [string]$default ) {
        if( $null -ne $this.ini_contents[$section] ){
            if( $null -ne $this.ini_contents[$section][$entry] ){
                return $this.ini_contents[$section][$entry].value
            }
        }

        Write-Host "[IniFileManager::GetValueString] Forbidden route : Undefined section/entry (Section:'${section}'/Entry:'${entry}')"

        return $default
    }

    [long] GetValueLong( [string]$section, [string]$entry) {
        return $this.GetValueLong($section,$entry,0)
    }

    [long] GetValueLong( [string]$section, [string]$entry, [long]$default ) {
        if( $null -ne $this.ini_contents[$section] ){
            if( $null -ne $this.ini_contents[$section][$entry] ){
                [long] $ret=0
                [string] $value = $this.ini_contents[$section][$entry].value
                if( ![long]::TryParse($value,[ref]$ret) ){
                    Write-Host "[IniFileManager::GetValueLong] Forbidden route : Cannot convert '[${section}] ${entry}=${value}'"
                    $ret = 0
                }
                return $ret
            }
        }
        return $default
    }

    [double] GetValueDouble( [string]$section, [string]$entry ) {
        return $this.GetValueDouble( $section, $entry, 0.0 )
    }

    [double] GetValueDouble([string]$section, [string]$entry, [double]$default ) {
        if( $null -ne $this.ini_contents[$section] ){
            if( $null -ne $this.ini_contents[$section][$entry] ){
                [double] $ret=0
                [string] $value = $this.ini_contents[$section][$entry].value
                if( ![double]::TryParse($value,[ref]$ret) ){
                    Write-Host "[IniFileManager::GetValueDouble] Forbidden route : Cannot convert '[${section}] ${entry}=${value}'"
                    $ret = 0
                }
                return $ret
            }
        }

        Write-Host "[IniFileManager::GetValueDouble] Forbidden route : Undefined section/entry (Section:'${section}'/Entry:'${entry}')"

        return $default
    }

    [string[]] GetValueStringArray([string]$section, [string]$entry ) {
        return $this.GetValueStringArray( $section, $entry, ",", @() )
    }

    [string[]] GetValueStringArray([string]$section, [string]$entry, [string]$delimiter ) {
        return $this.GetValueStringArray( $section, $entry, $delimiter, @() )
    }

    [string[]] GetValueStringArray([string]$section, [string]$entry, [string]$delimiter, [string[]]$default ) {
        if( $null -ne $this.ini_contents[$section] ){
            if( $null -ne $this.ini_contents[$section][$entry] ){
                if( [string]::IsNullOrEmpty($this.ini_contents[$section][$entry].value) ){
                    return [string[]]@()
                } else {
                    return $this.ini_contents[$section][$entry].value.Split($delimiter)
                }
            }
        }

        Write-Host "[IniFileManager::GetValueStringArray] Forbidden route : Undefined section/entry (Section:'${section}'/Entry:'${entry}')"

        return $default
    }

    [System.Collections.Specialized.OrderedDictionary] GetEntryItems( [string]$section ) {

        [System.Collections.Specialized.OrderedDictionary]$ret=@{}

        if( $null -ne $this.ini_contents[$section] ){
            foreach( $entry in $this.ini_contents[$section].Keys ){
                $ret.Add($entry,$this.ini_contents[$section][$entry].value)
            }
        } else {
            Write-Host "[IniFileManager::GetEntryItems] Forbidden route : Undefined section (Section:'${section}')"
        }

        return $ret
    }

    Update([string]$section, [string]$entry, [bool]$value ) {
        if($value){
            $this.Update( $section, $entry, "true" )
        } else {
            $this.Update( $section, $entry, "false" )
        }
    }

    Update([string]$section, [string]$entry, [string]$value ) {

        if( $null -eq $this.ini_contents[$section] ){
            $this.ini_contents[$section] += [System.Collections.Specialized.OrderedDictionary]@{}
        }

        if( $null -eq $this.ini_contents[$section][$entry] ){
            # $this.ini_contents[$section].Add($entry,[Entry]::new($value))
            $this.ini_contents[$section].Insert(0,$entry,[Entry]::new($value))
        } else {
            $this.ini_contents[$section][$entry].value = $value
        }
    }

    UpdateComment([string]$section, [string]$entry, [string]$comment ) {
        if( $null -ne $this.ini_contents[$section] ){
            if( $null -ne $this.ini_contents[$section][$entry] ){
                $this.ini_contents[$section][$entry].comment = $comment
                return
            }
        }

        Write-Host "[IniFileManager::UpdateComment] Forbidden route : Undefined section/entry (Section:'${section}'/Entry:'${entry}')"
    }

    RemoveSection( [string]$section ) {
        if( $null -ne $this.ini_contents[$section] ){
            $this.ini_contents.Remove( $section )
        } else {
            Write-Host "[IniFileManager::RemoveSection] Forbidden route : Undefined section (Section:'${section}')"
        }
    }

    RemoveEntry( [string]$section, [string]$entry ) {
        if( $null -ne $this.ini_contents[$section] ){
            if( $null -ne $this.ini_contents[$section][$entry] ){
                $this.ini_contents[$section].Remove( $entry )
            }
        }

        Write-Host "[IniFileManager::RemoveEntry] Forbidden route : Undefined section/entry (Section:'${section}'/Entry:'${entry}')"
    }

    Write() {

        $temp_ini_file = $this.ini_file_path + ".tmp"

        if( Test-Path "${temp_ini_file}" ){
            Remove-Item "${temp_ini_file}"
        }

        $stream_writer = New-Object System.IO.StreamWriter($temp_ini_file,[System.Text.Encoding]::GetEncoding("utf-8"))

        [bool]$first_line=$true
        foreach( $section in $this.ini_contents.Keys ){
            if( -Not($section.Equals("NoSection") ) ){
                if($first_line){
                    $stream_writer.Write("[$section]`n")
                } else {
                    $stream_writer.Write("`n[$section]`n")
                }
            }

            $first_line=$false

            [long]$max_entry_length = 0
            foreach( $entry in $this.ini_contents[$section].Keys ){
                $line = ($entry+"="+$this.ini_contents[$section][$entry].value)
                $full_width_count = [regex]::Matches($line, "[^\x00-\xff]").Count
                $half_width_count = $line.Length - $full_width_count
                $data_length = ( $full_width_count *2 ) + $half_width_count
                if( $max_entry_length -lt $data_length ){
                    $max_entry_length = $data_length
                }
            }

            foreach( $entry in $this.ini_contents[$section].Keys ){

                $value   = $this.ini_contents[$section][$entry].value
                $comment = $this.ini_contents[$section][$entry].comment

                if( $entry -cmatch "^=.*=$" ){
                    if( $entry -cmatch "^=Comment[1-9][0-9]*=$" ){
                        $stream_writer.WriteLine("${value}")
                    }
                } else {
                    if( [string]::IsNullOrEmpty($comment) ){
                        $stream_writer.WriteLine("${entry}=${value}")
                    } else {
                        $line = ($entry+"="+$this.ini_contents[$section][$entry].value)
                        $full_width_count = [regex]::Matches($line, "[^\x00-\xff]").Count
                        $half_width_count = $line.Length - $full_width_count
                        $data_length = ( $full_width_count * 2 ) + $half_width_count
                        $stream_writer.WriteLine( $line + " "*($max_entry_length-$data_length) + " ;" + $comment )
                    }
                }
            }
        }

        $stream_writer.Close()

        $updated_ini_file_path=$this.ini_file_path
        if( Test-Path "${updated_ini_file_path}" ){
            Remove-Item "${updated_ini_file_path}"
        }
        Move-Item "${temp_ini_file}" "${updated_ini_file_path}"
    }
}

class Log {

    [string]$script_name
    [string]$log_file_path

    Log(
        [string]$script_name,
        [string]$log_file_path = $null
    ){
        $this.script_name = $script_name
        $this.log_file_path = $log_file_path

        if( ![string]::IsNullOrEmpty($log_file_path) ){
            $log_file_dir = ( Split-Path -Parent ${log_file_path} )
            if( -Not(Test-Path (${log_file_dir}) ) ){
                New-Item -Type Directory ${log_file_dir} > $null
            }
        }
    }

    Write(
        [OutputLogType]$type,
        [string]$function,
        [string]$message
    ){
        [string]$output = ""

        if( ![string]::IsNullOrEmpty($this.script_name) -and ![string]::IsNullOrEmpty($function) ){
            $output += [string]::Format("[{0}:{1}] ", $this.script_name, $function )
        } elseif( ![string]::IsNullOrEmpty($this.script_name)){
            $output += [string]::Format("[{0}] ", $this.script_name )
        } else {
            $output += [string]::Format("[{0}] ", $function )
        }

        $output += [string]::Format("{0} : ", ((Get-Date).ToString("yyyy/MM/dd HH:mm:ss")) )

        if( $type -ne [OutputLogType]::NoType ){
            $output += [string]::Format("{0} : ", $type )
        }

        $output += [string]::Format("{0} ", $message )

        Write-Host $output
        if(![string]::IsNullOrEmpty($this.log_file_path)){
            Add-Content -Path $this.log_file_path -Value $output -Encoding utf8
        }
    }
}

###################
# Function
###################
function ConvertPath([string]$filepath){

    [string]$directory = ( Split-Path -Parent $filepath ) 
    [string]$filename = ( Split-Path -Leaf $filepath ) 

    [string]$modified_filename = $filename.Replace('[','`[').Replace(']','`]')

    return $directory+"\"+$modified_filename
}

function GetDurationString([TimeSpan]$duration){
    [string]$duration_string=""

    if($duration.TotalSeconds -lt 60){
        $duration_string=($duration.TotalMilliseconds/1000).ToString("0.00")+" sec."
    } else {
        $duration_string+=($duration.TotalSeconds.ToString("0")/60).ToString("0")+" min. "
        $duration_string+=($duration.TotalSeconds.ToString("0")%60).ToString("0")+" sec. "
    }

    return $duration_string
}

function GetFileSizeString([uint64]$size){
    [string]$size_string=""

    if( $size -lt 1024 ){
        $size_string = $size.ToString() + "Byte"
    } elseif( $size -lt 1024 * 1024 ){
        $size_string = ( ( [Math]::Truncate( ( $size / 1024 ) * 100 ) / 100 ) ).ToString() + "KB"
    } elseif( $size -lt 1024 * 1024 * 1024 ){
        $size_string = ( ( [Math]::Truncate( ( $size / 1024 / 1024 ) * 100 ) / 100 ) ).ToString() + "MB"
    } else {
        $size_string = ( ( [Math]::Truncate( ( $size / 1024 / 1024 / 1024 ) * 100 ) / 100 ) ).ToString() + "GB"
    }

    return $size_string
}

function Shutdown([log]$log) {
    [int]$wait_time = 180

    for( [int]$i=$wait_time;$i -gt 0;$i-=10){
        $log.Write( [OutputLogType]::Info, "", [string]::Format("${i}秒後にシャットダウンします", ((Get-Date).ToString("yyyy/MM/dd HH:mm:ss")) ) )
        Start-Sleep -s 10
    }
    $log.Write( [OutputLogType]::Info, "", [string]::Format("シャットダウンします", ((Get-Date).ToString("yyyy/MM/dd HH:mm:ss")) ) )
    shutdown.exe /s -t 5
}

function CheckAlreadyRunning {
    [int]$number_of_process = (Get-Process -Name powershell | Where-Object -FilterScript {$_.Id -ne $PID}).Count

    return ($number_of_process -gt 1)
}

function DeleteFolders {
    Param(
        [string]$source,
        [bool]$quiet,
        [Log]$log
    )

    $folders = (Get-ChildItem $source -Directory -Recurse -ErrorAction SilentlyContinue) | Sort-Object -Descending

    $delete_folders = @()
    foreach($folder in $folders){
        try{
            if( $folder.GetFileSystemInfos().Count -eq 0 ) {
                $delete_folders += $folder
            }
        } catch {
            # do nothing
        }
    }

    if( $delete_folders.Count -gt 0 ){
        [string]$console_output = "以下の空フォルダを削除します"
        foreach( $delete_folder in $delete_folders ){
            $console_output += ( "`n  - " + $delete_folder.FullName )
        }
        $log.Write( [OutputLogType]::Info, "DeleteFolders", $console_output )

        if( !$quiet ){
            $console_output = "よろしいですか?([y]es/[N]o)"

            $response = ( Read-Host $console_output ).Trim()
            switch($response){
                "y" {}
                default { return; }
            }
        }
    }

    foreach($delete_folder in $delete_folders){
        Remove-Item  $delete_folder.FullName -Recurse -Force
    }
}

function DeleteUnnecessaryFiles {
    Param(
        [string]$source,
        [bool]$quiet,
        [Log]$log
    )

    $files = Get-ChildItem "$source" -Recurse -include '._*' -Force -ErrorActio SilentlyContinue

    if( $files.Count -gt 0 ){
        [string]$console_output = "以下の不要ファイルを削除します。"
        foreach( $file in $files ){
            $console_output += ( "`n  - " + $file.FullName )
        }
        $log.Write( [OutputLogType]::Info, "DeleteUnnecessaryFiles", $console_output )

        if( !$quiet ){
            $console_output = "よろしいですか?([y]es/[N]o)"

            $response = ( Read-Host $console_output ).Trim()
            switch($response){
                "y" {}
                default { return; }
            }
        }
    }

    foreach($file in $files){
        $log.Write( [OutputLogType]::Info, "DeleteUnnecessaryFiles", ( "Delete '"+$file.Name+"'") )
        Remove-Item -Path (ConvertPath($file.FullName)) -Force
    }
}

function CopyOneFile {
    Param(
        [string]$source,
        [string]$destination,
        [Log]$log
    )

    [string]$source_file_name = ( Split-Path -Leaf $source ) 
    [string]$source_file_path = ConvertPath($source)

    [string]$destination_directory = Split-Path -Parent $destination
    if( -Not(Test-Path(${destination_directory}))){
        New-Item -Type Directory ($destination_directory) > $null
    }

    if( Test-Path(ConvertPath(${destination})) ){
        Remove-Item -Path (ConvertPath(${destination}))
    }

    [UInt64]$source_size = (Get-Item $source_file_path).Length
    $log.Write( [OutputLogType]::Info, "CopyOneFile", ( "Copy `""+$source_file_name+"`" ("+( GetFileSizeString( $source_size ) )+")..." ) )

    [DateTime]$start_time = [DateTime]::Now
    if( $debug ){ $log.Write( [OutputLogType]::Debug, "CopyOneFile", "Copy-Item -Path $source_file_path -Destination $destination_directory -Force" ) }

    [string]$temp_destination_path = ("$destination_directory"+"\"+$source_file_name+"_")
    if( Test-Path(ConvertPath(${temp_destination_path})) ){
        Remove-Item -Path (${temp_destination_path})
    }

    try{
        Copy-Item -Path "$source_file_path" -Destination ${temp_destination_path} -Force
    } catch {
        $log.Write( [OutputLogType]::Error, "CopyOneFile", ( $source_file_name + "のコピーに失敗しました" ) )
    }

    if( Test-Path(ConvertPath(${destination})) ){
        Remove-Item -Path (ConvertPath(${destination}))
    }

    if( Test-Path(ConvertPath(${temp_destination_path}) ) ){
        Move-Item -Path  (ConvertPath(${temp_destination_path})) -Destination ${destination} -Force

        [TimeSpan]$duration=[DateTime]::Now-$start_time
        $log.Write( [OutputLogType]::Info, "CopyOneFile",( "コピー処理時間 : " + (GetDurationString($duration)) ) )
        
        [string]$destination_file_path = ConvertPath($destination)
        if(-Not(Test-Path($destination_file_path))){
            $log.Write( [OutputLogType]::Error, "CopyOneFile", ( "コピー失敗 : " + $source_file_name  ) )
        }
    } else {
        $log.Write( [OutputLogType]::Error, "CopyOneFile", ( "コピー失敗 : " + $source_file_name ) )
    }
}
Using Module ".\Utils.psm1"

Param(
    [string]$log_file = $null,
    [string]$source,
    [string]$destination,
    [int]$day,
    [switch]$sync,
    [switch]$quiet,
    [switch]$shutdown,
    [switch]$help
)

#########################
# Parameters
#########################
[string]$script:script_path = Split-Path -Parent $PSCommandPath
[string]$script:script_name = Split-Path -Leaf $PSCommandPath

[Log]$script:log=[Log]::new($script:script_name,$log_file)

[string]$script:source_root      = $source
[string]$script:destination_root = $destination
[string]$script:duration_day     = $day

[bool]$script:quiet_flag=$quiet

[bool]$debug=$false
if($debug){
    # [string]$script:source_root      = "${script:script_path}\files\m2ts"
    # [string]$script:destination_root = "${script:script_path}\files\m2ts_backup"
    [string]$script:source_root      = "\\bluesky-nas\tv_program\converted_files\delete"
    [string]$script:destination_root = "E:\NAS_Backup\tv_program\converted_files\delete"

    if( -Not(Test-Path "${script:destination_root}") ){
        New-Item -Type Directory ${script:destination_root} > $null
    }

    $debug = $true
    $script:quiet_flag=$true
    $script:duration_day = 1
}

#########################
# Help
#########################
if( $help ) {
    Write-Host "NAME"
    Write-Host ""
    Write-Host "   $script:script_name - ファイルをバックアップする"
    Write-Host ""
    Write-Host "SYPNOSYS"
    Write-Host ""
    Write-Host "   $script:script_name -log_file <file path>"
    Write-Host "                       -source <directory path>"
    Write-Host "                       -destination <directory path>"
    Write-Host "                       [-quiet] [-help]"
    Write-Host ""
    Write-Host "DESCRIPTION"
    Write-Host ""
    Write-Host "   -source"
    Write-Host "        バックアップ元のファイルが格納されているディレクトリを指定する"
    Write-Host ""
    Write-Host "   -destination"
    Write-Host "        バックアップ先のディレクトリを指定する"
    Write-Host ""
    Write-Host "   -day <day>"
    Write-Host "        最終更新日から<day>日以内の数以内のファイルをコピーする"
    Write-Host ""
    Write-Host "   -sync"
    Write-Host "        <source>に無いファイルを<destination>から削除する"
    Write-Host ""
    Write-Host "   -shutdown"
    Write-Host "        バックアップ終了後、${delay_shutdown}秒後にPCをシャットダウンする"
    Write-Host ""
    Write-Host "   -help"
    Write-Host "        ヘルプを表示する"
    Write-Host ""
    exit
}

#########################
# Parameter check
#########################
if( [string]::IsNullOrEmpty("${script:source_root}") -or -Not(Test-Path "${script:source_root}") ){
    $script:log.Write( [OutputLogType]::Error, "ParameterCheck", ( "バックアップ元フォルダがありません '"+${script:source_root}+"'") )
    exit
}

if( [string]::IsNullOrEmpty("${script:destination_root}") -or -Not(Test-Path "${script:destination_root}") ){
    $script:log.Write( [OutputLogType]::Error, "ParameterCheck", ( "バックアップ先フォルダがありません '"+${script:destination_root}+"'") )
    exit
}

###################
# Function
###################
function BackupFiles_Day {
    Param(
        [string]$source,
        [string]$destination,
        [int]$day
    )

    $files = Get-ChildItem "$source" -File -Recurse

    $copy_files = @()
    foreach( $file in $files ){
        if( !$file.Name.StartsWith("_") -and !$file.Name.StartsWith(".") ){
            [timespan]$time_diff = [DateTime]::Now - $file.LastWriteTime
            [string]$destination_path=ConvertPath($file.FullName.replace($source,$destination))
            if( ( $day -eq 0 ) -or ( $time_diff.TotalDays -le $day ) ){
                if( -Not(Test-Path($destination_path)) -or ( $file.Length -ne (Get-ItemProperty $destination_path).Length) ){
                    $copy_files += $file
                }
            }
        }
    }

    if( $copy_files.Count -le 0 ){
        if( ${day} -ne 0 ){
            [string]$console_output = "最終書込日時から${day}日以内のファイルはありませんでした"
        } else {
            [string]$console_output = "バックアップ対象ファイルはありませんでした"
        }
        $script:log.Write( [OutputLogType]::Info, "BackupFiles_Day", $console_output )
    } else {
        if( ${day} -ne 0 ){
            [string]$console_output = "最終書込日時から${day}日以内の以下のファイルをコピーします"
        } else {
            [string]$console_output = "以下のファイルをコピーします"
        }
        foreach( $delete_file in $copy_files ){
            $console_output += ( "`n  - " + (Split-Path -Leaf $delete_file.Name) `
                            + " (最終書込日時:" +  $delete_file.LastWriteTime.ToString("yyyy/MM/dd") + " / " `
                            + "ファイルサイズ:" +  ( GetFileSizeString( $delete_file.Length ) ) + " )" )
        }
        $script:log.Write( [OutputLogType]::Info, "BackupFiles_Day", $console_output )

        if( !$script:quiet_flag ){
            $console_output = "よろしいですか?([y]es/[N]o)"

            $response = ( Read-Host $console_output ).Trim()
            switch($response){
                "y" {}
                default { return; }
            }
        }
    }

    foreach( $copy_file in $copy_files ){
        $copy_destination = $copy_file.FullName.replace($source,$destination)
        CopyOneFile -source $copy_file.FullName -destination $copy_destination -Log $script:log
    }
}

function Synchronize {
    Param(
        [string]$source,
        [string]$destination
    )
    $files = Get-ChildItem "$destination" -Recurse

    $delete_files = @()
    foreach( $file in $files ){
        if( !$file.Name.StartsWith("_") -and !$file.Name.StartsWith(".") ){
            [string]$source_path=ConvertPath($file.FullName.replace($destination,$source))
            if( -Not(Test-Path($source_path)) ){
                $delete_files += $file
            }
        }
    }

    if( $delete_files.Count -le 0 ){
        [string]$console_output = "同期による削除対象ファイルはありませんでした"
        $script:log.Write( [OutputLogType]::Info, "DeleteFiles_Day", $console_output )
    } else {
        [string]$console_output = "フォルダを同期するため以下のファイルを削除します"
        foreach( $delete_file in $delete_files ){
            $console_output += ( "`n  - " + (Split-Path -Leaf $delete_file.Name) )
            if($delete_file.PSIsContainer){
                $console_output += "(フォルダ)"
            } else {
                $console_output += ( "`(最終書込日時:" +  $delete_file.LastWriteTime.ToString("yyyy/MM/dd") + " / " `
                                    + "ファイルサイズ:" + ( GetFileSizeString( $delete_file.Length ) ) + " )" )
            }
        }
        $script:log.Write( [OutputLogType]::Info, "DeleteFiles_Day", $console_output )

        if( !$script:quiet_flag ){
            $console_output = "よろしいですか?([y]es/[N]o)"

            $response = ( Read-Host $console_output ).Trim()
            switch($response){
                "y" {}
                default { return; }
            }
        }
    }

    foreach( $delete_file in $delete_files ){
        [string]$converted_path=ConvertPath($delete_file.FullName)
        if( Test-Path($converted_path) ) {
            Remove-Item -Path $converted_path -Force -Recurse
        }
    }
}

#########################
# Main
#########################
$script:log.Write( [OutputLogType]::Info, "", "【バックアップを開始します】" )
$script:log.Write( [OutputLogType]::Info, "", "  バックアップ元フォルダ:${script:source_root}" )
$script:log.Write( [OutputLogType]::Info, "", "  バックアップ先フォルダ:${script:destination_root}" )

if( !$debug -and ( CheckAlreadyRunning ) ){
    $script:log.Write( [OutputLogType]::Info, "", "変換・コピー処理実行中のため中断します" )
    $script:log.Write( [OutputLogType]::Info, "", "【バックアップを終了しました】" )
    exit
}

DeleteFolders -source "$script:destination_root" -Log $script:log -quiet $script:quiet_flag
DeleteUnnecessaryFiles -source "$script:destination_root" -Log $script:log -quiet $script:quiet_flag

[DateTime]$start_time = [DateTime]::Now
BackupFiles_Day -source "$script:source_root" -destination "$script:destination_root" -day ${script:duration_day}
[TimeSpan]$duration=[DateTime]::Now-$start_time
$script:log.Write( [OutputLogType]::Info, "Main", ( "バックアップ処理時間 : " + (GetDurationString($duration)) ) )

if( $sync ){
    Synchronize -source "$script:source_root" -destination "$script:destination_root"
}

#shutdown
if( $shutdown ){
    Shutdown($script:log)
}

$script:log.Write( [OutputLogType]::Info, "", "【バックアップを終了しました】" )
Using Module ".\Utils.psm1"

Param(
    [string]$log_file,
    [string]$source,
    [string]$destination,
    [switch]$quiet,
    [switch]$shutdown,
    [switch]$help
)

#########################
# Parameters
#########################
[string]$script:script_path = Split-Path -Parent $PSCommandPath
[string]$script:script_name = Split-Path -Leaf $PSCommandPath

if([string]::IsNullOrEmpty($log_file)){
    $log_file_path = $script:script_path+((Get-Date).ToString("\\yyyyMMdd_HHmmss_"))+$script_name.Replace(".ps1",".log")
    [Log]$script:log=[Log]::new($script:script_name,$log_file_path)
} else {
    [Log]$script:log=[Log]::new($script:script_name,$log_file)
}

[string]$script:source_root      = $source
[string]$script:destination_root = $destination

[bool]$script:quiet_flag=$quiet

[string]$script:ini_file_path     = "${script:script_path}\CopyM2ts.ini"
[string]$script:section_copy_m2ts = $script:script_name

[bool]$debug=$false
if($debug){
    #[string]$script:source_root      = "${script:script_path}\files\m2ts"
    #[string]$script:destination_root = "${script:script_path}\files\m2ts_backup"
    [string]$script:source_root      = "\\192.168.1.6\tv-recorder\recorded_files\delete"
    [string]$script:destination_root = "\\bluesky-nas\tv_program_backup\m2ts\delete"
    [bool]$script:quiet_flag         = $true

    if( -Not(Test-Path "${script:destination_root}") ){
        New-Item -Type Directory ${script:destination_root} > $null
    }
}

[int]$recording_detection_duration = 1*60  #Unit:sec

#########################
# Help
#########################
if( $help ) {
    Write-Host "NAME"
    Write-Host ""
    Write-Host "   $script:script_name - M2TSファイルをコピーする"
    Write-Host ""
    Write-Host "SYPNOSYS"
    Write-Host ""
    Write-Host "   $script:script_name -log_file <file path>"
    Write-Host "                       -source <directory path>"
    Write-Host "                       -destination <directory path>"
    Write-Host "                       [-quiet] [-help]"
    Write-Host ""
    Write-Host "DESCRIPTION"
    Write-Host ""
    Write-Host "   -source"
    Write-Host "        M2TSファイルが格納されているディレクトリを指定する"
    Write-Host ""
    Write-Host "   -destination"
    Write-Host "        M2TSファイルのコピー先のディレクトリを指定する"
    Write-Host ""
    Write-Host "   -quiet"
    Write-Host "        処理開始前の確認をスキップする"
    Write-Host ""
    Write-Host "   -shutdown"
    Write-Host "        変換終了後、${delay_shutdown}秒後にPCをシャットダウンする"
    Write-Host ""
    Write-Host "   -help"
    Write-Host "        ヘルプを表示する"
    Write-Host ""
    exit
}

###################
# Function
###################
function CopyM2ts {
    Param(
        [string]$source,
        [string]$destination
    )

    [IniFileManager]$ini_file = [IniFileManager]::new($script:ini_file_path)

    $copy_files = @()

    $src_m2ts_files = Get-ChildItem  "$source" -Recurse -Filter *.m2ts -Exclude "lost+found"
    $dest_m2ts_files = Get-ChildItem "$destination" -Recurse -Filter *.m2ts

    foreach( $src_m2ts_file in $src_m2ts_files ){

        if( !$src_m2ts_file.Name.StartsWith(".") -and !$src_m2ts_file.Name.StartsWith("_") ){

            [bool]$copy_flag = $false

            [string]$destination_file_path = $src_m2ts_file.FullName.Replace($source,$destination)
            $dest_m2ts_file = CopyM2ts_GetDestFile -file_path $destination_file_path -dest_files $dest_m2ts_files
    
            if( $null -eq $dest_m2ts_file ){
                $copy_flag = $true
            } elseif( $src_m2ts_file.Length -ne $dest_m2ts_file.Length ){
                $copy_flag = $true
            } else {
                [string]$temp_file_path = ( Split-Path -Parent ${destination_file_path}) + "\TEMP_" +(Split-Path -Leaf ${destination_file_path})
                $temp_file_path = CopyM2ts_GetDestFile -file_path $temp_file_path -dest_files $dest_m2ts_files
                if( -Not([string]::IsNullOrEmpty($temp_file_path.FullName) ) ){
                    $copy_flag = $true
                }
            }
        }

        if($copy_flag){
            [timespan]$time_diff = [DateTime]::Now - $src_m2ts_file.LastWriteTime
            if( $time_diff.TotalSeconds -lt $recording_detection_duration ){
                $script:log.Write( [OutputLogType]::Info, "CopyM2ts", ( "スキップ(録画中) .................... " + $src_m2ts_file.Name ) )
                $copy_flag = $false
            } elseif( $src_m2ts_file.Length -eq $ini_file.GetValueLong( $script:section_copy_m2ts, $src_m2ts_file.Name, -1 ) ){ `
                if( $debug ){ $script:log.Write( [OutputLogType]::Info, "CopyM2ts", ( "スキップ(コピー済み) .... " + $src_m2ts_file.Name ) ) }
                $copy_flag = $false
            } else {

                [string]$temp_file_path = (Split-Path -Parent ${destination_file_path}) + "\TEMP_" +(Split-Path -Leaf ${destination_file_path})
                if( Test-Path( $temp_file_path) ){
                    Remove-Item -Path ${temp_file_path}
                }

                $copy_files += $src_m2ts_file
            }
        }
    }

    if( $copy_files.Count -gt 0 ) {
        [string]$console_output = "以下のファイルをコピーします。"
        foreach( $copy_file in $copy_files ){
            $console_output += ( "`n  - ${copy_file}" )
        }
        $console_output += "`n  コピー先:$destination"
        $script:log.Write( [OutputLogType]::Info, "CopyM2ts", $console_output )

        if( !$script:quiet_flag ){
            $response = ( Read-Host "よろしいですか?([Y]es/[n]o)" )
            switch($response){
                "" {}
                "y" {}
                default { return; }
            }
        }
    }

    foreach( $copy_file in $copy_files ){

        $destination_file = $copy_file.FullName.Replace($source,$destination)
        CopyM2ts_OneFile -source $copy_file.FullName -destination $destination_file

        if( Test-Path(ConvertPath($destination_file)) ){
            $m2ts_file_copy = (Get-Item -Path ( ConvertPath($copy_file.FullName) ) )
            $ini_file.Update( $script:section_copy_m2ts, $m2ts_file_copy.Name, $m2ts_file_copy.Length )
            $ini_file.Write()
        }
    }
}

function CopyM2ts_GetDestFile {
    Param(
        [string]$file_path,
        [System.Array]$dest_files
    )

    foreach( $dest_file in $dest_files ){
        if( $file_path.Equals($dest_file.FullName) ){
            return $dest_file
        }
    }

    return $null
}

function CopyM2ts_OneFile {
    Param(
        [string]$source,
        [string]$destination
    )

    [string]$source_file_name = ( Split-Path -Leaf $source ) 
    [string]$source_file_path = ConvertPath($source)

    [string]$destination_directory = Split-Path -Parent $destination
    if( -Not(Test-Path(${destination_directory}))){
        New-Item -Type Directory ($destination_directory) > $null
    }

    if( Test-Path(ConvertPath(${destination})) ){
        Remove-Item -Path (ConvertPath(${destination}))
    }

    [UInt64]$source_size = (Get-Item $source_file_path).Length
    $script:log.Write( [OutputLogType]::Info, "CopyM2ts_OneFile", ( "Copy `""+$source_file_name+"`" ("+($source_size/1000/1000/1000).ToString("0.00")+"GB) ..." ) )

    [DateTime]$start_time = [DateTime]::Now
    if( $debug ){ $script:log.Write( [OutputLogType]::Debug, "CopyM2ts_OneFile", "Copy-Item -Path $source_file_path -Destination $destination_directory -Force" ) }

    [string]$temp_destination_path = ("$destination_directory"+"\TEMP_"+$source_file_name)
    if( Test-Path(ConvertPath(${temp_destination_path})) ){
        Remove-Item -Path (${temp_destination_path})
    }

    Copy-Item -Path "$source_file_path" -Destination ${temp_destination_path} -Force

    if( Test-Path(ConvertPath(${destination})) ){
        Remove-Item -Path (ConvertPath(${destination}))
    }

    if( Test-Path(ConvertPath(${temp_destination_path}) ) ){
        Move-Item -Path  (ConvertPath(${temp_destination_path})) -Destination ${destination} -Force

        [TimeSpan]$duration=[DateTime]::Now-$start_time
        $script:log.Write( [OutputLogType]::Info, "CopyM2ts_OneFile",( "コピー処理時間 : " + (GetDurationString($duration)) ) )
        
        [string]$destination_file_path = ConvertPath($destination)
        if(-Not(Test-Path($destination_file_path))){
            $script:log.Write( [OutputLogType]::Error, "CopyM2ts_OneFile", ( "コピー失敗 : " + $source_file_name  ) )
        }
    } else {
        $script:log.Write( [OutputLogType]::Error, "CopyM2ts_OneFile", ( "コピー失敗 : " + $source_file_name ) )
    }
}

#########################
# Parameter check
#########################
if( [string]::IsNullOrEmpty("${script:source_root}") -or -Not(Test-Path "${script:source_root}") ){
    $script:log.Write( [OutputLogType]::Error, "ParameterCheck", ( "コピー元フォルダがありません '"+${script:source_root}+"'" ) )
    exit
}

if( [string]::IsNullOrEmpty("${script:destination_root}") -or -Not(Test-Path "${script:destination_root}") ){
    $script:log.Write( [OutputLogType]::Error, "ParameterCheck", ( "コピー先フォルダがありません '"+${script:destination_root}+"'" ) )
    exit
}

#########################
# Main
#########################
$script:log.Write( [OutputLogType]::Info, "", "【M2TSファイルのコピーを開始します】" )
$script:log.Write( [OutputLogType]::Info, "", "  コピー元フォルダ:${script:source_root}" )
$script:log.Write( [OutputLogType]::Info, "", "  コピー先フォルダ:${script:destination_root}" )

if( CheckAlreadyRunning ){
    $script:log.Write( [OutputLogType]::Info, "", "変換・コピー処理実行中のため中断します" )
    $script:log.Write( [OutputLogType]::Info, "", "【M2TSファイルのコピーを終了しました】" )
    exit
}

# Backup m2ts
if( -Not(Test-Path "${script:destination_root}") ){
    New-Item -Type Directory ${script:destination_root} > $null
}

# Copy M2TS
CopyM2ts -source ${script:source_root} -destination ${script:destination_root}

#shutdown
if( $shutdown ){
    Shutdown($script:log)
}

$script:log.Write( [OutputLogType]::Info, "", "【M2TSファイルのコピーを終了しました】" )
Using Module ".\Utils.psm1"

Param(
    [string]$log_file,
    [string]$source,
    [string]$destination,
    [switch]$nvenc,
    [switch]$quiet,
    [switch]$shutdown,
    [switch]$help
)

#########################
# Parameters
#########################
[string]$script:script_path = Split-Path -Parent $PSCommandPath
[string]$script:script_name = Split-Path -Leaf $PSCommandPath

if([string]::IsNullOrEmpty($log_file)){
    $log_file_path = $script:script_path+((Get-Date).ToString("\\yyyyMMdd_HHmmss_"))+$script_name.Replace(".ps1",".log")
    [Log]$script:log=[Log]::new($script:script_name,$log_file_path)
} else {
    [Log]$script:log=[Log]::new($script:script_name,$log_file)
}

[string]$script:source_root      = $source
[string]$script:destination_root = $destination

[string]$script:ini_file_path    = "${script:script_path}\Convert.ini"
[string]$script:section_convert  = $script:script_name

[bool]$script:quiet_flag=$quiet
[bool]$script:nvenc_flag=$nvenc

[bool]$debug=$false
if($debug){
    # [string]$script:source_root      = "${script:script_path}\files\m2ts"
    # [string]$script:destination_root = "${script:script_path}\files\convert"
    [string]$script:source_root      = "\\bluesky-nas\tv_program_backup\m2ts"
    [string]$script:destination_root = "\\bluesky-nas\tv_program\converted_files"
    $script:quiet_flag = $true

    if( -Not(Test-Path "${script:source_root}") ){
        New-Item -Type Directory ${script:destination_root} > $null
    }
}

[string]$script:amatsukaze_directory = $script:script_path.Replace("\AutomationBatch","")
if( [string]::IsNullOrEmpty($script:amatsukaze_directory) ){
    $script:amatsukaze_directory = ""
}
[string]$script:amatsukaze_cli_exe = "$script:amatsukaze_directory\exe_files\AmatsukazeCLI.exe"
[string]$script:work_directory = "$script:amatsukaze_directory\temp"

[string]$script:convert_to_mp4_commandline = ""
$script:convert_to_mp4_commandline += " -s 1024"
$script:convert_to_mp4_commandline += " --drcs `"$script:amatsukaze_directory\drcs\drcs_map.txt`""
$script:convert_to_mp4_commandline += " -w `"$script:work_directory`""
$script:convert_to_mp4_commandline += " --chapter-exe `"$script:amatsukaze_directory\exe_files\chapter_exe.exe`""
$script:convert_to_mp4_commandline += " --jls `"$script:amatsukaze_directory\exe_files\join_logo_scp.exe`""
$script:convert_to_mp4_commandline += " --cmoutmask 2"
if( $script:nvenc_flag ){
    $script:convert_to_mp4_commandline += " -et NVEnc"
    $script:convert_to_mp4_commandline += " -e `"$script:amatsukaze_directory\exe_files\NVEncC\NVEncC64.exe`""
} else {
    $script:convert_to_mp4_commandline += " -et QSVEnc"
    $script:convert_to_mp4_commandline += " -e `"$script:amatsukaze_directory\exe_files\QSVEncC\QSVEncC64.exe`""
}
$script:convert_to_mp4_commandline += " -j `"$script:work_directory\2-enc.json`""
$script:convert_to_mp4_commandline += " --mp4box `"$script:amatsukaze_directory\exe_files\mp4box.exe`""
$script:convert_to_mp4_commandline += " -t `"$script:amatsukaze_directory\exe_files\timelineeditor.exe`""
if( $script:nvenc_flag ){
    $script:convert_to_mp4_commandline += " -eo `"-c h264 --profile main --vbrhq 0 --vbr-quality 25 --gop-len 90 --cqp 20:23:25`""
} else {
    $script:convert_to_mp4_commandline += " -eo `"-c h264 --profile main --qvbr-quality 25 --gop-len 90 --cqp 20:23:25`""
}
$script:convert_to_mp4_commandline += " -fmt mp4"
$script:convert_to_mp4_commandline += " -m `"$script:amatsukaze_directory\exe_files\muxer.exe`""
$script:convert_to_mp4_commandline += " -bcm 0.5"
$script:convert_to_mp4_commandline += " --chapter"
$script:convert_to_mp4_commandline += " -f `"$script:amatsukaze_directory\avscache\17E37FEB.avs`""
$script:convert_to_mp4_commandline += " --subtitles"
$script:convert_to_mp4_commandline += " --jls-cmd `"$script:amatsukaze_directory\JL\JL_Standard.txt`""
if( $script:nvenc_flag ){
    $script:convert_to_mp4_commandline += " --mpeg2decoder CUVID"
    $script:convert_to_mp4_commandline += " --h264decoder CUVID"
} else {
    $script:convert_to_mp4_commandline += " --mpeg2decoder default"
    $script:convert_to_mp4_commandline += " --h264decoder default"
}
$script:convert_to_mp4_commandline += " --ignore-no-drcsmap"
$script:convert_to_mp4_commandline += " --no-delogo"
$script:convert_to_mp4_commandline += " --ignore-no-logo"
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID171-1.lgd`""
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID1032-1.lgd`""
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID1040-1.lgd`""
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID1040-2.lgd`""
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID1048-1.lgd`""
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID1056-1.lgd`""
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID1064-1.lgd`""
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID1072-1.lgd`""
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID1072-2.lgd`""
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID23608-1.lgd`""
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID101-1.lgd`""#NHK BS
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID211-1.lgd`""#BS11
$script:convert_to_mp4_commandline += " --logo `"$script:amatsukaze_directory\logo\SID171-2.lgd`""#BSテレ東

#########################
# Help
#########################
if( $help ) {
    Write-Host "NAME"
    Write-Host ""
    Write-Host "   $script:script_name - M2TSファイルをCMを抜いたMP4形式に変換する"
    Write-Host ""
    Write-Host "SYPNOSYS"
    Write-Host ""
    Write-Host "   $script:script_name -log_file <file path>"
    Write-Host "                       -source <directory path>"
    Write-Host "                       -destination <directory path>"
    Write-Host "                       [-nvenc] [-quiet] [-shutdown] [-help]"
    Write-Host ""
    Write-Host "DESCRIPTION"
    Write-Host ""
    Write-Host "   -source"
    Write-Host "        変換元のm2tsファイルが格納されているディレクトリを指定する"
    Write-Host ""
    Write-Host "   -destination"
    Write-Host "        mp4ファイルの格納先のディレクトリを指定する"
    Write-Host ""
    Write-Host "   -nvenc"
    Write-Host "        NVEncを使用してエンコードする。指定しない場合はQVEncを使用してエンコードする"
    Write-Host ""
    Write-Host "   -quiet"
    Write-Host "        処理開始前の確認をスキップする"
    Write-Host ""
    Write-Host "   -shutdown"
    Write-Host "        変換終了後、${delay_shutdown}秒後にPCをシャットダウンする"
    Write-Host ""
    Write-Host "   -help"
    Write-Host "        ヘルプを表示する"
    Write-Host ""
    exit
}

###################
# Function
###################
function ConvertToMp4 {
    Param(
        [string]$source,
        [string]$destination
    )

    [IniFileManager]$ini_file = [IniFileManager]::new($script:ini_file_path)

    $convert_files = @()

    $m2ts_files = Get-ChildItem "$source"      -Recurse -Filter *.m2ts
    $mp4_files  = Get-ChildItem "$destination" -Recurse -Filter *.mp4

    foreach( $m2ts_file in $m2ts_files ){

        if( !$m2ts_file.Name.StartsWith(".") -and !$m2ts_file.Name.StartsWith("_") -and !$m2ts_file.Name.StartsWith("TEMP_") ){

            [bool]$convert_flag = $false

            [string]$destination_file_path = $m2ts_file.FullName.Replace($source,$destination).Replace(".m2ts",".mp4")
            $mp4_file = ConvertToMp4_GetMp4File -file_path $destination_file_path -mp4_files $mp4_files

            if( $null -eq $mp4_file ){
                $convert_flag = $true
            } elseif( $m2ts_file.Length -ne $ini_file.GetValueLong( $script:section_convert, $m2ts_file.Name, -1 ) ){
                $convert_flag = $true
            } else {
                [string]$temp_file_path = ( Split-Path -Parent ${destination_file_path}) + "\TEMP_" +(Split-Path -Leaf ${destination_file_path})
                $mp4_file = ConvertToMp4_GetMp4File -file_path $temp_file_path -mp4_files $mp4_files
                if( -Not([string]::IsNullOrEmpty($mp4_file.FullName) ) ){
                    $convert_flag = $true
                }
            }
        }

        if($convert_flag){
            if( $m2ts_file.Length -eq $ini_file.GetValueLong( $script:section_convert, $m2ts_file.Name, -1 ) ){ `
                if( $debug ){ $script:log.Write( [OutputLogType]::Info, "ConvertToMp4", ( "スキップ(変換済み) .... " + $m2ts_file.Name ) ) }
            } else {

                [string]$temp_file_path = (Split-Path -Parent ${destination_file_path}) + "\TEMP_" +(Split-Path -Leaf ${destination_file_path})
                if( Test-Path( $temp_file_path) ){
                    Remove-Item -Path ${temp_file_path}
                }

                $convert_files += $m2ts_file
            }
        }
    }

    if( $convert_files.Count -gt 0 ){
        [string]$console_output = "以下のファイルをMP4へ変換します。"
        foreach( $convert_file in $convert_files ){
            $console_output += ( "`n  - " + (Split-Path -Leaf $convert_file) )
        }
        $console_output += "`n  変換ファイル格納先:$destination"
        $script:log.Write( [OutputLogType]::Info, "ConvertToMp4", $console_output )

        if( !$script:quiet_flag ){
            $response = ( Read-Host "よろしいですか?([Y]es/[n]o)" )
            switch($response){
                "" {}
                "y" {}
                default { return; }
            }
        }
    }

    foreach( $convert_file in $convert_files ){

        $destination_mp4 = $convert_file.FullName.Replace($source,$destination).Replace(".m2ts",".mp4")
        ConvertToMp4_OneFile -source $convert_file.FullName -destination $destination_mp4

        if( Test-Path( ConvertPath($destination_mp4) ) ){
            $m2ts_file_convert = (Get-Item -Path ( ConvertPath($convert_file.FullName) ) )
            $ini_file.Update( $script:section_convert, $m2ts_file_convert.Name, $m2ts_file_convert.Length )
            $ini_file.Write()
        }
    }

    Remove-Item "${script:work_directory}/*" -Exclude .gitkeep -Recurse -Force > $null
}

function ConvertToMp4_GetMp4File {
    Param(
        [string]$file_path,
        [System.Array]$mp4_files
    )

    foreach( $mp4_file in $mp4_files ){
        if( $file_path.Equals($mp4_file.FullName) ){
            return $mp4_file
        }
    }

    return $null
}

function ConvertToMp4_OneFile {
    Param(
        [string]$source,
        [string]$destination
    )

    [string]$source_file_name = ( Split-Path -Leaf $source ) 

    [string]$destination_directory = (Split-Path -Parent $destination)
    if( -Not(Test-Path(${destination_directory}))){
        New-Item -Type Directory ${destination_directory} > $null
    }

    $script:log.Write( [OutputLogType]::Info, "ConvertToMp4_OneFile", "Convert `"$source_file_name`" ..." )

    [DateTime]$start_time_convert = [DateTime]::Now

    [string]$temp_file = ${script:work_directory} + "\TEMP_" +(Split-Path -Leaf ${destination})

    [string]$argument_list = $script:convert_to_mp4_commandline
    $argument_list += " --input `"${source}`" "
    $argument_list += " --output `"${temp_file}`" "
    $script:log.Write( [OutputLogType]::Info, "ConvertToMp4_OneFile", "Start-Process $script:amatsukaze_cli_exe -ArgumentList `"$argument_list`" -Wait" )

    [System.Diagnostics.ProcessStartInfo]$process_start_info = New-Object System.Diagnostics.ProcessStartInfo
    $process_start_info.FileName = $script:amatsukaze_cli_exe
    $process_start_info.CreateNoWindow = $false
    $process_start_info.RedirectStandardError = $false
    $process_start_info.RedirectStandardOutput = $true
    $process_start_info.UseShellExecute = $false
    $process_start_info.Arguments = $argument_list

    [System.Diagnostics.Process]$process = New-Object System.Diagnostics.Process
    $process.StartInfo = $process_start_info
    $string_builder = New-Object -TypeName System.Text.StringBuilder
    $output_data_received = {
        if (! [String]::IsNullOrEmpty($EventArgs.Data)) {
            $Event.MessageData.AppendLine($EventArgs.Data)
        }
    }
    $output_event = Register-ObjectEvent -InputObject $process `
        -Action $output_data_received -EventName 'OutputDataReceived' `
        -MessageData $string_builder
    [void]$process.Start()
    $process.BeginOutputReadLine()
    $process.WaitForExit()
    Unregister-Event -SourceIdentifier $output_event.Name
    $string_builder.ToString()

    if( Test-Path(ConvertPath(${destination})) ){
        Remove-Item -Path (ConvertPath(${destination}))
    }
    if( Test-Path(ConvertPath(${destination}).Replace(".mp4",".ass")) ){
        Remove-Item -Path (ConvertPath(${destination}).Replace(".mp4",".ass"))
    }

    if( Test-Path(ConvertPath(${temp_file}) ) ){
        Move-Item -Path (ConvertPath(${temp_file})) -Destination (ConvertPath(${destination}))

        if( Test-Path(ConvertPath(${temp_file}).Replace(".mp4",".ass")) ){
            Move-Item -Path (ConvertPath(${temp_file}).Replace(".mp4",".ass")) -Destination (ConvertPath(${destination}).Replace(".mp4",".ass"))
        }
            
        [TimeSpan]$duration_convert=[DateTime]::Now-$start_time_convert
        $script:log.Write( [OutputLogType]::Info, "ConvertToMp4_OneFile", ( "変換処理時間 : " + (GetDurationString($duration_convert) ) ) )
        
        [string]$destination_file_path = ConvertPath($destination)
        if(-Not(Test-Path($destination_file_path))){
            $script:log.Write( [OutputLogType]::Error, "ConvertToMp4_OneFile", ( "変換失敗 : " + $source_file_name ) )
        }
    } else {
        $script:log.Write( [OutputLogType]::Error, "ConvertToMp4_OneFile", ( "変換失敗 : " + $source_file_name ) )
    }
}

#########################
# Parameter check
#########################
if( [string]::IsNullOrEmpty("${script:source_root}") -or -Not(Test-Path "${script:source_root}") ){
    $script:log.Write( [OutputLogType]::Error, "ParameterCheck", ( "変換元フォルダがありません '"+${script:source_root}+"'" ) )
    exit
}

if( [string]::IsNullOrEmpty("${script:destination_root}") -or -Not(Test-Path "${script:destination_root}") ){
    $script:log.Write( [OutputLogType]::Error, "ParameterCheck", ( "変換先フォルダがありません '"+${script:destination_root}+"'" ) )
    exit
}

if( -Not(Test-Path "${script:work_directory}") ){
    New-Item -Type Directory ${script:work_directory} > $null
    if( -Not(Test-Path "${script:work_directory}") ){
        $script:log.Write( [OutputLogType]::Error, "ParameterCheck", ( "作業用ディレクトリを作成できませんでした '"+${script:work_directory}+"'") )
        exit
    }
}

#########################
# Main
#########################
$script:log.Write( [OutputLogType]::Info, "", "【M2TS->MP4変換を開始します】" )
$script:log.Write( [OutputLogType]::Info, "", "  変換元フォルダ:$script:source_root" )
$script:log.Write( [OutputLogType]::Info, "", "  変換先フォルダ:$script:destination_root" )

if( !$debug -and ( CheckAlreadyRunning ) ){
    $script:log.Write( [OutputLogType]::Info, "", "変換・コピー処理実行中のため中断します" )
    $script:log.Write( [OutputLogType]::Info, "", "【M2TS->MP4変換を終了しました】" )
    exit
}

Remove-Item "${script:work_directory}/*" -Exclude .gitkeep -Recurse -Force > $null

if( -Not(Test-Path "${script:destination_root}") ){
    New-Item -Type Directory ${script:destination_root} > $null
}
# Convert to MP4
ConvertToMp4 -source "$script:source_root" -destination "$script:destination_root"

#shutdown
if( $shutdown ){
    Shutdown($script:log)
}

$script:log.Write( [OutputLogType]::Info, "", "【M2TS->MP4変換を終了しました】" )
Using Module ".\Utils.psm1"

Param(
    [string]$log_file = $null,
    [string]$source,
    [int]$day,
    [float]$totalsize,
    [switch]$quiet,
    [switch]$shutdown,
    [switch]$help
)

#########################
# Parameters
#########################
[string]$script:script_path = Split-Path -Parent $PSCommandPath
[string]$script:script_name = Split-Path -Leaf $PSCommandPath

[Log]$script:log=[Log]::new($script:script_name,$log_file)

[string]$script:source_root    = $source
[string]$script:duration_day   = $day
[uint64]$script:totalsize_byte = $totalsize*1024*1024*1024

[bool]$script:quiet_flag=$quiet

[bool]$debug=$false
if($debug){
    [string]$script:source_root    = "S:\recorded_files"
    [int]$script:duration_day      = 100
    [uint64]$script:totalsize_byte = 800*1024*1024*1024
}

#########################
# Help
#########################
if( $help ) {
    Write-Host "NAME"
    Write-Host ""
    Write-Host "   $script:script_name - 最終更新日から指定した日数を経過したファイルを削除し、"
    Write-Host "                    フォルダサイズが指定サイズ以下になるように古いファイルを削除する"
    Write-Host ""
    Write-Host "SYPNOSYS"
    Write-Host ""
    Write-Host "   $script:script_name -source <directory path>"
    Write-Host "                       [-day <number of day>]"
    Write-Host "                       [-totalsize <number of day>]"
    Write-Host "                       -log_file <file path>"
    Write-Host "                       [-quiet] [-help]"
    Write-Host ""
    Write-Host "DESCRIPTION"
    Write-Host ""
    Write-Host "   -source"
    Write-Host "        ファイル削除対象のディレクトリを指定する"
    Write-Host ""
    Write-Host "   -day"
    Write-Host "        最終更新日からここで指定した日数を経過したファイルを削除する"
    Write-Host ""
    Write-Host "   -totalsize"
    Write-Host "        フォルダ内のファイルが占有しているファイルサイズが指定したサイズ(単位:GB)以上の場合、”
    Write-Host "        指定サイズ以下になるように最終更新日時の古いものを削除する"
    Write-Host ""
    Write-Host "   -log_file"
    Write-Host "        ログファイルパスを指定する"
    Write-Host ""
    Write-Host "   -quiet"
    Write-Host "        処理開始前の確認をスキップします"
    Write-Host ""
    Write-Host "   -shutdown"
    Write-Host "        コピー終了後、${delay_shutdown}秒後にPCをシャットダウンする"
    Write-Host ""
    Write-Host "   -help"
    Write-Host "        ヘルプを表示する"
    Write-Host ""
    exit
}

###################
# Function
###################
function DeleteFiles_Day {
    Param(
        [string]$source,
        [int]$day
    )

    $files = Get-ChildItem "$source" -Recurse -File

    $delete_files = @()
    foreach( $file in $files ){
        if( !$file.Name.StartsWith("_") -and !$file.Name.StartsWith(".") ){
            [timespan]$time_diff = [DateTime]::Now - $file.LastWriteTime
            if( $time_diff.TotalDays -gt $day ){
                $delete_files += $file
            }
        }
    }

    if( $delete_files.Count -le 0 ){
        [string]$console_output = "最終書込日時から${day}日経過したファイルはありませんでした"
        $script:log.Write( [OutputLogType]::Info, "DeleteFiles_Day", $console_output )
    } else {
        [string]$console_output = "最終書込日時から${day}日経過した以下のファイルを削除します"
        foreach( $delete_file in $delete_files ){
            $console_output += ( "`n  - " + (Split-Path -Leaf $delete_file.Name) `
                            + " (最終書込日時:" +  $delete_file.LastWriteTime.ToString("yyyy/MM/dd") + " / " `
                            + "ファイルサイズ:" + ( GetFileSizeString( $delete_file.Length ) ) + " )" )
        }
        $script:log.Write( [OutputLogType]::Info, "DeleteFiles_Day", $console_output )

        if( !$script:quiet_flag ){
            $console_output = "よろしいですか?([y]es/[N]o)"

            $response = ( Read-Host $console_output ).Trim()
            switch($response){
                "y" {}
                default { return; }
            }
        }
    }

    foreach( $delete_file in $delete_files ){
        Remove-Item -Path (ConvertPath($delete_file.FullName))
    }
}

function DeleteFiles_TotalSize{
    Param(
        [string]$source,
        [uint64]$totalsize
    )

    $files = ( Get-ChildItem "$source" -Recurse -File ) | Sort-Object -Property LastWriteTime -Descending

    $delete_files = @()
    [uint64]$current_size = 0
    foreach( $file in $files ){
        $current_size+=$file.Length
        if( $file.Name.EndsWith(".m2ts") -or $file.Name.EndsWith(".mp4") -or $file.Name.EndsWith(".ass") ){
            if( $current_size -gt $totalsize ){
                $delete_files += $file
            }
        }
    }

    [float]$current_size_gb=[Math]::Truncate( ( ( $current_size ) / 1024 / 1024 / 1024 ) * 10 ) / 10

    if( $delete_files.Count -le 0 ){
        [string]$console_output = "現在のフォルダサイズは${current_size_gb}GBのため、削除するファイルはありませんでした"
        $script:log.Write( [OutputLogType]::Info, "DeleteFiles_TotalSize", $console_output )
    } else {
        [string]$console_output = "現在のフォルダサイズが${current_size_gb}GBのため、以下のファイルを削除します"
        foreach( $delete_file in $delete_files ){
            $console_output += ( "`n  - " + (Split-Path -Leaf $delete_file.Name) `
                            + " (最終書込日時:" +  $delete_file.LastWriteTime.ToString("yyyy/MM/dd") + " / " `
                            + "ファイルサイズ:" + ( GetFileSizeString( $delete_file.Length ) ) + " )" )
        }
        $script:log.Write( [OutputLogType]::Info, "DeleteFiles_TotalSize", $console_output )

        if( !$script:quiet_flag ){
            $console_output = "よろしいですか?([y]es/[N]o)"

            $response = ( Read-Host $console_output ).Trim()
            switch($response){
                "y" {}
                default { return; }
            }
        }
    }

    foreach( $delete_file in $delete_files ){
        Remove-Item -Path (ConvertPath($delete_file.FullName))
    }
}

#########################
# Parameter check
#########################
if( [string]::IsNullOrEmpty("${script:source_root}") -or -Not(Test-Path "${script:source_root}") ){
    $script:log.Write( [OutputLogType]::Error, "ParameterCheck", ( "ファイル削除対象フォルダがありません '"+${script:source_root}+"'") )
    exit
}

#########################
# Main
#########################
$script:log.Write( [OutputLogType]::Info, "", "【ファイル・空フォルダ削除を開始します】" )
$script:log.Write( [OutputLogType]::Info, "", "  ファイル・空フォルダ削除対象フォルダ:$script:source_root" )

if( !$debug -and ( CheckAlreadyRunning ) ){
    $script:log.Write( [OutputLogType]::Info, "", "変換・コピー処理実行中のため中断します" )
    $script:log.Write( [OutputLogType]::Info, "", "【ファイル・空フォルダ削除を終了しました】" )
    exit
}

if( ( ${script:duration_day} -le 0 ) -and ( ${script:totalsize_byte} -le 0 ) ){
    $script:log.Write( [OutputLogType]::Info, "", "  不要ファイル・空フォルダを削除します" )
} else {
    if( ${script:duration_day} -gt 0 ){
        $script:log.Write( [OutputLogType]::Info, "", "  削除対象ファイル       :最終書込日時から${script:duration_day}日経過したファイル" )
    }
    if( ${script:totalsize_byte} -gt 0 ){
        $script:log.Write( [OutputLogType]::Info, "", ( "  フォルダサイズ         :"+(${script:totalsize_byte}/1024/1024/1024)+"GB以下" ) )
    }
}

# delete
if( ${script:duration_day} -gt 0 ){
    DeleteFiles_Day -source "$script:source_root" -day ${script:duration_day}
}
if( ${script:totalsize_byte} -gt 0 ){
    DeleteFiles_TotalSize -source "$script:source_root" -totalsize ${script:totalsize_byte}
}
DeleteFolders -source "$script:source_root" -Log $script:log -quiet $script:quiet_flag
DeleteUnnecessaryFiles -source "$script:source_root" -Log $script:log -quiet $script:quiet_flag

#shutdown
if( $shutdown ){
    Shutdown($script:log)
}

$script:log.Write( [OutputLogType]::Info, "", "【ファイル・空フォルダ削除を終了しました】" )

トラブルシューティングメモ

再起動するとタスクスケジューラによる定期変換が行われない

Windows再起動後にタスクスケジューラが正しく動作せず、定期変換処理が実行されなくなることがありました。Windows再起動は自動で行われることも度々あるため、結構ストレスになります。

タスクスケジューラWindow上から直接実行をしたとき、タスクスケジューラのパラメータが全く一緒であるにも関わらず起動できるバッチと起動するバッチがあり、ここで起動できないバッチファイルがタスクスケジューラでも実行できないところまでは分かりましたが、何故実行できないかはっきりした原因はまだわかっていません。

いまのところ、下記の構成をデフォルトの「Windows Vista, Windows Server 2008」から「Windows 10」に変更することで自動実行が動作するようになったところまでは確認していますが、何故これで動作するようになったかの原因は掴めていません。

原因が判明し恒久的な対処法が分かり次第、本記事をアップデートします。