rokkonet

PC・Androidソフトウェア・アプリの開発・使い方に関するメモ

EPGREC録画動画ファイルを扱うrubyユーティリティ

2020 Apr. 18.

Videofile.rb

定義関数

# ファイル名からEPGREC録画ファイル名形式(epgrec-stem)部分を取り出す
#  EPGREC-type-filename: ^20\d{12}_20\d{12}(GR|BS)CHAN.EXT
#
#  epgrec_stem
#   20170923012300_20170923022200GR22.ts -> 20170923012300_20170923022200GR22
#   20170923210000_20170923215900BS103.ts -> 20170923210000_20170923215900BS103
def get_epgrec_stem


# get array of VideoCodecs in Videofile
def getArrayVideoCodecs


# get array of AudioCodecs in Videofile
def getArrayAudioCodecs


# get array of program-numbers in Videofile
def getArrayProgramNumbers


# convert raw-ts-file to split-ts-file
def convertRawTs2splitTsFile


# convert ts-file to H.264-file
def convertTs2H264File


# get array of same basename filenames
def getArraySameBasenameFileNames


# get array of filenames of same-epgrec-stem in the directory
def getArraySameEpgrecStemFileNamesInDir


# get array of filenames of same-epgrec-stem in the directory recursively
def getArraySameEpgrecStemFileNamesInDirRecursive


# delete redundant files from same epgrec-stem-files in derectory.
def deleteRedundantEpgrecStemFilesInDir(recursive)


# get epgrec-do-record-file-mode-number of arguement
def getEpgrecDoRecordMode


# get array of epgrec-do-record-mode and filename of smallest-stem-file
def getEpgrecDoRcordIdFilePathSmallestStemFile

Videofile.rb
# coding: utf-8

# ruby2.0
# 2020 Apr. 18.
# 2013 Oct. 27.
# Ryuichi Hashimoto

# This is utility for video-file.

# If filename is EPGREC-type, more functions work.
# Limited functions works with non EPGREC-type filename. 

# EPGREC-type-filename: ^20\d{12}_20\d{12}(GR|BS)CHAN.EXT
#   20170923012300_20170923022200GR22.ts
#   20170923210000_20170923215900BS103.ts


require 'open3'

class Videofile
  
  FFMPEG="ffmpeg"
#  FFMPEG="avconv"

  private_class_method :new
  
  def self.init(fname)
    if File.file?(fname)
      new(fname)
    else
      nil
    end
  end


  def initialize(fname)
    @filename = fname
    @filedir = File.dirname(@filename)
    @filebasename = File.basename(@filename, '.*') 
  end


  # get array of VideoCodecs in Videofile
  def getArrayVideoCodecs
    command = "#{FFMPEG} -i #{@filename}"
    stdout = ''
    stdout, stderr, status = Open3.capture3(command)
    outlines = stderr.encode("UTF-16BE", :invalid => :replace, :undef => :replace, :replace => '?').encode("UTF-8").split("\n")
    codec = Array.new
    outlines.each do | line |
      codec << line if line.index(": Video:")
    end
    return codec
  end

 
  # get array of AudioCodecs in Videofile
  def getArrayAudioCodecs
    command = "#{FFMPEG} -i #{@filename}"
    stdout = ''
    stdout, stderr, status = Open3.capture3(command)
    outlines = stderr.encode("UTF-16BE", :invalid => :replace, :undef => :replace, :replace => '?').encode("UTF-8").split("\n")
    codec = Array.new
    outlines.each do | line |
      codec << line if line.index(": Audio:")
    end
    return codec
  end


  # get array of program-numbers in Videofile
  def getArrayProgramNumbers
    command = "#{FFMPEG} -i #{@filename}"
    stdout = ''
    stdout, stderr, status = Open3.capture3(command)
    outlines = stderr.encode("UTF-16BE", :invalid => :replace, :undef => :replace, :replace => '?').encode("UTF-8").split("\n")
    programLines = Array.new
    program = Array.new
    outlines.each do | line |
      programLines << line if line.index("Program ")
    end

    # in case that string 'Program' is not contained in outlines
    if programLines.length < 1 then
      # if ": Video:" or ": Audio:" is contained in outlines, a stream exists without program-number
      outlines.each do | line |
        if (line.index(": Video:") || line.index(": Audio:"))
          program << nil
          return program
        end
      end
    else
    # in case that string 'Program' is contained in outlines
      programLines.each do | programWord |
        programWord.match(/[0-9]+/)
        program << $&
      end
    end
    return program
  end
  
  
  # convert raw-ts-file to split-ts-file
  def convertRawTs2splitTsFile
    outFileName = @filename + "_part.ts"
    videoPrograms = getArrayProgramNumbers
    if videoPrograms.length > 1 then
      sid = videoPrograms.shift
      system("tssplitter_lite #{@filename} #{outFileName} #{sid}")
      return outFileName
    elsif videoPrograms.length == 1 then
      File.rename(@filename, outFileName)
      return outFileName
    else
      return nil
    end
  end
  
  
  # convert ts-file to H.264-file
  def convertTs2H264File
    if !(splitTsFileName = convertRawTs2splitTsFile) then
      return nil
    end
    h264FileName = splitTsFileName + ".mp4"
    thread = `/usr/bin/getconf _NPROCESSORS_ONLN`.chomp
    ts2H264 = "#{FFMPEG} -y -i #{splitTsFileName} -f mp4 -c:v libx264 -preset slow -r 30000/1001 -aspect 16:9 -s 800x450  -bufsize 20000k -maxrate 25000k -acodec copy -threads #{thread} #{h264FileName} 2> /dev/null"
    system(ts2H264)
    return $?
  end


  # get array of same basename filenames
  def getArraySameBasenameFileNames
    fileDir = File.dirname(@filename)
    fileBasename = File.basename(@filename, '.*') 
    return Dir.glob(fileDir + '/' + fileBasename + '*')
  end

 
  # get array of filenames of same-epgrec-stem in the directory
  def getArraySameEpgrecStemFileNamesInDir
    stm = self.get_epgrec_stem
    if stm then
      Dir.glob(@filedir + '/' + self.get_epgrec_stem + '*')
    else
      return nil
    end
  end


  # get array of filenames of same-epgrec-stem in the directory recursively
  def getArraySameEpgrecStemFileNamesInDirRecursive
    stm = self.get_epgrec_stem
    if stm then
      Dir.glob(@filedir + '/**/*/' + self.get_epgrec_stem + '*')
    else
      return nil
    end
  end
  
  
  # delete redundant files from same epgrec-stem-files in derectory.
  # return a filename which is not redundant.
  def deleteRedundantEpgrecStemFilesInDir(recursive)
    # get same-stem-files
    if recursive then
      stemFiles = getArraySameEpgrecStemFileNamesInDirRecursive
    else
      stemFiles = getArraySameEpgrecStemFileNamesInDir
    end

    if !stemFiles then
      return nil
    end

    # get each file-size & get array of filename & filesize
    stemFilesPathSize = Array.new()
    stemFiles.each do | stemFilePath |
      # push an array[path, size] to the array(stemFilesPathSize[])
      stemFilesPathSize.push [stemFilePath, File.stat(stemFilePath).size]
    end

    # sort array of filename & filesize by size
    stemFilesPathSize.sort!{|a,b|(a[1]<=>b[1])}
    
    # check each file has video-stream
    stemFilesCounter = 0
    while stemFilesCounter < stemFilesPathSize.length do
      video = Videofile.init(stemFilesPathSize[stemFilesCounter][0])
    
      # If this file has video-stream and the file-size is larger than 1MB,
      # delete other stem-files
      #   The file less than 1MB might be broken.
      if ( video.getArrayVideoCodecs.length > 0 ) then
        deleteFilesNumber = stemFilesPathSize.length - stemFilesCounter -1
        deleteFilesCounter = 0
        while deleteFilesCounter < deleteFilesNumber do
           # delete the same-stem-file if its modified time is more than 10 seconds ago and the size of the least size file is larger than 1MB.
           if ( (Time.now - File.mtime(stemFilesPathSize[stemFilesCounter + deleteFilesCounter + 1][0])) > 10 ) && ( File.size(stemFilesPathSize[stemFilesCounter][0]) > 1000000 ) then
              File.delete(stemFilesPathSize[stemFilesCounter + deleteFilesCounter + 1][0]) 
           end
           deleteFilesCounter += 1
        end

        # return a filename which is not redundant.
        return stemFilesPathSize[stemFilesCounter][0]
      
      # delete no-video-stream file
      else
        File.delete(stemFilesPathSize[stemFilesCounter][0])
      end
      stemFilesCounter += 1
    end
    return nil
  end


  # get epgrec-do-record-file-mode-number of arguement
  def getEpgrecDoRecordMode
    mpeg2StreamCount = 0
    videoCodecs = getArrayVideoCodecs
    if videoCodecs.length < 1 then
      return nil
    elsif videoCodecs.length < 2 then
      videoCodec = videoCodecs[0].match(/: Video: \S+ /)
      return 1 if videoCodec.index('mpeg2video')
      return 2 if videoCodec.index('h264')
      audioCodecs = getArrayAudioCodecs
      audioCodec = audioCodecs[0].match(/: Audio: \S+ /)
      return 3 if audioCodec.index('mp3')
    else
      videoCodec = videoCodecs[0].match(/: Video: \S+ /)
      return 0
    end
  end

  
  # get array of epgrec-do-record-mode and filename of smallest-stem-file
  def getEpgrecDoRcordIdFilePathSmallestStemFile
    stemFilePath = deleteRedundantEpgrecStemFiles
    stemVideo = Videofile.init(stemFilePath)
    doRecordMode = stemVideo.getEpgrecDoRecordMode
    stemIdFileName = Array.new()
    stemIdFileName.push [doRecordMode, stemFilePath]
    return stemIdFileName
  end


  def get_epgrec_stem
    fileBasenameWithPiriod = @filebasename + "."
    fileStemWithPeriod = fileBasenameWithPiriod.match(/^20\d{12}_20\d{12}(GR|BS).*?\./).to_a[0]

    if fileStemWithPeriod != nil then
      return File.basename(fileStemWithPeriod, '.*')
    else
      return nil
    end
  end
end