C# 調用 mkvinfo 抓取 正確 影片切割點 解決 mkvmerge 切割後出現跳幀 與 切割大小暴表的錯誤

 正常使用mkvmerge切割影片時他會抓取影片的關鍵幀位置進行切割 這本來是很正常的

不過 mkv 這個檔案有個叫做叢集的東西 如果關幀與叢集時間不一致,就會導致你播放切割影片的第一個時間點是在關鍵幀的時間上,可是你的第一個關鍵幀的時間並不是0,這會導致你關鍵幀前面的畫面都被跳過了,所以正確能切割的點因該是叢集時間與關鍵幀時間要一致這才是能切割的點。

本次C#代碼只做簡單計算,只能對單一軌道是Video的進行切割如果要同時完美切割音軌需要更複雜的計算加上我暫時用不到就沒寫~不過原理一樣就是找音軌與視訊軌的關鍵幀要同時一致並且與叢集時間一致。

==========分隔線==========

代碼展示

public static string[] 切割點計算(string VideoPath,string 輸出資料位置,long 切割大小上限)
{
    Console.WriteLine($"切割大小上限:{切割大小上限 / (1024.0 * 1024.0):0.00} MB");
    long fileSize = new FileInfo(VideoPath).Length; // 獲取檔案大小,單位是位元組(bytes)
    int 分割數量 = (int)Math.Ceiling(fileSize / (double)切割大小上限);// 计算段数
    long 平均大小 = fileSize / 分割數量;
    Console.WriteLine($"分割數量:{分割數量}");
    Console.WriteLine($"平均大小:{平均大小 / (1024.0 * 1024.0):0.00} MB");
    #region 解析叢集
    if (!mkvinfo(VideoPath, 輸出資料位置))
        throw new InvalidOperationException("mkvinfo無法生成輸出");
    List<mkv叢集資料> clusters = new List<mkv叢集資料>();
    string[] lines = File.ReadAllLines(輸出資料位置);
    for (int i = 0; i < lines.Length; i++)
    {
        if (lines[i].StartsWith("|+ Cluster"))
        {
            mkv叢集資料 currentCluster = new mkv叢集資料();
            currentCluster.資料大小 = 抓取叢集資料大小(lines[i]);
            if (lines[++i].StartsWith("| + Cluster timestamp:"))
            {
                currentCluster.時間 = 抓取叢集時間(lines[i]);
            }
            else
                throw new InvalidOperationException("叢集時間格式不對");
            if (lines[++i].StartsWith("| + Simple block: key"))
            {
                if (currentCluster.時間 == 抓取樣本區間關鍵幀時間(lines[i]))
                    currentCluster.關鍵叢集 = true;
            }
            else if (!lines[i].StartsWith("| + Simple block:"))
                throw new InvalidOperationException("樣本區間格式不對");
            clusters.Add(currentCluster);
        }
    }
    #endregion
    #region 計算切割點
    重新計算點:
    int currentIndex = 0;
    List<string> 切割時間點 = new List<string>();
    for (int i = 0; i < 分割數量; i++)
    {
        int startIndex = currentIndex;
        long currentSegmentSize = 0;

        if (i == 分割數量 - 1 && currentIndex < clusters.Count)
        {
            while (currentIndex < clusters.Count)
            {
                currentSegmentSize += clusters[currentIndex].資料大小;
                currentIndex++;
            }
            // 如果是最后一笔数据,且还有叢集未分配,则把剩下的所有叢集分配给最后一笔
            Console.WriteLine($"叢集區塊範圍:{startIndex,+5}-{clusters.Count - 1,-5} 叢集區塊大小:{currentSegmentSize / (1024.0 * 1024.0):0.00} MB");
            if(currentSegmentSize > 切割大小上限)
            {
                Console.WriteLine($"因最後一筆資料超過最大上限,故增加分割數量重新計算");
                平均大小 = fileSize / (++分割數量);
                Console.WriteLine($"分割數量:{分割數量}");
                Console.WriteLine($"平均大小:{平均大小 / (1024.0 * 1024.0):0.00} MB");
                goto 重新計算點;
            }
            break;
        }
        while (currentIndex < clusters.Count && (currentSegmentSize < 平均大小 || !clusters[currentIndex].關鍵叢集))
        {
            currentSegmentSize += clusters[currentIndex].資料大小;
            currentIndex++;
        }

        // 确保分割点是关键帧叢集且当前段不超过上限大小
        while (currentIndex < clusters.Count && (!clusters[currentIndex].關鍵叢集 || currentSegmentSize > 切割大小上限))
        {
            currentIndex--;
            currentSegmentSize -= clusters[currentIndex].資料大小;
        }
        Console.WriteLine($"叢集區塊範圍:{startIndex,+5}-{currentIndex,-5} 叢集區塊大小:{currentSegmentSize / (1024.0 * 1024.0):0.00} MB");
        切割時間點.Add(clusters[currentIndex].時間);
    }
    #endregion
    return 切割時間點.ToArray();
}
static long 抓取叢集資料大小(string input)
{
    string pattern = @"data size (\d+)";
    Match match = Regex.Match(input, pattern);

    if (match.Success)
        return long.Parse(match.Groups[1].Value);
    else
        throw new InvalidOperationException("Data size not found in the input string.");
}
static string 抓取叢集時間(string input)
{
    string pattern = @"Cluster timestamp: (\d{2}:\d{2}:\d{2}\.\d{9})";
    Match match = Regex.Match(input, pattern);

    if (match.Success)
        return match.Groups[1].Value;
    else
        throw new InvalidOperationException("Cluster timestamp not found in the input string.");
}
static string 抓取樣本區間關鍵幀時間(string input)
{
    string pattern = @"timestamp (\d{2}:\d{2}:\d{2}\.\d{9})";
    Match match = Regex.Match(input, pattern);

    if (match.Success)
        return match.Groups[1].Value;
    else
        throw new InvalidOperationException("Timestamp not found in the input string.");
}
class mkv叢集資料
{
    public string 時間 { get; set; }
    public bool 關鍵叢集 { get; set; }
    public long 資料大小 { get; set; }
}
public static bool mkvinfo(string videoPath, string outputPath)
{
    // 建立 ProcessStartInfo 物件來設定執行外部命令的參數
    ProcessStartInfo startInfo = new ProcessStartInfo
    {
        FileName = "mkvinfo",//使用程式
        Arguments = $"-t -z --ui-language en --output-charset UTF8 -r \"{outputPath}\" \"{videoPath}\"",//使用的參數
        UseShellExecute = false,//因該是禁用 SHell 改用cmd巴
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        WindowStyle = ProcessWindowStyle.Hidden,
        CreateNoWindow = true,
        StandardOutputEncoding = new System.Text.UTF8Encoding(false)//解決CMD 中文亂碼
    };

    // 建立 Process 物件並執行外部命令
    using (Process process = new Process())
    {
        process.StartInfo = startInfo;

        process.Start();

        // 等待外部程式結束
        process.WaitForExit();
    }
    return File.Exists(outputPath);
}
==========分隔線==========
大致上是這樣代碼我就不在精簡了 這就是我程式中的代碼~
還有 我這程式碼 在計算檔案大小 是按照你給的上限 去計算大致會產生多少分割檔 然後再把容量平均給每個檔案,這樣的好處就是最後一個檔案不會檔案很小,並且把最大檔案的大小拉低了可以加速讀取

留言