iPhone12になってから、プレイ動画の整理を全然やっていなかったです。サイズが変わってるのもあって、手直ししないといけなかったんだけど、放置してました。仕様もコースも少しづつ変わってきたし、最近調子がよくないのを分析したいというのもあって、手直しすることにしました。

C#から、Pythonに

iPhone7の時につくった元のコードはC#で書いてましたが、今回はPythonにしてみました。Pythonのほうが、もしかしたら、使ってくれる人がいるかもしれませんしね。

まずは分割する

使っているのは、OpenCV,FFMPEGです。動画を読み込んで、区切りにするファイルと画像比較して、区切り画像だったら、そこで分割しておくだけのコードです。区切り画像は、こんな奴です。

Kugiri

この区切り画像の一部分を比較します。実際の比較エリアはこの部分。背景は変わりますが、タイトルは同じなので、ここを使います。

kiridashi

画像検出のあれこれ

画像の検出は、色々なやり方があるのですが、ゲームのプレイ動画とういうことで、簡単にすませてます。位置が変わらないものを選んで、差分をとって平均とっただけです。テンプレートマッチングとかをちゃんとやっても遅くなるだけで結果は対してかわりませんでした。

ファイル名の手抜き~Microsoft One Drive の利用~

iphoneからケーブルでつないでファイルを収集してた時は、ファイル名が英字+数字になっていて日付とかは、メタデータから引っ張ってきてました。その日付でファイル名を作ってたのですが、今は、転送したら日付と時間のついたファイル名になっています。これは、Microsoft OneDriveで、転送するようにしたら、カメラロール内に年月フォルダにわかれるだけでなく、ファイル名にも時間がついてました。これのおかげで、先頭13文字をそのまま頂き、連番を付加するだけで以前と同じようにファイル名が作成できてます。時間はGMTになっちゃってますが、細かい話しなので、これでよしとしました。

タグ付けは?

以前は、ファイルにタグ付けしてましたので、分割ファイルができあがったら、次はタグ付けソフトのコードを書いていこうと思います。以前のはこんな感じです。

Tag

実際のコード


import os
import glob
import cv2
import ffmpeg
import numpy as np

# 比較画像クラス
class comp_image:
  def __init__(self,img,x,y,w,h):
      self.x1 = x
      self.y1 = y
      self.x2 = x+w
      self.y2 = y+h
      self.sq = w*h
      self.fr = 0.0
      self.cmpimage = img[y:y+h,x:x+w]
      self.res = False

  # 参照画像と比較してみる
  def comp(self,frame,threshold):
     # 差分の平均をみる、一致時は大体20以下
    im_diff = np.abs(frame[self.y1:self.y2,self.x1:self.x2].astype(int) - self.cmpimage.astype(int))
    self.fr= np.average(im_diff)

    if self.fr < threshold:
      self.res = True
      return True
    else:
      self.res = False
      return False

# ffmpeg 
# 分割時に横750にしておく
def writebyffmpeg(in_video,out_video,start_pos,t):
  in_options = {
      'c:v':'h264_cuvid',
      'ss':start_pos
      }
  out_options = {
      'ss':'0', 't':t,
      'vf':'crop=888:1720:0:100,hwupload_cuda,scale_cuda=750:-1',
      'r':30,
      'ar':44100,
      'cq':20,
      'c:v':'h264_nvenc'
      }
  (
      ffmpeg
      .input(in_video, **in_options)
      .output(out_video, **out_options)
      .run()
  )

# 区切り画像で、動画分割
# mabiki 分だけ、frameは読み飛ばし
def CutMovie(mvfile,cutimages,mabiki,outfolder):
  # filecounter
  filecounter = 1
  # VideoCapture
  cap = cv2.VideoCapture(mvfile)
  if (cap.isOpened()== False):  
    print("ビデオファイルを開くとエラーが発生しました") 
  
  # 終端サーチから
  end_search = True
  start_pos_sec = 0
  while(cap.isOpened()):
    # 読み飛ばし
    for i in range(mabiki):
      ret, frame = cap.read()
    # 表示
    pos_frame = cap.get(cv2.CAP_PROP_POS_FRAMES)
    #print(pos_frame)
    # False なら終了
    if ret == True:
      miniframe = cv2.resize(frame,dsize=None, fx=0.5 , fy=0.5)
      cv2.imshow("Video", miniframe)
      if cv2.waitKey(25) & 0xFF == ord('q'): 
          break
      # フレームチェック
      # CutImages[0]:GolfClashロゴ CutImages[1]: 終了ボタン
      # CutImages[3]:左上設定マーク
      cutimages[0].comp(frame,20)
      cutimages[1].comp(frame,20)
      cutimages[2].comp(frame,20)
      if cutimages[0].res or cutimages[1].res or cutimages[2].res:
        # 見つけた!!
        if end_search:
          # 終端検索時の場合
          # 終端側サーチ中だったので先頭サーチに変更する
          end_search = False
          # 現在位置の把握、先端からの切り出し時間計算
          pos_frame_msec = cap.get(cv2.CAP_PROP_POS_MSEC)
          end_pos_sec = pos_frame_msec /1000.0                 
          t = end_pos_sec - start_pos_sec
          # 10.0秒以上の動画のみ書き出し
          if t>10.0 :
            # 書き出し!!
            # 出力先の作成:ファイル名は13文字目までが日付_時刻になっているので、そのまま流用
            outfullpath = outfolder + "\\" + os.path.basename(mvfile)[:13] +  f"_{filecounter:02d}.mp4"
            writebyffmpeg(mvfile,outfullpath,start_pos_sec,t)
            filecounter += 1

        else:
          # 先頭サーチ中、分割画像の継続中は飛ばす
          pass
      else:
        if not end_search:
          # 先頭サーチ中で、分割画像が途切れたので先頭位置として記録
          # 位置把握
          pos_frame_msec = cap.get(cv2.CAP_PROP_POS_MSEC)
          start_pos_sec = pos_frame_msec / 1000.0
          # 終端サーチへ移行
          end_search = True                 
    else:
      # 最終までの分割
      # 位置把握
      pos_frame = cap.get(cv2.CAP_PROP_FRAME_COUNT)
      fps = cap.get(cv2.CAP_PROP_FPS)
      end_pos_sec = pos_frame/fps                  
      t = end_pos_sec - start_pos_sec
      # 10.0秒以上の動画のみ書き出し
      if t>10.0 :
        # 書き出し!!
        outfullpath = outfolder + "\\" + os.path.basename(mvfile)[:13] +  f"_{filecounter:02d}.mp4"
        writebyffmpeg(mvfile,outfullpath,start_pos_sec,t)
      break

  # 終了処理
  cap.release()

# Main
# 区切りにするスクショ画像
refimg1 = cv2.imread(r"D:\GolfClash\RefImage\OrgSize\SepImage.jpg")
refimg2 = cv2.imread(r"D:\GolfClash\RefImage\OrgSize\MatchEnd.jpg")
refimg3 = cv2.imread(r"D:\GolfClash\RefImage\OrgSize\Head.jpg")
cutimage1 = comp_image(refimg1,190,475,500,440)
cutimage2 = comp_image(refimg2,240,1700,420,90)
cutimage3 = comp_image(refimg3,0,0,130,230)
cutimages =[cutimage1,cutimage2,cutimage3]

# 出力先のフォルダ
outfolder = r"D:\FFMPEG\CutMovie"

# 指定フォルダ内のmp4ファイルを順次処理する
top_level_dir_path = r'E:\iPhone12Play'
# サブディレクトリ、ファイルの絶対パスのリストを得る
paths = map(os.path.abspath, glob.glob(top_level_dir_path + '/**/*.mp4', recursive=True))
# ファイルのみフィルタリングする
paths = filter(os.path.isfile, paths)
# ソートされていないので昇順にソートする
paths = sorted(paths)
# 1つずつ処理する
for i, p in enumerate(paths):
    print('[%s / %s] Start processing %s' % (i + 1, len(paths), p))
    CutMovie(p,cutimages,2,outfolder)