1

Тема: AHK: Объединение MTS, MPG и AVI файлов с sd-карты либо из папки

Скрипт объединяет mts, mpg и avi файлы записанные на камеры Sony без потери в качестве и рассинхронов.
Если находит и MTS и AVI и MPG файлы, то просит выбрать нужный формат.
Нужно иметь установленный ffmpeg.
Можно выводить аудио отдельно для последующей обработки в звуковом редакторе.
Если в MTS было запихнуто PCM, то происходит перекодировка аудио в ac3 320k.
В MPG аудио конвертируется в MP2 320kb.
MTS, MPG и AVI должны быть записаны с одинаковыми кодеками и параметрами.
Можно выбрать с какого по какой файл объединять.
Если эти поля пустые, то берется первый и последний файлы.
Выбирать можно с помощью кнопки Browse - откроется сразу нужная папка со структуры SD карты, либо скопировав файл и вставив его путь - правой кнопкой мыши.

InputFolder := "H:\"
OutputFolder := "D:\sizeti"
AudioSeparate := 0      ; 0 - false, 1 - true
ffmpeg := "C:\Program Files\ffmpeg\bin\ffmpeg.exe"

if !FileExist(ffmpeg)
{   
   MsgBox, 262144,, Установите ffmpeg
   ExitApp
}

#SingleInstance force
SetBatchLines, -1
SetTitleMatchMode, 2
DetectHiddenWindows, on
OnExit, Exit
OnMessage(0x204, "WM_RBUTTONDOWN")
OnMessage(0x205, "WM_RBUTTONUP")
InputFolderDefault := RegexReplace(InputFolder,"\\$")

Gui, +AlwaysOnTop
Gui,  Add, Text, x10 y10, Output Name:
Gui, Add, Edit, vName w400 x10 y30
Gui, Add, Text, x10 y60, From:
Gui, Add, Edit, vFrom w400 x10 y80
Gui, Add, Button,gBrowse v1 w80 x420  y80, Browse
Gui, Add, Text, x10 y110, To:
Gui, Add, Edit, vTo w400 x10 y130
Gui, Add, Button,gBrowse v2 w80 x420  y130, Browse
Gui, Add, Text, x10 y160, Input Folder:
Gui, Add, Edit, vInputFolder w400 x10 y180, %InputFolder%
Gui, Add, Button,gBrowse v3 w80 x420  y180, Browse
Gui, Add, Text, x10 y210, Output Folder:
Gui, Add, Edit, vOutputFolder w400 x10 y230, %OutputFolder%
Gui, Add, Button,gBrowse v4 w80 x420  y230, Browse
Gui, Add, CheckBox, x105 y265  w100 h20 vWav Checked%AudioSeparate%, Wav отдельно
Gui, Add, CheckBox, x212 y265  w50 h20 vAvi gCheck, AVI
Gui, Add, CheckBox, x263 y265  w50 h20 vMts gCheck, MTS
Gui, Add, CheckBox, x318 y265  w50 h20 vMpg gCheck, MPG
Gui, Add, CheckBox, x373 y265  w50 h20 vAll gCheck Checked, ALL
Gui, Add, Button, x10 w40 y267 Default, OK
Gui, Show
Gui, 2: +OwnDialogs
Gui, 2: Add, Progress, x6 y40 w460 h20 Range0-1000000 vProgress
Gui, 2: Add, Text, x6 y10 w240 h20 vDo, Идет проверка файлов
Menu, CMenu, Add, Paste, Handler
return

Handler:
GuiControl,,%Control%, %ClipBoard%
return

Check:
If (A_GuiControl="AVI")
{
   GuiControlGet, State,,AVI
   If State
   {
      GuiControl,,MTS, 0
      GuiControl,,MPG, 0
      GuiControl,,ALL, 0
   }
   Else
      GuiControl,,ALL, 1
}
Else if (A_GuiControl="MTS")
{
   GuiControlGet, State,,MTS
   If State
   {
      GuiControl,,AVI, 0
      GuiControl,,MPG, 0
      GuiControl,,ALL, 0
   }
   Else
      GuiControl,,ALL, 1
}
Else if (A_GuiControl="MPG")
{
   GuiControlGet, State,,MPG
   If State
   {
      GuiControl,,MTS, 0
      GuiControl,,AVI, 0
      GuiControl,,ALL, 0
   }
   Else
      GuiControl,,ALL, 1
}
Else
{
   GuiControl,,MTS, 0
   GuiControl,,AVI, 0
   GuiControl,,MPG, 0
   GuiControl,,ALL, 1
}
return

Browse:
Gui, -AlwaysOnTop +OwnDialogs
if (A_GuiControl = 3) or (A_GuiControl = 4)
{
   if (A_GuiControl = 3)
      Prompt := "Select Input Folder"
   else
      Prompt := "Select Output Folder"
   FileSelectFolder, SelectedFile, ::{20d04fe0-3aea-1069-a2d8-08002b30309d}, 0, %Prompt%
}
else
{
   GuiControlGet, Avi
   GuiControlGet, Mpg
   GuiControlGet, Mts
   GuiControlGet, All
   GuiControlGet, InputFolder
   InputFolder := RegexReplace(InputFolder,"\\$")
   if !FileExist(InputFolder)
   {   
      MsgBox, Input Folder не существует
      GuiControl, Focus, InputFolder
      return
   }
   if (InputFolder = InputFolderDefault)
   {
      if (all = 1)
      {
         targetFolder := InputFolder "\PRIVATE\SONY\DVF\100DVF"
         if !FileExist(targetFolder "\*.avi")
         {
            targetFolder := InputFolder "\PRIVATE\AVCHD\BDMV\STREAM"
            if !FileExist(targetFolder "\*.mts")
            {
               targetFolder := InputFolder "\MP_ROOT\100PNV01"
               if !FileExist(targetFolder "\*.mpg")
                  targetFolder := InputFolder
            }
         }
      }
      if (avi = 1)
      {
         targetFolder := InputFolder "\PRIVATE\SONY\DVF\100DVF"
         if !FileExist(targetFolder "\*.avi")
            targetFolder := InputFolder
      }
      if (mts = 1)
      {
         targetFolder := InputFolder "\PRIVATE\AVCHD\BDMV\STREAM"
         if !FileExist(targetFolder "\*.mts")
            targetFolder := InputFolder
      }
      if (mpg = 1)
      {
         targetFolder := InputFolder "\MP_ROOT\100PNV01"
         if !FileExist(targetFolder "\*.mpg")
            targetFolder := InputFolder
      }
   }
   else
      targetFolder := InputFolder
   if (A_GuiControl = 1)
      Prompt := "Open a file From"
   else
      Prompt := "Open a file To"
   FileSelectFile, SelectedFile,, %targetFolder%, %Prompt%, Video File (*.AVI; *.MTS; *.MPG)      
}
if (A_GuiControl = 1) and (SelectedFile != "")
   GuiControl,,From,%SelectedFile%
if (A_GuiControl = 2) and (SelectedFile != "")
   GuiControl,,To,%SelectedFile%
if (A_GuiControl = 3) and (SelectedFile != "")
   GuiControl,,InputFolder,%SelectedFile%
if (A_GuiControl = 4) and (SelectedFile != "")
   GuiControl,,OutputFolder,%SelectedFile%
Gui, +AlwaysOnTop -OwnDialogs
Return

GuiClose:
Gui, destroy
ExitApp

2GuiClose:
Gui, 2: destroy 
ExitApp

ButtonOK:
Gui, +OwnDialogs
Gui, Submit, NoHide

StringReplace, name, name, %A_SPACE%, _, All
isValidFileName(Name)
If ErrorLevel
{
   MsgBox, В имени файла недоступные символы.`nПереименуйте!
   GuiControl, Focus, Name
   return
}
If (Name = "")
{
   MsgBox, Забыли написать имя файла!
   GuiControl, Focus, Name
   return
}
OutputFolder := RegexReplace(OutputFolder,"\\$")
if !FileExist(OutputFolder)
{   
   MsgBox, Output Folder не существует
   GuiControl, Focus, OutputFolder
   return
}
if FileExist(OutputFolder "\" Name ".avi") or FileExist(OutputFolder "\" Name ".mts") or FileExist(OutputFolder "\" Name ".mpg") or FileExist(OutputFolder "\" Name ".wav")
{
   MsgBox, Такое имя уже существует.`nПереименуйте!
   GuiControl, Focus, Name
   return
}
InputFolder := RegexReplace(InputFolder,"\\$")
if !FileExist(InputFolder)
{   
   MsgBox, Input Folder не существует
   GuiControl, Focus, InputFolder
   return
}
if (from != "")
{
   if !FileExist(From)
   {   
      MsgBox, Файла с таким названием, как в "От" не найдено.
      GuiControl, Focus, From
      Return
   }
   If !RegExMatch(From, "^\Q" InputFolder "\E")
   {   
      MsgBox, Путь "От" и Input Folder различаются
      GuiControl, Focus, From
      Return
   }
   if (SubStr(from, StrLen(from)-3) != ".avi") and (SubStr(from, StrLen(from)-3) != ".mts") and (SubStr(from, StrLen(from)-3) != ".mpg")
   {   
      MsgBox, В "От" выбран не видеофайл.
      GuiControl, Focus, From
      Return
   }
}
if (to != "")
{
   if !FileExist(to)
   {   
      MsgBox, Файла с таким названием, как в "До" не найдено.
      GuiControl, Focus, to
      Return
   }
   If !RegExMatch(to, "^\Q" InputFolder "\E")
   {   
      MsgBox, Путь "До" и Input Folder различаются
      GuiControl, Focus, to
      Return
   }
   if (SubStr(to, StrLen(to)-3) != ".avi") and (SubStr(to, StrLen(to)-3) != ".mts") and (SubStr(to, StrLen(to)-3) != ".mpg")
   {   
      MsgBox, В "До" выбран не видеофайл.
      GuiControl, Focus, to
      Return
   }
}
if (from != "") and (to != "") 
{
   if (RegExReplace(from, "(.*)\\.*$","$1") != RegExReplace(to, "(.*)\\.*$","$1"))
   {   
      MsgBox, Директории "От" и "До" различаются.
      GuiControl, Focus, From
      Return
   }
   if (From > To)
   {   
      MsgBox, Начальный файл больше конечного.
      GuiControl, Focus, From
      return
   }
}
If (SubStr(InputFolder, 1, 2) != InputFolderDefault)
   SdCard := 1
FileList := found := PrevDir := ListDir := prev := dir := List := PrevList := VarList1 := VarList2 := count := size := DifferentCodec := VideoSize := AudioSize := AudioSizeWav := ""
Global Container := ["avi", "mts", "mpg"]
If (from != "") or (to != "")
{
   If ((from != "") and (SubStr(from, StrLen(from)-3) != ".AVI")) or ((to != "") and (SubStr(to, StrLen(to)-3) != ".AVI"))
      Remove("avi")
   If ((from != "") and (SubStr(from, StrLen(from)-3) != ".MTS")) or ((to != "") and (SubStr(to, StrLen(to)-3) != ".MTS"))
      Remove("mts")
   If ((from != "") and (SubStr(from, StrLen(from)-3) != ".MPG")) or ((to != "") and (SubStr(to, StrLen(to)-3) != ".MPG"))
      Remove("mpg")
}
else if (all = 0)
{
   If (avi = 0)
      Remove("avi")
   If (mts = 0)
      Remove("mts")
   If (mpg = 0)
      Remove("mpg")
}
Loop, Files, %InputFolder%\*.*, R
{
   Loop % Container.MaxIndex()
   {
      If (A_LoopFileExt = Container[A_Index])
      {
         FileList .= A_LoopFileLongPath "|" A_LoopFileSizeMB "`n"
         break
      }
   }
}
Sort, FileList
StringTrimRight, FileList, FileList, 1
Loop % Container.MaxIndex()
{
   ext := Container[A_Index]
   Loop, parse, FileList, `n
   {
      StringSplit, ALoopField, A_LoopField, |
      SplitPath, ALoopField1, ALoopFileName, ALoopFileDir, ALoopFileExt
      if (ext != ALoopFileExt)
         Continue
      if (found = 1)
      {
         if (SdCard != 1)
            MsgBox, На sd карте находятся avi, mts и mpg файлы.`nВыбирите какие-нибудь одни.
         else
            MsgBox, В указанной папке находятся avi, mts и mpg файлы.`nВыбирите какие-нибудь одни.
         Return
      }
      if (from != "") and (from != ALoopField1)
         Continue
      from := ""
      if (PrevDir != ALoopFileDir)   ;   если новая директория
      {
         if (StrLen(ListDir) > prev)       ;  если длина последней директории больше предпоследней 
         {
            prev := StrLen(ListDir)         
            dir := PrevDir
            List := PrevList VarList1
         }
         else
            List .= VarList2
         PrevList .= VarList2
         ListDir := VarList1 := VarList2 := ""
      }
      PrevDir := ALoopFileDir
      ListDir .= ALoopFileDir
      VarList1 .= ALoopFileName "|"
      VarList2 .= ALoopField1 "|"
      Count++
      Size += ALoopField2
      if (to = ALoopField1)
         break
   }
   if (found = "")
   {
      if (StrLen(ListDir) > prev)
      {
         dir := PrevDir
         List := PrevList VarList1
      }
      else
         List .= VarList2
      PrevList .= VarList2
      StringTrimRight, List, List, 1
      StringTrimRight, PrevList, PrevList, 1
      If (PrevList != "")
      {
         found = 1
         foundExt := ext
      }
   }
   If (A_Index != Container.MaxIndex())
      Continue
   If (found = "")
   {
      if (SdCard != 1)
         MsgBox, На sd карте нету видео!
      else
         MsgBox, В указанной папке нету видео!
      Return
   }
   else
   {
      ext := foundExt
      if (Count > 5400)
      {
         MsgBox, Слишком много файлов.`nМаксимум 5400
         Return
      }
      DriveSpaceFree, FreeSpace, %OutputFolder%
      if (ext = "mpg") and (StrLen(List)>16000)
         FreeSpace /= 5
      else if (ext = "mpg")
         FreeSpace /= 4
      else if (ext = "mts") or (StrLen(List)>16000)
         FreeSpace /= 2.2
      else
         FreeSpace /= 1.2
      if (size > FreeSpace)
      {
         MsgBox, Ошибка!!! Не хватает места!
         return
      }
      Gui, Hide
      Gui, 2: Show, x229 y182 h87 w477
      ProgressCount := 1000000/Count

      Run, %comspec% /k ,,Hide UseErrorLevel, cPid
      WinWait, ahk_pid %cPid%,, 10
      DllCall("AttachConsole","uint",cPid)
      hCon:=DllCall("CreateFile","str","CONOUT$","uint",0xC0000000,"uint",7,"uint",0,"uint",3,"uint",0,"uint",0)
      objShell := ComObjCreate("WScript.Shell")
      Loop, parse, PrevList, |
      {
         ALoopField := A_LoopField
         objExec := objShell.Exec("""" ffmpeg """ -i """ A_LoopField """")
         strStdErr := ""
         while, !objExec.StdErr.AtEndOfStream
            strStdErr := objExec.StdErr.ReadAll()
         RegExMatch(strStdErr, "s)Duration: (.+?)`,.+?bitrate: (.+?) kb/s.+?(Stream #0:0.+ (.+?) kb/s.+?)\R", match)
         Codec := match3
         if A_Index = 1
         {
            FirstALoopField := A_LoopField
            FirstCodec := Codec
            pcm := RegExMatch(FirstCodec, "Audio: pcm")
            if (ext = "mpg")
            {
               if !InStr(FirstCodec, "720x576")
               {
                  Gui, 2: Hide
                  MsgBox, Объединить невозможно, так как MPG не 720x576
                  Gui, Show
                  Return
               }
               if InStr(FirstCodec, "16:9")
                  aspect := "16:9"
               else if InStr(FirstCodec, "4:3")
                  aspect := "4:3"
               else
               {
                  Gui, 2: Hide
                  MsgBox, Неизвестен aspect `n %FirstCodec%
                  Gui, Show
                  Return
               }
            }
         }
         else
         {
            loop, parse, Codec, `n, `r
            {
               if !InStr(FirstCodec, RegexReplace(A_LoopField, "^(\s+?|)Stream #0:\d"))
               {
                  if (DifferentCodec = "")
                     DifferentCodec := FirstALoopField "`nvs`n"
                  DifferentCodec .= ALoopField "`n"
                  break
               }
            }
         }
         Duration_array := StrSplit(match1, ":")
         Duration := Duration_array[1]*3600 + Duration_array[2]*60 + Duration_array[3]
         if (ext = "mpg")
            VideoSize += Duration*3800
         else
            VideoSize += Duration*match2*0.125
         if (ext = "mpg")
            AudioSize += Duration*1536*0.125
         else if (pcm = 0) or (ext = "avi")
            AudioSize += Duration*match4*0.125
         else
            AudioSize += Duration*320*0.125
         if (wav = 1)
            AudioSizeWav += Duration*1536*0.125
         GuiControl, 2:, Progress, +%ProgressCount%
      }
      VideoSize *=0.9
      AudioSize *=0.9
      DllCall("CloseHandle", "uint", hCon)
      DllCall("FreeConsole")
      Process, Close, %cPid%
      if (DifferentCodec != "")
      {
         Gui, 2: Hide
         MsgBox, Объединить невозможно, так как у видео разные кодеки.`n%DifferentCodec%
         Gui, Show
         Return
      }
      if (AudioSizeWav > 4147200)
      {
         Gui, 2: Hide
         MsgBox, Wav отдельно сохранить нельзя, так как больше 4 гб.
         wav = 0
         Gui, 2: Show
      }
      if (StrLen(List)>16000) or (ext = "mts")
      {
         loop
         {
            TempFolder := OutputFolder "\TEMP\" RegExReplace(RandomStr(), "[^A-Za-z0-9]", "i")
            if !FileExist(TempFolder)
            {
               FileCreateDir, %TempFolder%
               break
            }
         }
      }
      if (StrLen(List)>16000)
      {
         GuiControl, 2:, Progress, 0
         GuiControl, 2:, Do, Идет копирование
         List := "", dir := TempFolder
         setformat,integer,hex
         a:= 0x20000
         Loop, parse, PrevList, |
         {
            b := Chr(a)
            List .= b "|"
            a++
            RunWait, %comspec% /c COPY "%A_LoopField%" "%TempFolder%\%b%", , Hide, CopyPID
            FileGetSize, SizeF, %TempFolder%\%b%, M
            ProgressCount := 1000000/size*SizeF
            GuiControl, 2:, Progress, +%ProgressCount%
         }
         setformat,integer,d
      }
      SetTimer, CheckSize, 1000
      If (ext = "avi") or (ext = "mpg")
      {
         GuiControl, 2:, Progress, 0
         GuiControl, 2:, Do, Идет экспорт видео
         FinalSize := VideoSize + AudioSize
         Filename := OutputFolder "\" Name ".avi"
         if (ext = "avi")
            RunWait, %ffmpeg% -i "concat:%list%" -c copy -map_metadata -1 %Filename%, %dir%, hide
         if (ext = "mpg")
            RunWait, %ffmpeg% -i "concat:%list%" -f avi -vcodec dvvideo -s pal -r pal -aspect %aspect% -pix_fmt yuv420p -vtag dvsd -vf fieldorder=bff -acodec pcm_s16le -ac 2 -ar 48000 -map_metadata -1 %Filename%, %dir%, hide
         If (wav = 1)
         {
            GuiControl, 2:, Progress, 0
            GuiControl, 2:, Do, Идет экспорт аудио
            FinalSize := AudioSizeWav
            Filename := OutputFolder "\" Name ".wav"
            if (ext = "avi")
               RunWait, %ffmpeg% -i "concat:%list%" -c:a copy -vn -map_metadata -1 %Filename%, %dir%, hide
            if (ext = "mpg")
               RunWait, %ffmpeg% -i "concat:%list%" -c:a pcm_s16le -ac 2 -ar 48000 -vn -map_metadata -1 %Filename%, %dir%, hide
         }
      }
      If (ext = "mts")
      {
         GuiControl, 2:, Progress, 0
         GuiControl, 2:, Do, Извлекается видео поток
         FinalSize := VideoSize
         Filename := TempFolder "\v.mts"
         RunWait, %ffmpeg% -i "concat:%list%" -c:v copy -an -map_metadata -1 %Filename%, %dir%, hide
         GuiControl, 2:, Progress, 0
         GuiControl, 2:, Do, Извлекается аудио поток
         FinalSize := AudioSize
         Filename := TempFolder "\a.ac3"
         if (pcm = 0)
            RunWait, %ffmpeg% -i "concat:%list%" -c:a copy -vn -map_metadata -1 %Filename%, %dir%, hide
         else
            RunWait, %ffmpeg% -i "concat:%list%" -c:a ac3 -b:a 320k -ar 48000 -ac 2 -vn -map_metadata -1 %Filename%, %dir%, hide
         if (dir = TempFolder)
         {
            Loop, %TempFolder%
            {
               If (A_LoopFileName != "a.ac3") and (A_LoopFileName != "v.mts")
                  FileDelete, %A_LoopFileLongPath%
            }
         }
         GuiControl, 2:, Progress, 0
         GuiControl, 2:, Do, Идет экспорт видео
         FinalSize := VideoSize + AudioSize
         Filename := OutputFolder "\" Name "." ext
         RunWait, %ffmpeg% -i v.mts -i a.ac3 -c copy -map_metadata -1 %Filename%, %TempFolder%, hide
         If (wav = 1)
         {
            GuiControl, 2:, Progress, 0
            GuiControl, 2:, Do, Идет экспорт аудио
            FinalSize := AudioSizeWav
            Filename := OutputFolder "\" Name ".wav"
            RunWait, %ffmpeg% -i "concat:%list%" -c:a pcm_s16le -ac 2 -ar 48000 -vn -map_metadata -1 %Filename%, %dir%, hide
         }
      }
      Gui, 2: Destroy
      MsgBox, 262144,, done
      ExitApp
   }
}

CheckSize:
FileGetSize, FileSize, %Filename%, K
ProgressCount := 1000000/FinalSize*FileSize
GuiControl, 2:, Progress, %ProgressCount%
return

Exit:
WinClose, ahk_pid %CopyPID%
WinWaitClose, ahk_pid %CopyPID%
WinClose, ahk_exe ffmpeg.exe
WinWaitClose, ahk_exe ffmpeg.exe
FileRemoveDir, %TempFolder%, 1
ExitApp




Remove(ext)
{
   for key, val in Container
   {
      if (val = ext)
      {
         Container.RemoveAt(A_Index)
         return
      }
   }
}

isValidFileName(_fileName, _isLong=true)
{
    forbiddenChars := _isLong ? "[<>|""\\/:*?]" : "[;=+<>|""\]\[\\/']"
    ErrorLevel := RegExMatch( _fileName , forbiddenChars )
    Return ! ErrorLevel
}

RandomStr(l = 10, i = 48, x = 122)
{
   Loop, %l%
   {
      Random, r, i, x
      s .= Chr(r)
   }
   Return, s
}

WM_RBUTTONDOWN(){
   if (A_GuiControl = "From") or (A_GuiControl = "To") or (A_GuiControl = "InputFolder") or (A_GuiControl = "OutputFolder")
   {
      Global Control := A_GuiControl
      GuiControl, Focus, %Control%
      Return 0
   }
}
WM_RBUTTONUP(){
   if (A_GuiControl = "From") or (A_GuiControl = "To") or (A_GuiControl = "InputFolder") or (A_GuiControl = "OutputFolder")
   { 
      Global Control := A_GuiControl
      GuiControl, Focus, %Control%
      Menu, cMenu, Show
   }
}

Тема для обсуждения