【Ruby】FAT32用にmp4を4GB以内に分割する【ffmpeg】

# FAT32の壁
通常であればFAT32なんて使わずに他のフォーマットを使えばいいわけで、そしてリミットである１ファイル4GB以上の動画ファイルを扱うなんてことはあまりないと思うが…たまに、この問題に遭遇するorz

例えばFire TV(2015)で使えるmicro SDがまさにそうで、ハードウェアとしては4Kに対応しているのに4GBの上限というのは結構面倒臭い。

制約としては以下

 1. mp4の収録時間とファイルサイズでいい感じに分割する(ffmpegの-fsオプションは使わない)
 1. 再エンコードしない
 1. 分割したファイルがちゃんと再生される 

例えば1については

```bash
ffmpeg -i input.mp4 -fs ファイルサイズ output.mp4
``` 
このオプションを使えば分割できるんだけど、これじゃなくて例えば**合計が32GBで収録時間が1時間20分**なら4分割して**20分ずつの連番ファイル**にしたい。
もちろん、単純にこれをやると微妙に4GBを超えるファイルができる可能性があるのでバッファを入れる

2については、当たり前で再エンコードしてたら時間がもったいない。
3については、ffmpegのよくある罠で、オプションを書く位置や指定の仕方によって最初の数秒音声のみだけになるようなファイルができてしまうからだ。(フレーム指定すればいいんだけど、それよりも同じ分数のファイルが並ぶ方が気持ちがよいし）

## とりあえずffmpegとbashでmp4の収録時間を取得する

```bash
ffmpeg -i hogehoge.mp4 2>&1 | grep "Duration
```

これで、返ってくる

## 特定の時間でちゃんと分割
とりあえず、20分ずつ分割する例

NGな例（2番目のファイルの最初数秒がは音声のみで動画が真っ黒になる）

```bash
ffmpeg -i imput.mp4 -ss 00:00:00 -to 00:20:00 -c copy output1.mp4
ffmpeg -i imput.mp4 -ss 00:20:00 -to 00:40:00 -c copy output2.mp4
```

なので、

```bash
ffmpeg -ss 00:00:00 -i imput.mp4 -t 1200 -c copy output1.mp4
ffmpeg -ss 00:20:00 -i imput.mp4 -t 1200 -c copy output1.mp4
```

上記のようにやる必要がある。

## ということで、Rubyでサクっとやる
とりあえず3.8GBを上限とした書き殴りの[スクリプト](https://github.com/bu-kurokky/mp4split_for_fat32
)。

```rb

# coding: utf-8
require 'time'
require 'filesize'

DEBUG=false
LIMIT_GB=3.8

class Main
	def initialize(argv)
		@file = argv
		@dir_name  = File.dirname(@file)
		@base_name = File.basename(@file)
	end
	def get_total_time
		ret = `ffmpeg -i #{@file} 2>&1 | grep "Duration"`
		total_time = ret.gsub(/,.+|\n|\..+/,"").gsub(/.+Duration: /,"")
		return total_time
	end
	def get_split_count
		file_size = File.stat(@file).size
		file_size = Filesize.from("#{file_size} B").to_f('GB') + 1.0
		file_size = file_size.to_s.gsub(/\..+/,"").to_i
		split_count = (file_size/LIMIT_GB).round + 1
		return split_count
	end
	def calculation_total_minutes
		total_times_array = get_total_time.split(":").reverse
		# add buffer
		total_minutes = total_times_array[1].to_i + 1
		if total_times_array[2]
			total_minutes += total_times_array[2].to_i*60
		end
		return total_minutes
	end
	def get_setting
		setting = Hash.new
		# add buffer
		split_count   = get_split_count + 1
		total_minutes = calculation_total_minutes
		split_minute  = total_minutes/split_count
		setting['split_count']  = split_count
		setting['split_minute'] = split_minute
		return setting
	end
	def sec2hms(sec)
		time = sec
		sec = time % 60
		time /= 60
		mins = time % 60
		time /= 60
		hours = time % 24
		return [sprintf("%02d",hours), sprintf("%02d",mins), sprintf("%02d",sec)].join(":")
	end
	def split_mp4
		setting = get_setting
		setting['split_count'].times do |n|
			begin_time = sec2hms(n*setting['split_minute']*60)
			cmd = "ffmpeg -ss #{begin_time} -i '#{@file}' -t #{setting['split_minute']*60} -c copy '#{@file.gsub('.mp4',"")}_#{sprintf("%03d",n+1)}.mp4'"
			if DEBUG
				puts cmd
			else
				`#{cmd}`
			end
		end
	end
end

if !ARGV[0]
	puts "---------------------------"
	puts "Not find argv"
	puts ""
	puts "ruby #{__FILE__} target.mp4"
	puts "---------------------------"
	sleep 0.5
	exit
end
if ARGV[0] !~ /mp4$/
	puts "---------------------------"
	puts "Please choose mp4 file"
	puts "---------------------------"
	sleep 0.5
	exit
end

main = Main.new(ARGV[0])
main.split_mp4
```

サクッとやれればいいのでコーディングがアレなのは失礼