旅行好きなソフトエンジニアの備忘録

プログラミングや技術関連のメモを始めました

【WPF】 簡易動画プレーヤーを作成する

今回は動画のプレーヤーを作ってみます(簡易的なものですが)。調べてみるとWPFにはMediaElementというものがあって、これを使ってみると良さそうです。
まずはxamlが以下のようになります。

<Window x:Class="CSMediaPlayer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CSMediaPlayer"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="6*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <MediaElement Name="MediaElementMovie" Grid.Row="0" Stretch="Fill" LoadedBehavior="Manual" UnloadedBehavior="Manual"/>
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="4*"/>
            </Grid.ColumnDefinitions>
            <Button Name="ButtonPlay" Grid.Column="0" Content="▶" Click="ButtonPlay_Click"/>
            <Button Name="ButtonPause" Grid.Column="1" Content="||" Click="ButtonPause_Click"/>
            <Button Name="ButtonStop" Grid.Column="2" Content="■" Click="ButtonStop_Click"/>
            <Grid Grid.Column="3">
                <Slider Name="SliderMoviePosition" VerticalAlignment="Center" Minimum="0" Maximum="100" ValueChanged="SliderMoviePosition_ValueChanged"/>
            </Grid>
        </Grid>
    </Grid>
</Window>

上記のXAMLでこんな感じの見た目になります。あとはボタンやスライダーにイベントを割り振っていきます。
f:id:ni4muraano:20170730170109p:plain

using System;
using System.Windows;
using System.Windows.Threading;

namespace CSMediaPlayer
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        // 動画の相対パス
        private readonly string _videoPath = "video.avi";
        // スライダーを動かすためのタイマー
        private DispatcherTimer _timer = new DispatcherTimer();
        // タイマー起動間隔(1秒)
        private const int _timerInterval = 1;
        // 動画経過時間
        private int _elapsedSec = 0;
        // スライダーをユーザーが動かしたかどうか判別するフラグ
        private bool _sliderValueChangedByProgram = false;

        public MainWindow()
        {
            InitializeComponent();

            _timer.Interval = new TimeSpan(0, 0, _timerInterval);
            _timer.Tick += dispatcherTimer_Tick;
        }

        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            // 動画経過時間に合わせてスライダーを動かす
            _elapsedSec += _timerInterval;
            double totalSec = MediaElementMovie.NaturalDuration.TimeSpan.TotalSeconds;
            SliderMoviePosition.Value = _elapsedSec / totalSec * SliderMoviePosition.Maximum;
            _sliderValueChangedByProgram = true;
        }

        private void ButtonPlay_Click(object sender, RoutedEventArgs e)
        {
            // 動画を再生
            _timer.Start();
            MediaElementMovie.Source = new Uri(_videoPath, UriKind.Relative);
            MediaElementMovie.Play();
        }

        private void ButtonPause_Click(object sender, RoutedEventArgs e)
        {
            // 動画を一次停止
            _timer.Stop();
            MediaElementMovie.Pause();
        }

        private void ButtonStop_Click(object sender, RoutedEventArgs e)
        {
            // 動画を停止
            _timer.Stop();
            _elapsedSec = 0;
            SliderMoviePosition.Value = 0;
            MediaElementMovie.Stop();
            MediaElementMovie.Source = null;
        }

        private void SliderMoviePosition_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (!_sliderValueChangedByProgram)
            {
                // スライダーを動かした位置に合わせて動画の再生箇所を更新する
                double totalSec = MediaElementMovie.NaturalDuration.TimeSpan.TotalSeconds;
                double sliderValue = SliderMoviePosition.Value;
                int targetSec = (int)(sliderValue * totalSec / SliderMoviePosition.Maximum);
                _elapsedSec = targetSec;
                TimeSpan ts = new TimeSpan(0, 0, 0, targetSec);
                MediaElementMovie.Position = ts;
            }
            _sliderValueChangedByProgram = false;
        }
    }
}


参考にしたサイトはここです(方法 : MediaElement (再生、一時停止、停止、ボリューム、および速度) を制御する )。サイトでは様々なイベントを定義しているので、きちんと作るのであれば上記コードは改善しないといけなさそうです。。。

【WPF】 スクリーンショットを撮り続けて動画に保存する

タイトルにあるようにスクリーンショットを撮り続けて動画に保存する方法のメモをします。
まずはスクリーンショットを撮る部分ですが、スクリーンショットをそのまま動画にすると容量がすごいことになるので、スクリーンショットを撮る⇒縮小するまでを作成します。

// Bitmapクラスを使うため参照の追加が必要
using System.Drawing;

private Bitmap GetScreenShot(int width, int height)
{
    var resizedBmp = new Bitmap(width, height);
    using (var bmp = new Bitmap((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight))
    using (Graphics g = Graphics.FromImage(bmp))
    using (Graphics resizedG = Graphics.FromImage(resizedBmp))
    {
        // スクリーンショットを撮る
        g.CopyFromScreen(new System.Drawing.Point(0, 0), new System.Drawing.Point(0, 0), bmp.Size);

        // 動画サイズを減らすためリサイズする
        resizedG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Bilinear;
        resizedG.DrawImage(bmp, 0, 0, width, height);
    }

    return resizedBmp;
}


一定時間ごとに上記メソッドを呼び出す必要があるので、DispatcherTimerクラスを利用します。動画保存はAccord.Video.VFWのAVIWriterクラスを利用します。Accord.Video.VFWはNuGETを使ってインストールします。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;

using Accord.Video.VFW;
using System.Drawing;
using System.Windows.Threading;

namespace CSMovie
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly DispatcherTimer _timer = new DispatcherTimer();
        private readonly AVIWriter _aviWriter = new AVIWriter();
        // フレームレート
        private readonly int _frameRate = 15;
        // 動画の横サイズ
        private readonly int _videoWidth = 640;
        // 動画の縦サイズ
        private readonly int _videoHeight = 480;
        private readonly List<Task> _tasks = new List<Task>();

        public MainWindow()
        {
            InitializeComponent();

            InitializeDispatcherTimer();

            InitializeVideo();
        }

        private void InitializeDispatcherTimer()
        {
            // フレームレートに合わせて起動時間を設定する
            int interval_msec = 1000 / _frameRate;
            _timer.Interval = new TimeSpan(0, 0, 0, 0, interval_msec);
            _timer.Tick += dispatcherTimer_Tick;
        }

        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            // 時間がかかる処理なのでバックグラウンドで実行する
            _tasks.Add(
                Task.Run(() =>
                {
                    // スクリーンショットを撮る
                    using (Bitmap bmp = GetScreenShot(_videoWidth, _videoHeight))
                    {
                        // フレームに追加する
                        _aviWriter.AddFrame(bmp);
                    }
                }));
        }

        private void InitializeVideo()
        {
            _aviWriter.FrameRate = _frameRate;
            _aviWriter.Open("video.avi", _videoWidth, _videoHeight);
        }

        private void Window_ContentRendered(object sender, EventArgs e)
        {
            _timer.Start();
        }

        private Bitmap GetScreenShot(int width, int height)
        {
            var resizedBmp = new Bitmap(width, height);
            using (var bmp = new Bitmap((int)SystemParameters.PrimaryScreenWidth, (int)SystemParameters.PrimaryScreenHeight))
            using (Graphics g = Graphics.FromImage(bmp))
            using (Graphics resizedG = Graphics.FromImage(resizedBmp))
            {
                // スクリーンショットを撮る
                g.CopyFromScreen(new System.Drawing.Point(0, 0), new System.Drawing.Point(0, 0), bmp.Size);

                // 動画サイズを減らすためリサイズする
                resizedG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Bilinear;
                resizedG.DrawImage(bmp, 0, 0, width, height);
            }

            return resizedBmp;
        }

        private void Window_Closed(object sender, EventArgs e)
        {
            // タイマーを止める
            _timer.Stop();
            // 動作中のタスクがあれば待つ
            foreach (var task in _tasks)
            {
                task.Wait();
            }
            if (_aviWriter != null)
            {
                _aviWriter.Close();
                _aviWriter.Dispose();
            }
        }
    }
}


ちなみにスクリーンショットを撮る部分はここを参考にしました。

fieldlight.blog126.fc2.com

【Python】 PythonでC#のFileSystemWatcher相当のクラスを作成する

C#側がファイルを作成し、それをトリガとしてpythonが起動するようにしたいと思っています。そのため、C#のFileSystemWatcherに相当するようなクラスが欲しいと思ったのですが、以下のサイトに書かれていました。 qiita.com

import time
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler

class MyFileEventHandler(PatternMatchingEventHandler):
    def __init__(self, patterns):
        super(MyHandler, self).__init__(patterns=patterns)

    def on_moved(self, event):
        # ファイルが移動した時に実行されるメソッド
        print('file moved')

    def on_created(self, event):
        # ファイルが作成された時に実行されるメソッド
        print('file created')

    def on_deleted(self, event):
        # ファイルが削除された時に実行されるメソッド
        print('file deleted')

    def on_modified(self, event):
        # ファイルが更新された時に実行されるメソッド
        print('file modified')

# MyFileEventHandlerの使い方例
if __name__ == '__main__':
    # イベントを発生させるファイルパターン
    file_pattern_to_watch = '*.txt'
    # MyFileEventHandlerを生成して
    event_handler = MyFileEventHandler(file_pattern_to_watch)
    # Observerに登録する
    file_system_watcher = Observer()
    file_system_watcher.schedule(event_handler, directory_to_watch, recursive=False)
    # 監視を開始
    file_system_watcher.start()
    # 指定時間待つ
    time_out_sec = 30
    file_system_watcher.join(time_out_sec)
    # タイムアウトが起きたかどうか調べる
    if file_system_watcher.isAlive():
        # メモ:実際にタイムアウトが起きたらkillするしかない?
        print('timeout happened')

【WPF】 透明なウインドウの作り方

XAMLのWindowタグに下記3つを加えるだけです。作ってみると完全に透明なウインドウが作成され、ウインドウ右上部の×ボタンも消えてしまいました。そのため、例えばEscボタンを押したらWindowをCloseするような処理が必要でした。

  • WindowStyle=“None”
  • AllowsTransparency=“True”
  • Background=“Transparent”


参考にしたサイトは以下です。

www.atmarkit.co.jp

【Python】 動的なデフォルト引数を指定するときにはNoneとドキュメンテーション文字列を使う

書籍”Effective Python”項目15のメモです。Pythonを始めて半年経ちますが、真面目に言語自体の勉強をしていなかったので空き時間使ってまだ知らなかった事のメモをします。

Pythonはデフォルト引数をモジュールロード時の一回しか評価しないので、動的な値をデフォルト引数にはしないように、という話でした。

from datetime import datetime
import time

# 動的な型をデフォルト引数にしてみる
def bad_example(when=datetime.now()):
    print(when)

# 時刻が表示される
bad_example()
# 一秒待つ
time.sleep(1)
# 上と同じ時刻が表示される
bad_example()

書籍ではこのような時はデフォルト引数にNoneを指定してドキュメンテーション文字列で振る舞いを書いておくことを勧めています。

def good_example(when=None):
    """
    when: datetime of when the function is called.
          Defaults to the present time.
    """

    when = datetime.now() if when is None else when
    print(when)

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目

【Python】 クロージャ内外で変数を共有する

書籍”Effective Python”項目15のメモです。Pythonを始めて半年経ちますが、真面目に言語自体の勉強をしていなかったので空き時間使ってまだ知らなかった事のメモをします。

Pythonにはnonlocalというキーワードがあり、これによりクロージャ内外でデータを共有できるようです。

def outer_func1():
    found = False
    def inner_func():
        # outer_func1のfoundとは別の変数
        found = True
    inner_func()
    print(found)
# Falseと表示される
outer_func1()

def outer_func2():
    found = False
    def inner_func():
        # 変数foundを外と共有
        nonlocal found
        found = True
    inner_func()
    print(found)
# Trueと表示される
outer_func2()

Effective Python ―Pythonプログラムを改良する59項目

Effective Python ―Pythonプログラムを改良する59項目