RustでPixabayからreqwestを使って動画を取得する。


# Rustの外部クレートの取り込み
導入するクレートは下記4つになります。
```toml:Cargo.toml
[dependencies]
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.11" }
tokio = { version = "1", features = ["full"] }
serde_json = "1.0"
```

# Serdeの準備
Pixabay APIから得られるjsonは要素がたくさんありますが、構造体に全て列挙する必要はないので使いそうな要素だけを書き出すだけで事足ります。
```rs:main.rs
#[derive(Serialize, Deserialize, Debug)]
struct Data{
    total: isize,
    #[serde(rename = "totalHits")]
    total_hits : isize,
    hits:Vec<Hit>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
struct Hit{
    id : isize,
    #[serde(rename = "pageURL")]
    page_url: String,
    duration : isize,
    videos : Videos,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
struct Videos{
    //large : Video,
    //medium : Video,
    small : Video,
    //tiny : Video
}

#[derive(Serialize, Deserialize, Debug, Clone)]
struct Video{
    url : String,
    width : isize,
    height : isize,
    size : isize,
}
```

## jsonのパーステスト
Jsonへのパースは書き間違いが多かったのでこまめに動作確認を行う方が良い。(Rustでは改行を含む文字列はr#"〜"#で囲う必要がある)
```rs
#[test]
fn test() {
    let data = r#"
    {
        "total": 1,
        "totalHits": 1,
        "hits": [
            {
                "id": 1,
                "pageURL": "https://pixabay.com",
                "type": "film",
                "tags": "Some",
                "duration": 1,
                "picture_id": "1",
                "videos": {
                    "large": {
                        "url": "https://pixabay.com",
                        "width": 1,
                        "height": 1,
                        "size": 1
                    },
                    "medium": {
                        "url": "https://player.vimeo.com/",
                        "width": 1,
                        "height": 1,
                        "size": 1
                    },
                    "small": {
                        "url": "https://player.vimeo.com/",
                        "width": 1,
                        "height": 1,
                        "size": 1
                    },
                    "tiny": {
                        "url": "https://player.vimeo.com/",
                        "width": 1,
                        "height": 1,
                        "size": 1
                    }
                },
                "views": 1,
                "downloads": 1,
                "likes": 1,
                "comments": 1,
                "user_id": 1,
                "user": "",
                "userImageURL": "https://cdn.pixabay.com/"
            }
        ]
    }
        "#;
    let d :Data = serde_json::from_str(data).unwrap();
    dbg!(d);
}
```
# APIを使って、動画のurlを取得と保存
全てダウンロードすると時間がかかるので、ここでは20s以下の動画のみをダウンロードする。

```rs: main.rs
use serde::Deserialize;
use serde::Serialize;
use std::path::Path;
use std::fs::File;
use std::fs;
use std::io::Write;
use std::io;
#[tokio::main]
async fn main() -> reqwest::Result<()> {
    let key = "pixabayのAPIkey";
    let q = "炎";
    let url = format!(
        "https://pixabay.com/api/videos/?key={}&q={}",
        key,
        q
    );
    let body = reqwest::get(url)
        .await?
        .text()
        .await?;
    
    let p :Data = serde_json::from_str(&body).unwrap();

    //得られたjsonから再生時間が20s以下のみのurlを取り出す。
    let urls:Vec<String> = p.hits.into_iter().filter(|hit| (0 <hit.duration && hit.duration < 200)).map(|hit| hit.videos.small.url).collect();

    let dir = Path::new("./movie");
    let mdir = fs::create_dir(dir);//エラーは使わない

    for (index, url) in urls.into_iter().enumerate(){
        let s = format!("./movie/{:03}.mp4", index);
        //mp4動画のバイト列を取得する。
        let bytes = reqwest::get(url)
            .await?
            .bytes()
            .await?;
        if let Ok(mut file) = File::create(s){
            file.write_all(&bytes);
        }
    }
    
    Ok(())
}
```
## reqwest::getのtext()とbytesの違い
受け取ったデータをバイト列か文字列で解釈するかの違いしかない。

bytesは画像や音声にも使えて、そのままファイルに保存できる。
```rs
let bytes = reqwest::get(url)
    .await?
    .bytes()
    .await?;
```
textは文字通り、&strで保存する。
``` rs
let body = reqwest::get(url)
    .await?
    .text()
    .await?;
```

# 最後に
webからのデータ取得は大変ではないが、Jsonに含まれるオブジェクトが増えたり、入れ子が深くなったものをRustで使おうとすると構造体が大きくなるので、使わない情報は切り捨てる方向で実装した方が良いかもしれないです。