C#でmp4ファイルのチャプターテキストを作成するアプリ

コンピュータ
mp4ファイルを読み込んでチャプターを作成しテキストファイルとして保存するアプリです。
作成したテキストファイルはフリーウェアソフト「Drax」でmp4ファイルにチャプターをセットします。
Releases · stevie910/drax
Contribute to stevie910/drax development by creating an account on GitHub.
スポンサーリンク

実行環境構築

プロジェクトの作成

mkdir ChapterEditor
cd ChapterEditor
dotnet new winforms
dotnet add package OpenCvSharp4.Windows
dotnet add package OpenCvSharp4.Extensions
dotnet add package OpenCvSharp4.runtime.win
code .

ソースプログラム

namespace ChapterEditor;

using System;
using System.Windows.Forms;

using OpenCvSharp;
using OpenCvSharp.Extensions;

public partial class Form1 : Form
{
    SplitContainer splitContainer = new SplitContainer
    {
        Dock = DockStyle.Fill,
        Panel1MinSize = 100,
    };
    PictureBox picbox = new PictureBox
    {
        Dock = DockStyle.Fill,
        AllowDrop = true,
        SizeMode = PictureBoxSizeMode.Zoom,
    };
    FlowLayoutPanel panel = new FlowLayoutPanel
    {
        Dock = DockStyle.Bottom,
    };
    TrackBar trackBar = new System.Windows.Forms.TrackBar
    {
        Value = 1,
        Maximum = 1,
        Enabled = false,
    };
    Button addButton = new Button
    {
        Width = 100,
        Height = 40,
        Text = "Add",
        Enabled = false,
    };
    Button saveButton = new Button
    {
        Width = 100,
        Height = 40,
        Text = "Save",
        Enabled = false,
    };
    ListBox chapterList = new ListBox
    {
        Dock = DockStyle.Fill,
        Sorted = true,
        Enabled = false,
    };
    VideoCapture? vc = null;
    string path = "";
    string chapter_text_path = "";

    string getChapterItem()
    {
        if (vc == null) return "";
        int msec = (int)((1000.0/vc.Fps) * trackBar.Value);
        var ts = new TimeSpan(0,0,0,0,msec);
        var s = ts.ToString(@"hh\:mm\:ss\.fff") + " " + trackBar.Value;
        return s;
    }
    private void readFrame()
    {
        if (vc == null) return;
        using var mat = new Mat();
        if (!vc.Read(mat)) return;
        if (!mat.IsContinuous()) return;
        picbox.Invoke(new Action(() =>  {
            if (picbox.Image != null) {
                picbox.Image.Dispose();
            }
            picbox.Image = BitmapConverter.ToBitmap(mat);
        }));
        Text = getChapterItem();
    }
    public Form1()
    {
        InitializeComponent();

        Controls.Add(splitContainer);
        splitContainer.Panel1.Padding = new Padding(10);
        splitContainer.Panel2.Padding = new Padding(10);
        splitContainer.Panel1.Controls.Add(picbox);
        splitContainer.Panel2.Controls.Add(chapterList);
        panel.Controls.Add(trackBar);
        panel.Controls.Add(addButton);
        panel.Controls.Add(saveButton);
        Controls.Add(panel);

        trackBar.ValueChanged += (s, e) => {
            if (vc == null) return;
            vc.PosFrames = trackBar.Value;

            readFrame();
        };
        picbox.DragEnter += (o, e) => {
            if (e.Data == null) return;
            if(!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
            e.Effect = DragDropEffects.Copy;
        };
        picbox.DragDrop += async (sender, e) => {
            // ファイルドロップ
            if (e.Data == null) return;
            if(!e.Data.GetDataPresent(DataFormats.FileDrop)) return;
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
            if (files.Length < 1) return;
            path = files[0];

            // 読み込み
            if (picbox.Image != null) picbox.Image.Dispose();
            if (vc != null) vc.Dispose();
            await Task.Run(()=>{
                vc = new VideoCapture(path);
            });
            if (vc== null || !vc.IsOpened()) return;

            trackBar.Maximum = vc.FrameCount;
            trackBar.Value = (trackBar.Value > 0) ? 0 : trackBar.Maximum;

            var filename = Path.GetFileNameWithoutExtension(path) + ".chapter.txt";
            chapter_text_path = Path.GetDirectoryName(path) + "\\" + filename;
            chapterList.Items.Clear();
            if (File.Exists(chapter_text_path)) {
                using var sr = new StreamReader(chapter_text_path);
                var line = "";
                while ((line = sr.ReadLine()) != null)
                    chapterList.Items.Add(line);
            }
            else {
                chapterList.Items.Add("00:00:00.000 0");
            }

            chapterList.Enabled = true;
            trackBar.Enabled = true;
            addButton.Enabled = true;
            saveButton.Enabled = true;
        };
        addButton.Click += (sender, e) => {
            chapterList.Items.Add(getChapterItem());
        };
        saveButton.Click += (sender, e) => {
            using var sw = new StreamWriter(chapter_text_path);

            foreach(var line in chapterList.Items)
                sw.WriteLine(line);
        };
        chapterList.SelectedIndexChanged += (sender, e) => {
            if (chapterList.SelectedItem == null) return;
            
            var s = chapterList.SelectedItem.ToString();
            if (s == null) return;
            string[] arr = s.Split(' ');
            if (arr.Length < 2) return;
            trackBar.Value = int.Parse(arr[1]);
        };
        chapterList.KeyDown += (sender, e) => {
            if (chapterList.Enabled == false || chapterList.SelectedIndex < 1) return;
            if (e.KeyCode == Keys.Delete) chapterList.Items.RemoveAt(chapterList.SelectedIndex);
        };
        this.Load += (sender, e) => {
            trackBar.Width = (int)(panel.Width * 0.7);
        };
        this.Resize += (sender, e) => {
            addButton.Width = (int)(panel.Width * 0.1);
            saveButton.Width = (int)(panel.Width * 0.1);
            trackBar.Width = (int)(panel.Width * 0.7);
        };
    }// Form1
}// class

実行

dotnet run

使い方

mp4ファイルをドラッグアンドドロップします。
トラックバーを動かすと連動して動画の位置が移動します。
「Add」ボタンを押すと現在位置でチャプターアイテムが追加されます。
チャプターアイテムを選択するとトラックバーが移動します。
チャプターアイテムを選択した状態で「Delete」キーを押すとアイテムが削除されます。
「Save」ボタンを押すとチャプターがテキストファイルとして保存されます。保存場所は動画ファイルと同じ場所に拡張子が.chapter.txtで保存されます。
保存したテキストファイルは「Drax」で動画ファイルにチャプターをセットします。

コメント