Flutter / 動画をダウンロードする。(YouTubeやオープンソースのMP4動画)
前書き
こんにちは、えんでばーです。
初めて記事を書いてみようと思ったので、
記事を書いています。(小泉構文?)
動画を自分のアプリのディレクトリを作り、ファイルを作りその中に動画をダウンロードした、
保存する方法を説明していこうと思います。
今回のコードはIOSでの実装となる為androidでは多少異なる場合があると思いますが、
暇だったらandroidの方の実装も書いていこうと思います。
今回ダウンロードする動画
- オープンソースのMP4動画
- youtubeにアップロードされている動画
この2つです。
では実際に説明していきます。
オープンソースのMP4動画をダウンロードする方法
※iosの実装となります。
オープンソースの動画をダウンロードする方法は二つあり(自分が知っている中で)
一つはflutter_downloaderとpath_providerとpermission_handlerを使い、
実装する方法ですが、色々とセットアップなど面倒なんで簡単なもう一つの方法の手順を紹介します。
必要なライブラリ
ファイルシステムで一般的に使用される場所を見つけるためのFlutterプラグイン。
Dart用の強力なHttpクライアント。インターセプター、グローバル構成、FormData、リクエストのキャンセル、ファイルのダウンロード、タイムアウトなどをサポートします。
この二つです。
をpubspec.yamlに入れましょう!
iosのセットアップ
まずはxcodeを開いて
開き方はプロジェクトのiosを右クリックで
ここ押せば開ける。
そしてInfo.plistのソースコードに
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
を追加しましょう!
ちなみにInfo.plistが
こんな感じになってる方いたら、
こうやったらソースコードまでいけます
実装
ここからは簡単で、
全文
final imgUrl = "https://flutter.github.io/assets-for-apidocs/assets/videos/butterfly.mp4";
bool downloading = false;
var progressString = "";
Future<void> downloadFile() async {
Dio dio = Dio();
try {
var dir = await getApplicationDocumentsDirectory();
final path = dir.path;
final directory = Directory('$path/video/');
await directory.create(recursive: true);
await dio.download(imgUrl, "${dir.path}/demo.mp4",
onReceiveProgress: (rec, total) {
print("Rec: $rec , Total: $total");
setState(() {
downloading = true;
progressString = ((rec / total) * 100).toStringAsFixed(0) + "%";
});
});
} catch (e) {
print(e);
}
setState(() {
downloading = false;
progressString = "Completed";
});
print("Download completed");
}
コードの説明
まずは自分のアプリ(プロジェクト)のディレクトリを取ってくる
final dir = await getApplicationDocumentsDirectory();
final path = dir.path;
保存したディレクトリの名前を決める、無ければつくる
final directory = Directory('$path/video/');
await directory.create(recursive: true);
今回はvideoディレクトリを作ったよ
directory.createSync(recursive: true);
ってやつもあるから調べてみてね。
第一引数にURL,第二引数に保存したい動画ファイルの名前
await dio.download(imgUrl, "${dir.path}/demo.mp4",
onReceiveProgress: (rec, total) {
setState(() {
downloading = true;
progressString = ((rec / total) * 100).toStringAsFixed(0) + "%";
});
});
onReceiveProgress()は進捗状況
toStringAsFixed(0)は少数点以下切り捨て
できる人は状態変化をsetState((){})じゃない方がいいよ
説明はこれぐらいいいかな。
あとは自分のデバイスのFiles -> 自分のiphone -> (アプリ)プロジェクトの名前 -> video
の中に入っていると思います。
YouTubeの動画をダウンロード
Youtubeの動画をダウンロードするのはちょっと難しいですが頑張っていきましょう!
基本的にオープンソースの動画のダウンロードとのやつと変わらないよ!
ドキュメント読める方はそっちみた方が早いかもね!
必要なライブラリ
ファイルシステムで一般的に使用される場所を見つけるためのFlutterプラグイン。
YoutubeExplodeは、YouTube動画、再生リスト、チャンネルのメタデータをクエリしたり、動画ストリームやクローズドキャプショントラックを解決してダウンロードしたりするためのインターフェースを提供するライブラリです。抽象化レイヤーの背後で、ライブラリは生のページコンテンツを解析し、リバースエンジニアリングされたAJAXリクエストを使用して情報を取得します。公式APIを使用しないため、APIキーも不要であり、使用量の割り当てもありません。
この二つです。
をpubspec.yamlに入れましょう!
iosのセットアップ
オープンソースの動画のダウンロードのやつと同じ内容です。
長くなるので書きません。
お手数おかけしますが、上記の内容を見てくだせぇ
実装
全文
String youTubeLink = "https://www.youtube.com/watch?v=Ja-85lFDSEM";
//とりあえず橋本環奈の動画入れています
_downloadVideo(youTubeLink) async{
final yt = YoutubeExplode();
final video = await yt.videos.get(youTubeLink);
// Get the video manifest.
final manifest = await yt.videos.streamsClient.getManifest(youTubeLink);
final streams = manifest.muxed;
final audio = streams.first;
final audioStream = yt.videos.streamsClient.get(audio);
final fileName = '${video.title}.${audio.container.name.toString()}'
.replaceAll(r'\', '')
.replaceAll('/', '')
.replaceAll('*', '')
.replaceAll('?', '')
.replaceAll('"', '')
.replaceAll('<', '')
.replaceAll('>', '')
.replaceAll('|', '');
final dir = await getApplicationDocumentsDirectory();
final path = dir.path;
final directory = Directory('$path/video/');
await directory.create(recursive: true);
final file = File('$path/video/$fileName');
final output = file.openWrite(mode: FileMode.writeOnlyAppend);
var len = audio.size.totalBytes;
var count = 0;
var msg = 'Downloading ${video.title}.${audio.container.name}';
stdout.writeln(msg);
await for (final data in audioStream){
count += data.length;
var progress = ((count / len) * 100).ceil();
print(progress);
output.add(data);
}
await output.flush();
await output.close();
}
後で自分なりに綺麗にしてくださいね!
ちょっとキモいコードですが見ていきましょう!
コードの説明
String youTubeLink = "https://www.youtube.com/watch?v=Ja-85lFDSEM";
youTubeLink は 上記の様にURLを入れてください
final video = await yt.videos.get(youTubeLink);
動画のストリームデータを取ってくる
final manifest = await yt.videos.streamsClient.getManifest(youTubeLink);
動画のみ音声のみ両方などを決める
final streams = manifest.muxed;
両方 | 動画のみ | 音声のみ |
---|---|---|
muxed | videoOnly | audioOnly |
今回は両方セットにしたよ!
最高のビットレートのオーディオトラックを取得する。
final audio = streams.first;
final audioStream = yt.videos.streamsClient.get(audio);
取ってきたyoutube動画のデータのタイトルから使えない文字削除
final fileName = '${video.title}.${audio.container.name.toString()}'
.replaceAll(r'\', '')
.replaceAll('/', '')
.replaceAll('*', '')
.replaceAll('?', '')
.replaceAll('"', '')
.replaceAll('<', '')
.replaceAll('>', '')
.replaceAll('|', '');
※これwindowsの命名規則です!
iosの命名規則どうなってんだろ。知ってる方教えてください!
ここからは上記のオープンソースの動画のダウンロードとあんまり変わりません。
まずは自分のアプリ(プロジェクト)のディレクトリを取ってくる
final dir = await getApplicationDocumentsDirectory();
final path = dir.path;
保存したディレクトリの名前を決める、無ければつくる
final directory = Directory('$path/video/');
await directory.create(recursive: true);
今回はvideoディレクトリを作ったよ
directory.createSync(recursive: true);
ってやつもあるから調べてみてね。
動画を保存するファイルの名前作成
final file = File('$path/video/$fileName');
書き込み用にファイルを開く!書込みのみのモード、ファイルがまだ存在しない場合は作成されます。
final output = file.openWrite(mode: FileMode.writeOnlyAppend);
audioStreamのデータを今さっき作ったファイルに書き込む、あと進捗状況
await for (final data in audioStream){
count += data.length;
var progress = ((count / len) * 100).ceil();
print(progress);
output.add(data);
}
書き込み用にファイルを開くいたのをそっと閉じる
await output.close();
あとはダウンロードできているかどうか確認してみてください
自分のデバイスのFiles -> 自分のiphone -> (アプリ)プロジェクトの名前 -> video
の中に入っていると思います。
最後に
sdkやライブラリのバージョンによって多少異なったりすると思いますが、
そこらへんは頑張って下さい!(丸投げ)
今回初めて記事を書いてみたんですが、結構疲れました。
androidの方も気が向いたら書いておきます。
twitterもしよかったらフォローしてほしいです!
よかったらハートマーク押して下さい。励みになります!
じゃあまた面白いもの書きたければ書きます。
Discussion
投稿ありがとうございます。
実際に試したわけでありませんが、こちらをさんこうにすれば以下のように
flush()
する必要があると思うのですが、その点はどうなのでしょうか?コメントありがとうございます
僕はこちらを参考に作ってみたんですけど問題はなかったのですが
https://github.com/Hexer10/youtube_explode_dart/blob/master/example/video_download.dartflush関数調べてみると必要な気がします。ご指摘ありがとうございました。