6
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

pixivのうごイラをダウンロードする

Last updated at Posted at 2018-09-06

2021/01/10 追記
2022/01/15 追記


#やったこと
pythonのライブラリにpixivpyがあります。pixivの非公式APIなのですがとても便利です。非公式なので乱用は禁物です。
普通のイラストをダウンロードすることはできるのだが、うごイラをダウンロードすることはできません(多分)。
なので、うごイラをダウンロードするプログラムを書いてみました。

#環境

  • os: windows10
  • python: 3.6.4
  • pixivpy: 3.3.4

#ソースコード

# -*- coding:utf-8 -*-
from pixivpy3 import *
from  time import sleep
from PIL import Image
import os, glob

#ログイン処理
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):
    frame_url = ugoira_url[0] + str(frame) + ugoira_url[1]
    aapi.download(frame_url, path=dir_name)
    sleep(2) #ダウンロードした後は一応2秒待機

#保存した画像をもとにgifを作成
frames = glob.glob(f'{dir_name}/*')
frames.sort(key=os.path.getmtime, reverse=False)
ims = []
for frame in frames:
    ims.append(Image.open(frame))
ims[0].save(f'{dir_name}/{illust_id}.gif', save_all=True, append_images=ims[1:], optimize=False, duration=ugoira_delay, loop=0)

#説明のようなもの
##うごイラについて
うごイラはgifや映像を再生しているのではなく一枚一枚の画像を連続して表示しているだけでした。なので、うごイラに使われている画像をすべてダウンロードしてgifに変換して1枚の画像にすることにしました。

##変数について
illust_idイラストID。
illustイラストの詳細情報。タイトル、タグ、画像のURLなどの情報。
ugoira_urlうごイラの一枚目のURLを0を区切りに2分割したもの。
ugoiraうごイラの詳細情報。zipファイルのURL(これを使ったほうが簡単かも?)、画像一枚一枚のファイル名とdelay。
ugoira_framesうごイラに使われている画像の枚数。
ugoira_delaydelayの値が何なのかはよくわからないけど多分表示時間。gifを作るときに使う。
dir_nameうごイラの画像を保存するフォルダの名前。[イラストID]_ugoira。

##画像のダウンロードについて
うごイラに使われている画像のURLはhttps://(省略)/[イラストID]_ugoira[画像の番号].jpgのようになっていて画像の番号は0から始まります。
そのため、URLの画像の番号を変えていくことですべての画像のURLを知ることができます。ugoira_urlで0を区切りに2分割したのは画像の番号を変えやすくするためです。
for文で画像の枚数分回すことですべての画像を保存しています。

##gif作成について
gifを作るときはPILを使用しました。詳しい説明は「Python, PillowでアニメーションGIFを作成、保存」を見てもらったほうがわかりやすいと思います。

#追記(2021/01/10)
@yuki_2020さんが「pythonのpixivpyを使用してpixivからうごイラをmp4に変換してダウンロードする」という記事を書いていました。
上の記事ではgif画像だときれいなうごイラにならないということでmp4に変換するという手法をとっています。
これに影響されて綺麗なうごイラを作ることに挑戦してみます。

##その1 APNGに変換する
GIF画像では256色までしか表現できないため、GIF画像に変換するのは諦めます。
GIF画像に代わるアニメーション画像としてAPNGというものがあるみたいなのでこれを使うことにしました。

まずはライブラリをインストール

pip install apng

以下がソースコードになります。
(上で書いたソースコードに続くように書いています)

from apng import APNG
APNG.from_files(frames, delay=ugoira_delay).save(f'{dir_name}/{illust_id}_apng.png')

これだけで済みます。簡単ですね。
ただ、画像を見るときはAPNG対応ブラウザまたはビューアで見る必要があり、Windows標準のビューアだとただの静止画として表示されます。

##その2 うごイラを再現する
どうせブラウザで見ることになるならAPNGに変換するのではなく、うごイラを再現すればいいじゃないという発想です。
うごイラは静止画を連続で表示しているだけなので再現はそこまで難しくありません。

以下がソースコードになります。

html = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <canvas id="ugoira" width="{width}" height="{height}"></canvas>
    <script>
        const images = [];
        for(let i=0; i<{frames}; i++){{
            const img = new Image();
            img.src = `./{illust_id}_ugoira${{i}}.jpg`;
            images.push(img);
        }}
        const canvas = document.querySelector('#ugoira');
        const context = canvas.getContext('2d');
        let count = 0;
        window.addEventListener('load', function(){{
            setInterval(function(){{
                context.clearRect(0, 0, canvas.width, canvas.height);
                context.drawImage(images[count], 0, 0);
                count++;
                if(count>={frames}) count=0;
            }}, {delay});
        }});
    </script>
</body>
</html>
""".format(width=illust.illust.width, height=illust.illust.height, frames=ugoira_frames, illust_id=illust_id, delay=ugoira_delay)
with open(f'{dir_name}/ugoira.html', 'w', encoding='utf-8') as f:
    f.write(html)

うごイラを再現するコードを書いたhtmlを出力するようにしています。
やっていることとしてはcanvasにダウンロードした画像を連続して表示するようにしているだけです。

その3 すべての画像データをHTMLにまとめる

いっそのこと全てをHTMLのみで完結させようという感じです。

以下がソースコードになります。

import base64
illust_b64 = []
img_ext = frames[0].split('.')[-1]
for frame in frames:
    with open(frame, 'rb') as f:
        illust_b64.append(f'data:image/{img_ext};base64,{base64.b64encode(f.read()).decode()}')
html = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <canvas id="ugoira" width="{width}" height="{height}"></canvas>
    <script>
        const illust_b64 = {illust_b64};
        const images = [];
        for(let i=0; i<{frames}; i++){{
            const img = new Image();
            img.src = illust_b64[i];
            images.push(img);
        }}
        const canvas = document.querySelector('#ugoira');
        const context = canvas.getContext('2d');
        let count = 0;
        window.addEventListener('load', function(){{
            setInterval(function(){{
                context.clearRect(0, 0, canvas.width, canvas.height);
                context.drawImage(images[count], 0, 0);
                count++;
                if(count>={frames}) count=0;
            }}, {delay});
        }});
    </script>
</body>
</html>
""".format(width=illust.illust.width, height=illust.illust.height, frames=ugoira_frames, illust_id=illust_id, delay=ugoira_delay, illust_b64=str(illust_b64))
with open(f'{dir_name}/ugoira_onefile.html', 'w', encoding='utf-8') as f:
    f.write(html)

ダウンロードした画像をBase64に変換し、HTML内に書いておくことで画像を取っておく必要がなくなります。

おわりに

目的だった綺麗なうごイラを作るというのは達成できたかなと思います。
ただ、専用ビューアが必要だったりブラウザで開く必要があったりとちょっと微妙な部分がありますね。
また何か思いついたら追記しようと思います。

#追記(2022/01/15)
うごイラはうごイラを構成する画像1枚1枚に異なるdelayが設定されていることがあるみたいなのでそれも再現したいと思います。
以前作成した「その3 すべての画像データをHTMLにまとめる」に変更を加えます。
以下ソースコード

illust_b64 = []
img_ext = frames[0].split('.')[-1]
for num, frame in enumerate(frames):
    with open(frame, 'rb') as f:
        illust_b64.append([f'data:image/{img_ext};base64,{base64.b64encode(f.read()).decode()}', ugoira.ugoira_metadata.frames[num]['delay']])

html = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <canvas id="ugoira" width="{width}" height="{height}"></canvas>
    <script>
        const illust_b64 = {illust_b64};
        const images = [];
        for(let i=0; i<{frames}; i++){{
            const img = new Image();
            img.src = illust_b64[i][0];
            images.push([img, illust_b64[i][1]]);
        }}
        const canvas = document.querySelector('#ugoira');
        const context = canvas.getContext('2d');
        let count = 0;
        const drawImage = (index) => {{
            if(index>={frames}) index=0;
            context.clearRect(0, 0, canvas.width, canvas.height);
            context.drawImage(images[index][0], 0, 0);
            setTimeout(drawImage, images[index][1], index+1)
        }};
        window.addEventListener('load', () => drawImage(0));
    </script>
</body>
</html>
""".format(width=illust.illust.width, height=illust.illust.height, frames=ugoira_frames, illust_id=illust_id, illust_b64=str(illust_b64))
with open(f'{dir_name}/ugoira_onefile.html', 'w', encoding='utf-8') as f:
    f.write(html)

ポイントとしては画像データと一緒にdelayのデータをHTMLファイルに持たせていることと、setIntervalではなくsetTimeoutを使っていることです。setIntervalでは同じ間隔でしか関数を実行できませんが、setTimeoutを使うことで間隔を自由に設定することができます。

6
10
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?