pythonのpixivpyを使用してpixivからうごイラをmp4に変換してダウンロードする（番外編2）


#2021/2/21追記　ログイン方法を変えることで動くようになりました！！

https://qiita.com/yuki_2020/items/759e639a4cecc0770758

記事に従ってプログラムを変更してください。

今のこの記事のプログラムは変更していないので動きません。
自分で直してください。m(__)m

<!--
#2021/2/11追記　仕様変更によって現在動きません(解決済み)

https://github.com/upbit/pixivpy/issues/158

こちらに詳しいことがありますが、pixivpyはもともとandroidアプリを通してログインをしていましたが、
アップデートでログイン方法が変わったため、現在ログインできません。

```
PixivError: [ERROR] auth() failed! check username and password.
HTTP 400: {"has_error":true,"errors":{"system":{"message":"Invalid grant_type parameter or parameter missing","code":1508}},"error":"invalid_grant"}

```
こうなります。
とりあえず今はpixivpyの改良を待つか、

https://github.com/danbooru/danbooru/blob/39cc3ed5cf913499093d2f641d70d7682a14fa42/app/logical/pixiv_ajax_client.rb

こんなものが紹介されていたので、動くかわかりませんがrubyを勉強して試してみます。

-->



#はじめに
@choshicureさんの作った、pixivからうごイラをgifとしてダウンロードするプログラムがあります。

https://qiita.com/choshicure/items/8795bf929e34af6622fc


とても使いやすいですが、結構gifが圧縮されてしまい、本物同様のきれいなうごイラは再現できません。
gifについて調べたところ、使用する色の数やディザなど様々な方法で圧縮していることがわかりました。
このことについて詳しく知りたい方は、以下のサイトを見てください。

https://aviutl.info/gif-puraguinn/

gifの作成に使用されている`PILライブラリ`を詳しく見ましたが、残念ながら圧縮度合いを変えることはできないみたいです。
そこで`OpenCVライブラリ`を使用してうごイラから`mp4`動画を作ることにしました。
なお、`ffmpeg`の使用も考えましたが、ダウンロードやpathを通したり大変そうなのでやめました。



#注意
:::note warn
pixivpyはおそらく中国の有志が作ったライブラリであり、公式のものではありません。
そのためログイン時のユーザーid、passwordは流出してもいいように捨て垢を使うなどの方法を使うことをお勧めします。
:::

:::note warn
また乱用厳禁です。
スクレイピングのルールとして1秒当たりリクエリは1回までという暗黙のルールがあります。
これがないとdos攻撃となってしまい、法的にアウトです。
sleepをコメントアウトする際は自己責任でお願いします。
:::

:::note warn
また何か問題が起きても自己責任でお願いします。
:::


#環境
このプログラムはwindows10環境下で動かしています。
Linux、Mac環境下で動作するかはわかりません。
特に、スラッシュやバックスラッシュの扱いが異なってくるので、どうなるか本堂にわかりません。


#ライブラリのインポート
```
pip install opencv-python
pip install pixivpy
```

#コード

画像をダウンロードするところまでは
@choshicureさんの作った

https://qiita.com/choshicure/items/8795bf929e34af6622fc

と同じです。
変数の説明などはこちらの記事を読んでください。

後半の`mp4`に変換する部分は以下の記事を参考にしました。

https://qiita.com/penpenpen/items/acfa194cee45190cd8f3



```ugoira_mp4_downloader.py
from pixivpy3 import *
from  time import sleep
from PIL import Image
import os, glob
import sys
import cv2

#ログイン処理
aapi = AppPixivAPI()

ID = 'YOUR_ID'
PASS = 'YOUR_PASS'

aapi.login(ID, PASS)

#イラストIDの入力待機
illust_id = int(input('>>'))

illust = aapi.illust_detail(illust_id)
ugoira_url = illust.illust.meta_single_page.original_image_url.rsplit('0', 1)
ugoira = aapi.ugoira_metadata(illust_id)
ugoira_frames = len(ugoira.ugoira_metadata.frames)
ugoira_delay = ugoira.ugoira_metadata.frames[0].delay
dir_name = str(illust_id)+'_ugoira'

#うごイラを保存するフォルダの作成
if not os.path.isdir(dir_name):
    os.mkdir(dir_name)


#うごイラに使われているすべての画像のダウンロード
for frame in range(ugoira_frames):
    print(frame)
    frame_url = ugoira_url[0] + str(frame) + ugoira_url[1]
    aapi.download(frame_url, path=dir_name)
    sleep(2) #ダウンロードした後は一応2秒待機

#-------------------------------------------------
#ここからオリジナル

#動画の作成
frames = glob.glob(f'{dir_name}/*_ugoira*')
frames.sort(key=os.path.getmtime, reverse=False)

#パラメーター
fps = 1000 / ugoira_delay
height = illust.illust.height
width = illust.illust.width



# encoder(for mp4)
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
# output file name, encoder, fps, size(fit to image size)
video = cv2.VideoWriter(f'{dir_name}/{illust_id}.mp4',fourcc, fps, (width, height))

for frame in frames:
    img = cv2.imread(frame)
    video.write(img)

video.release()
print('written')

```





#まとめ
`gif`よりもファイルサイズが小さく、きれいな`mp4`のうごイラがダウンロードできるようになりました。
しかし、欠点として`mp4`は再生するソフトでいろいろ設定しないとループができなかったり、圧縮する以上元のうごイラよりも画像が劣化しているという問題があります。




#2022/2/21　追記

色々問題点があったので、修正しました。
具体的には、
1. OpenCVが全角文字のパスに対応していない
2. OpenCVで動画を作る際、αチャンネルを含んだpng画像に対応していない
3. フォルダ内に画像以外があるとエラーになる
4. 画像のソートをダウンロード時間ではなく、数字の大きさ順にした

の3点です。

##1. OpenCVが全角文字のパスに対応していない

オリジナルのプログラムでは、プレゼントディレクトリ直下の半角文字のみのフォルダに画像をダウンロードしているため、問題がありません。
しかし、プログラムをいじって保存場所を変えてディレクトリを指定する際に全角文字が入ってしまうと、エラーになってしまいます。
そこで、以下のサイトを参考に`Numpy`を使用して読み込むことにしました。

https://imagingsolution.net/program/python/opencv-python/read_save_image_files_in_japanese/


##2. OpenCVで動画を作る際、αチャンネルを含んだpng画像に対応していない

詳しくは以下の記事に書いています。

https://qiita.com/yuki_2020/items/b6082be0e609604001f3

要約すると、`Numpy`を経由したことで、αチャンネルを含んだpng画像がそのまま読み込まれてしまったので、αチャンネルを削除するようにしました。

##3. フォルダ内に画像以外があるとエラーになる
うごイラを初めてダウンロードするなら問題がありません。
しかし、ダウンロードしなおしたり、ほかの方法でダウンロードするとき、フォルダ内のファイルをすべて取得する際に、以前作成したhtmlなどが混在しているとエラーになってしまいます。
そこで、画像ファイルのみを取得するようにしました。



##4. 画像のソートをダウンロード時間ではなく、数字の大きさ順にした
題名の通りです。
以下のサイトを参考にしました。

https://note.nkmk.me/python-sort-num-str/



##プログラム
以上の修正を加えたプログラムが以下になります。


```ugoira_mp4_downloader.py
from pixivpy3 import *
from  time import sleep
from PIL import Image
import os, glob
import sys
import cv2

#ログイン処理
aapi = AppPixivAPI()
aapi.auth(refresh_token = "YOUR_refresh_token")

#イラストIDの入力待機
illust_id = int(input('>>'))

illust = aapi.illust_detail(illust_id)
ugoira_url = illust.illust.meta_single_page.original_image_url.rsplit('0', 1)
ugoira = aapi.ugoira_metadata(illust_id)
ugoira_frames = len(ugoira.ugoira_metadata.frames)
ugoira_delay = ugoira.ugoira_metadata.frames[0].delay
dir_name = str(illust_id)+'_ugoira'

#うごイラを保存するフォルダの作成
if not os.path.isdir(dir_name):
    os.mkdir(dir_name)


#うごイラに使われているすべての画像のダウンロード
for frame in range(ugoira_frames):
    print(frame)
    frame_url = ugoira_url[0] + str(frame) + ugoira_url[1]
    aapi.download(frame_url, path=dir_name)
    sleep(2) #ダウンロードした後は一応2秒待機

#-------------------------------------------------
#ここからオリジナル

#動画の作成
#jpg以外の画像あるのかわからない
frames = glob.glob(f'{dir_name}/*.jpg')
frames += glob.glob(f'{dir_name}/*.jpeg')
frames += glob.glob(f'{dir_name}/*.png')
#https://note.nkmk.me/python-sort-num-str/
frames.sort(key=lambda s: int(re.findall(r'\d+', s)[-1]))
#frames.sort(key=os.path.getmtime, reverse=False)

#パラメーター
fps = 1000 / ugoira_delay
height = illust.illust.height
width = illust.illust.width



# encoder(for mp4)
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
# output file name, encoder, fps, size(fit to image size)
video = cv2.VideoWriter(f'{dir_name}/{illust_id}.mp4',fourcc, fps, (width, height))

for frame in frames:
    #https://imagingsolution.net/program/python/opencv-python/read_save_image_files_in_japanese/
    #numpyで開いてopencvに渡すことで全角文字のパスでも動く
    buf = np.fromfile(frame, np.uint8)
    img = cv2.imdecode(buf, cv2.IMREAD_UNCHANGED)
    #アルファチャンネルの削除
    if img.shape[2] == 4:
        img = np.delete(img, 3, axis=2)
    video.write(img)

video.release()
print('written')

```


#参考サイト

https://qiita.com/choshicure/items/8795bf929e34af6622fc

https://qiita.com/penpenpen/items/acfa194cee45190cd8f3

https://qiita.com/Gyutan/items/e279e8680b90ca891250

https://water2litter.net/rum/post/python_matplotlib_anime_opencv/

https://docs.opencv.org/3.4/dd/d9e/classcv_1_1VideoWriter.html#ad59c61d8881ba2b2da22cff5487465b5

https://qiita.com/showchan33/items/968a07c078bf0a7f9e46