LoginSignup
2
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-03-10

FAT32の壁

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

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

制約としては以下

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

例えば1については

ffmpeg -i input.mp4 -fs ファイルサイズ output.mp4

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

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

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

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

これで、返ってくる

特定の時間でちゃんと分割

とりあえず、20分ずつ分割する例

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

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

なので、

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を上限とした書き殴りのスクリプト


# 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

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0