youtube download アプリ作成依頼があった話

この記事は「信州大学 kstm Advent Calendar 2019」18日の投稿です。

# 背景
従兄弟が高校の卒業関係でスライドにyoutubeの動画を貼りたいらしい。PC初心者の彼(windows)を覗いてみるとブラウザに変な通知を許可してる…　なにか変なサイト使ってるな…　どげんかせんといかん
## おすすめる
コンソールアプリであるyoutube-dlというものがwindows向けにある。あ勧めしたが、コンソールに不慣れな彼、黒い画面はウイルス教の信者だった。
## 思い立つ
何かGUIのアプリを作ってやろう、と。
僕には実力がないので動画のキャッシュを取るだの断片の動画を繋ぐだのの技術はない。ネットワーク側の操作を要するアプリ制作未経験。
ということでyoutube-dl,コンソールアプリをGUIで操作しようと。
# 算段
WPFのデザインが使いやすいだろうからC#で挑戦。
System.Diagnostics.Process...コンソールアプリを操作する上で使うやつ。
youtubeAPI...「名前を付けて保存」でファイル名を指定したいので、デフォルトは動画名を取ってきたい。なのでAPIキーを取っておく。
System.Net.HttpStatusCode...URLを受け取る際、それがURLであるか判別したい。
# 機能
- URL入力
- フォーマットMP4、MP3
- ダウンロードできたか確認のプレーヤー

# 最低限
原則、正常なyoutubeURLをMP4を「名前を付けて保存」出来れば良い。それ以外の機能はやっつけである。
# ソースコード
その他機能はいろんな記事の継ぎ接ぎであり、機能はするが無駄が多くソース(特に不揃いなコメント)が汚いので、事細かに解説したいと思う。
## GUI
XAMLでデザインされたGUI。
- URL入力のテキストボックス
- ステータス表示用テキストボックス
- ファイル形式変換用のコンボボックス
- ダウンロードボタン
- 動画表示用のメディアエレメント
- 再生、停止ボタン

## ソース

patched,patchedしてたら1ファイルになってた。

```c#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.Diagnostics;
using System.Windows.Forms;
using System.Windows;
using System.Text.RegularExpressions;
using System.Net;
using System.Windows.Threading;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {       
        public string exeFile = @"youtube-dl.exe";
        public string place = "";
        public string url = "default";
        //
        // 動画の相対パス
        public string fileName="";
        public string fileFormat = ".mkv";//デバクの関係で初期値を矯正
        private string _videoPath ="";
        
        public MainWindow()
        {
            InitializeComponent();
            

        }
        private void ClickBotton1(object sender, RoutedEventArgs e)// ダウンロードボタンが押されたら
        {
            string url = urlText.Text;//URLテキストボックス
            if (String.IsNullOrEmpty(url)) { statusBox.AppendText("\nURLを入力してください\n"); return; }//何も書かれてなければやめる。
            if (IsUrl(url)==false) { statusBox.AppendText("\nURLじゃないです\n"); return; }//urlじゃなきゃやめる。
            WebRequest.DefaultWebProxy = null;
            HttpStatusCode statusCode = GetStatusCode(url);
            int code = (int)statusCode;
            if(code >= 400)
            {
                //この場合URLが無効
                statusBox.AppendText("\nURLが無効です\n");
                return;
            }

            string fomatStr="";
            string fomatSelectedItem = fomatSelect.Text;// ファイル形式選択コンボボックス
            string title = "new_video";
            //タイトルを取得する
            string apiKey = "youtubeのAPIキー";
            string youtubeID = Regex.Match(url, @".?v=...........").Value;//ここやばｗ
            
            if(youtubeID!=null)
                youtubeID = youtubeID.Substring(3);
            else
                youtubeID = Regex.Match(url, @"[0-9]+$").Value;

            Console.WriteLine(youtubeID);

            if (Regex.IsMatch(youtubeID, @"[0-9a-zA-Z-_]{11}"))
            {
                //APIを叩く
                WebClient wc = new WebClient();
                string youtubeDataJson = Encoding.UTF8.GetString(wc.DownloadData($"https://www.googleapis.com/youtube/v3/videos?id={youtubeID}&key={apiKey}&part=snippet&fields=items(snippet(title))"));

                //最後の"までを削除
                youtubeDataJson = youtubeDataJson.Remove(youtubeDataJson.LastIndexOf("\""));

                //最後の"から後だけ残す(これで動画名の取得)
                title = youtubeDataJson.Substring(youtubeDataJson.LastIndexOf("\"") + 1);

            }

            //SaveFileDialogを生成する
            SaveFileDialog saFiDlog = new SaveFileDialog();
            saFiDlog.Title = "ファイルを保存する";
            saFiDlog.InitialDirectory = place;
            //System.Windows.MessageBox.Show(fomatSelectedItem);
            if (fomatSelectedItem == "変換なし")
            {
                fomatStr = "";
                saFiDlog.FileName = $@"{title}";
                saFiDlog.Filter = "すべてのファイル(*.*)|*.*";
                saFiDlog.FilterIndex = 1;

            } else if (fomatSelectedItem == "動画ファイル（mp4）")
            {
                fomatStr = "--recode-video mp4 ";
                fileFormat = ".mp4";
                saFiDlog.FileName = $@"{title}";
                saFiDlog.Filter = "ビデオファイル(*.mp4)|*.mp4|すべてのファイル(*.*)|*.*";
                saFiDlog.FilterIndex = 1;

            } else if (fomatSelectedItem == "オーディオファイル（mp3）")
            {
                fomatStr = "-x --audio-format mp3 ";
                fileFormat = ".mp3";
                saFiDlog.FileName = $@"{title}";
                saFiDlog.Filter = "オーディオファイル(*.mp3)|*.mp3|すべてのファイル(*.*)|*.*";
                saFiDlog.FilterIndex = 1;

            }

            //オープンファイルダイアログを表示する
            DialogResult result = saFiDlog.ShowDialog();

            if (result == System.Windows.Forms.DialogResult.OK)
            {
                //「保存」ボタンが押された時の処理
                statusBox.AppendText($"DL中：\n{title}\n");
                fileName = saFiDlog.FileName;//こいつはパス込みっぽい。
                place = saFiDlog.FileName;
                ProcessStartInfo startInfo = new ProcessStartInfo();
                startInfo.FileName = exeFile;
                //startInfo.CreateNoWindow = false;  //これがウィンドウ作るか
                //startInfo.UseShellExecute = false; //この2つ有効化でウイルスの黒いウィンドウ消える。
                

                startInfo.Arguments = $@"{fomatStr}-o ""{fileName}""" + " " + url;
                System.Diagnostics.Process p = System.Diagnostics.Process.Start(startInfo);
                
                p.WaitForExit();
                statusBox.AppendText($"完了：\n{title}\n"); //思ってたんと違う挙動


                //


                //

            }
            else if (result == System.Windows.Forms.DialogResult.Cancel)
            {
                //「キャンセル」ボタンまたは「×」ボタンが選択された時の処理
            }

            
        }
        //

       

        private void ButtonPlay_Click(object sender, RoutedEventArgs e)//再生ボタン
        {
            // 動画を再生
            if (fileFormat == ".mp3") { statusBox.AppendText("\nmp3は未対応です\n"); return; }
            _videoPath = fileName + fileFormat;
            //
            //問題：デフォルトでダウンロードした動画のフォーマットが不明である。
            //そのため最初のmkvであると力ずくでやってる。
            //MP4なら問題ない。
            //
            if (System.IO.File.Exists(_videoPath))
            {
            
            MediaElementMovie.Source = new Uri(_videoPath, UriKind.Relative);
            MediaElementMovie.Play();
            }
        }

        

        private void ButtonStop_Click(object sender, RoutedEventArgs e)//停止ボタン
        {
            // 動画を停止
            
            MediaElementMovie.Stop();
            MediaElementMovie.Source = null;
        }

        

        //

        
        static public HttpStatusCode GetStatusCode(string url)
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
            HttpWebResponse res = null;
            HttpStatusCode statusCode;
            try
            {
                res = (HttpWebResponse)req.GetResponse();
                statusCode = res.StatusCode;
            }catch(WebException ex)
            {
                res = (HttpWebResponse)ex.Response;
                if (res != null)
                {
                    statusCode = res.StatusCode;
                }
                else throw;
            }
            finally
            {
                if (res != null)
                {
                    res.Close();
                }
            }
            return statusCode;
        }
        public static bool IsUrl(string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                return false;
            }
            return Regex.IsMatch(
               input,
               @"^s?https?://[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+$"
            );
        }
    }
}
```

API入力で動くはず
# 依頼を納める(まとめ)
ダウンロード中に別の動画をダウンロードできない。
ダウンロード進行状態を確認できない。(コンソールアプリがエラーしていた場合気づけ無い)
等問題あるがMP4でのダウンロードは問題無い。
改良の余地はアリアリなので暇があれば更新(個人的にはコンソールで使うのでイラン)