PythonistaでYouTubeの動画をフォトライブラリーに追加する

#はじめに
Pythonistaとpytubeを使ってYouTube Downloaderをつくることにしました。既存のiOSアプリはアプリ内に動画をダウンロードしますが、今回はiOS純正のフォトライブラリーにダウンロードしたいと思います。

#方針
YouTubeアプリでダウンロードしたい動画のページへ行き、その動画の共有からPythonistaのスクリプトを実行します。スクリプトは大まかに、pytubeを使い動画をダウンロードし、それをフォトライブラリーに追加する工程に分かれます。

#環境設定
Stashをインストールした後、pipでpytubeをインストールする。

#実装
ファイルは以下のように分割します。
どうやらiCloud Driveでこれらのファイルを実行するとエラーが出る(Dropboxは未確認)。なのでローカルでこれらのファイルを作成し、実行してください。

```YouTube 
YouTube—-main.py
        |-url_get.py
        |-add_video.py
        |-byte2si.py
        |-Download
```

main.pyはその名の通りメインファイルである。
url_get.pyはYouTubeアプリからURLを取得する。
add_video.pyはフォトライブラリーに動画を追加する。
byte2si.pyは取得する動画のバイト数を補助単位つきのものに変換する。(ex:1,000B→1KB)
Downloadディレクトリは一時的に動画を保存する

##main.py

```python:main.py

from pytube import YouTube
import add_video
import byte2si
import url_get
import os
import glob
import time


def absfpath(path):
    # ”Download”ディレクトリ下のファイルを取得する
    flist = glob.glob(path + '/*')
    if flist:
        # “Download”ディレクトリ下の1つしかない動画ファイルを取得する
        fpath = os.path.abspath(flist[0])
         # “Download”ディレクトリ下にある動画ファイルの絶対パスを返す
        return fpath
    else:
        raise OSError('The file does not exist.')


def main():
    #プログレスバー
    print('Now executing...')

    # YouTubeアプリからurlを取得
    url = url_get.yturl()
    if not url:
        raise ('Not YouTube')

    # pytube.YouTubeを使い、urlから動画リストを取得し、そのリストの最初の動画をダウンロードする
    yt = YouTube(url)
    stream = yt.streams.first()
    # main.pyと同一ディレクトリ下にある"Download"のパスを取得
    drpath = os.getcwd() + 'Download'
    # ダウンロードする
    stream.download(drpath)
    # その動画の絶対パスを取得
    abspath = absfpath(drpath)
    # その動画のサイズを取得
    size = os.path.getsize(abspath)
    sibyte = byte2si.byte2si(size)
    #ファイルサイズを表示
    print('Size:', sibyte)

    # 動画のパスを渡し、フォトライブラリーに追加する
    add_video.add_video(abspath)
    # “Download”ディレクトリにはファイルを残さない、残すと”absfpath()”でバグる
    os.remove(abspath)


if __name__ == '__main__':
    # 開始時間を取得
    start = time.time()
    main()
    #終了時間を少数第3位まで取得
    finish = round(time.time() - start, 3)
    #実行所要時間を表示
    print('Execute Time:', finish)
    #終了
    print('Done')
```

##url_get.py
割とテンプレートのコピペ

```python:url_get.py
import appex
import re


def trns():
    if not appex.is_running_extension():
        raise RuntimeError(
            'This script is intended to be run from the sharing extension.')

    url = appex.get_text()
    if not url:
        raise ValueError('No input text found.')
    elif re.search(r'youtube', url):
        return url
    raise ValueError('Not YouTube')


def yturl():
    geturl = trns()
    # 取得したurlの余分なテキストを置換し、なくす
    url = geturl.replace('&feature=share', '')
    return url


if __name__ == '__main__':
    print(yturl())
```

##add_video.py
これはPythonistaのライブラリにはない機能でした。ということで、Pythonistaのフォーラムで質問したところ、[cvp](https://forum.omz-software.com/user/cvp)さんに回答を頂けました。ありがとうございます。
https://forum.omz-software.com/topic/4829/how-videos-add-to-the-camera-roll
今回はこれを一部変更して使っております。

```python:add_video.py
from objc_util import *
import threading


def add_video(path):
    PHPhotoLibrary = ObjCClass('PHPhotoLibrary')
    PHAssetChangeRequest = ObjCClass('PHAssetChangeRequest')
    lib = PHPhotoLibrary.sharedPhotoLibrary()
    url = nsurl(path)
    # path to local video file

    def change_block():
        req = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL_(url)
        
    def perform_changes():
        lib.performChangesAndWait_error_(change_block, None)
        
    t = threading.Thread(target=perform_changes)
    t.start()
    t.join()

if __name__ == '__main__':
    add_video() 
```

##byte2si.py
単位を付けるだけのメソッドです。

```python:byte2si.py
import math


def main(byte):
    #byteの桁数を取得して、3桁刻みに単位を変えてくだけ
    if 0 <= byte:
        dig = int(math.log10(byte))
        if 0 <= dig < 3:
            bytes = byte / pow(10, 0)
            return bytes, 'B'
        elif 3 <= dig < 6:
            bytes = byte / pow(10, 3)
            return bytes, 'KB'
        elif 6 <= dig < 9:
            bytes = byte / pow(10, 6)
            return bytes, 'MB'
        elif 9 <= dig < 12:
            bytes = byte / pow(10, 9)
            return bytes, 'GB'
        elif 12 <= dig <= 15:
            bytes = byte / pow(10, 12)
            return bytes, 'TB'
    else:
        raise ValueError('Not num')


def byte2si(byte):
    num, unit = main(byte)
    #少数第2位まで丸める
    renum = round(num, 2)
    return str(renum) + unit
```

#仕上げ
PythonistaのExtentionにmain.pyを結びつける。そして適当なアイコンを選択して完成。

#結果
こんな感じで作り上げたら、爆速でYouTubeをダウンロードできるスクリプトになりました。