googletrans , YouTube subtitle , Google Colab

[English](https://qiita.com/dauuricus/items/7c2d7a6a73b3755c5c6c)

<details><summary>以前、話していることを聞き書きしたいときにYouTubeにアップロードして、一時間ほど待って、キャプションが自動でできてから、そのキャプションファイルを .str / .sbv などでダウンロードして、さらに[Subtitleedit](https://nikse.dk/About)や[Aegisub](https://github.com/Aegisub/Aegisub)など優れた機能の字幕編集エディターを使って、間違いを修正して、編集終わればテキストファイルにして書き出していました。</summary>
<div>

[IBMワトソンTTSのdemo](https://speech-to-text-demo.ng.bluemix.net/)（日本語ドメインのは見たことないです。HPリンクのメンテ一切してないようなので。）や、京都大学の河原研究室の字幕生成の実験公開サーバーなども使わせていただいて、結果を比較したりしていました。

編集しやすいということでは、字幕ファイルにするのが最もスムーズだと感じました。
当時、emacsについては知りませんでしたので、mpvとemacsの組み合わせで、聞き書き、文字おこしがテキストエディター内でできるというナイスなことも知りませんでしたが、ある大学の研究で、ある場所で、そこに関連した写真を撮り続けた写真家の写真についての座談を収録してテキスト化するという[1つのプロジェクト](https://atom.log.osaka/index.php/sn01)に寄り添った[アーカイヴ作り](https://project.log.osaka/nakajima/)に少しだけ関わり....
</div></details>

rf.
（今確認すると去年でサービスを終えていた、悲しい。）
京都大学 河原研究室 音声認識を用いた自動字幕作成システム
Captioning System based on Automatic Speech Recognition Technology
http://caption.ist.i.kyoto-u.ac.jp/

rf.
中島敏フォトアーカイブ・解題 (Photo archive "KAIDAI")
https://project.log.osaka/nakajima/

rf.
（ windows 用ですが最も良くできてると感じた記憶があります。プログラムを何個比べたのかは記憶にないですが何日もかけて動作するものは全て使ってみました。）
Nikolaj Lynge Olsson's 'Subtitle Edit'
https://nikse.dk/

rf.
Aegisub github page
https://github.com/Aegisub/Aegisub

いや、これ書いていると、関係するとこまでたどり着くまでに夕方になりそうですね。

端折ります。

YouTubeにある動画で、字幕がすでについているもの（何語でもかまいませんが、この記事の中では英語と仮定しています。）を取り出して、YouTubeの自動翻訳の結果より、ちょっといい感じ？な翻訳結果をテキストで得るためのものです。
改良の余地はいろいろあります。
内容については、コードを見たらそのままです。
わからないとこは、コメントアウトを外してみて、それが何なのか print 見たらわかると思いますから、見て、だったらこうしたらいいなというところを変えていけばいいとおもいます（自分が）。
ほとんど [Amazon kindle Fire8 2020（6500円の Android OS）](https://www.youtube.com/watch?v=E5czxZUwygE&feature=youtu.be)だけでプログラムして検証していますから、画面が小さいデメリットがありますがインターネットとブラウザが　googlecolab　に対応していればスマートフォンでもできるんじゃないかなと思います。

なぜ googlecolab なのかは、メリットはあるのですが、まだはっきりしないので、はっきりとは書かないことにします。メリットの一つは IP アドレスが変えやすいことです。
ちなみに、googlecolabのノートのIPアドレスは

```
!curl ipecho.net/plain
```

>https://ipecho.net/developers.html
Please note that this is a free service, provided as-is with out any warranty.

>I know the links have to be consistent so they will probably never change, I'll also try to keep the uptime as close to 100% as possible, but again, I can't offer any warranty for anything and I reserve the right to do whatever I please with it, so you're using the service at your own risk.

>If you're planning to use this service in an application you might consider using this link instead: http://ipecho.net/plain

>PS: Please don't abuse the system, try to cache the IP for a reasonable amount of time before doing another request, so everyone can enjoy it.

で確認できるようです。[googlecolab をローカルで使う方法も用意されているようです。](https://research.google.com/colaboratory/local-runtimes.html?hl=ja)

以下のプログラムコードは python 使用歴一ヶ月程度の実力（ me ）で簡単に 2 つを 1 つにして自動化できますが、 2 つを 1 つにしてみてから、また 2 つに分けました。

実行しているシーン
https://youtu.be/E5czxZUwygE

一応説明すると、以下の 2 つのプログラムを[連結すると](https://qiita.com/dauuricus/private/6916169e1fe7655e38ee)ターゲットの YouTube 動画の URL を urltext に指定すると自動的に字幕のテキストを取りに行って、取ってきたテキストを googletrans が処理します。YouTubeの自動翻訳と　googletranslate　API　の結果を比べるために並べて表示するようなものにするつもりだったのですが、[やってみていくつか確認すると YouTube 自動翻訳というのは、キャプション（たとえば英語原文）の 2 行を 1 行にして googletranslate しているのと大差がないように見えた](https://qiita.com/dauuricus/items/1e2630fce709a5f1a577)ので、ちょっとそれは無視することにして、機械学習が得意そうな長い文章の意味づけ処理をさせるために 3 行を 1 行に、さらにそれを 2 行を 1 行に、さらに 2 行を 1 行にというようにしたものを googletranslate API へ送り出しているのです。その経緯も含めて残ったままのコードになっているので、これをもとにコピーして付足そうと考えているわけですが、春になれば忘れてしまいそうなので、ここにそのお気持ちを残します。
今のところ、機械学習やその周辺についての知識はゼロなので、どういうふうになるのか結果からエラーが出ないようプロパティを適当に変更しているだけです。

なにか別の機械翻訳と比較したい場合もあるので処理を、 2 つに分けました。
ちょっとまた付足せば、 URL のリストからバッチ処理でやっていくということもできると思います。

もちろん Android OS([pydroid3ではチェックしていますが、QPythonだとエラーになりました。](https://qiita.com/dauuricus/private/6916169e1fe7655e38ee)もっとも、[QPython](https://github.com/qpython-android/qpysl4a) だと何でもエラーになりますが。) や PC でローカルの python インタープリターでも可能ですが、以下のコードは googlecolab を想定しているので、その場合は少しだけ書き換える必要があります。[といっても難しくはないはずですし](https://qiita.com/dauuricus/private/6916169e1fe7655e38ee)、jupyter notebook の場合だとほとんど同じなんじゃないかなと思います。（やってないし知らんけどな。そのうち移行してチェックしたいけれども、QPythonで [jupyter](https://ipython.org/) インストールするのに何時間かロスして結局失敗していて、道筋がたっていない。）

#1 Subtitle
youtube_transcript_api (0.3.1)

```python:googlecolab
pip install youtube_transcript_api
```

```python:googlecolab
from youtube_transcript_api import YouTubeTranscriptApi
#from googletrans import translator
from google.colab import files
#import time
#import sys
from urllib.parse import urlparse, parse_qs

urltext ='https://www.youtube.com/watch?v=P9GLDezYVX4' ## YouTube URL
args = [urltext]
video_id = ''

def extract_video_id(url):
    query = urlparse(url)
    if query.hostname == 'youtu.be': return query.path[1:]
    if query.hostname in {'www.youtube.com', 'youtube.com'}:
        if query.path == '/watch': return parse_qs(query.query)['v'][0]
        if query.path[:7] == '/embed/': return query.path.split('/')[2]
        if query.path[:3] == '/v/': return query.path.split('/')[2]
    # fail?
    return None

for url in args:
    video_id = (extract_video_id(url))
    print('youtube video_id:',video_id)

## youtube video_id

line =[]
line[:] = YouTubeTranscriptApi.get_transcript(video_id,languages=['en']) ## if YouTube subtitle is English

text_list = [] # subtitle text lines
for l in line:
    ##print("text: ", l['text'])
    ##print("start:", l['start'])
    ##print("duration:", l['duration'])
    
    l['text']=l['text'].strip() # chop empty charactor
    l['text']=l['text'].rstrip('\n') # chop charactor
    l['text']=l['text'].rstrip('\r') # chop charactor
    l['text']=l['text'].replace('\r','') # cut charactor 
    l['text']=l['text'].replace('\n',' ') # cut charactor
    text_list.append(l['text'])

##text_list[:] = [a for a in text_list if a != ' '] ## same as above
##text_list[:] = [l.replace('\n',' ') for l in text_list]
##print(line)    

del line

##print(text_list)

original_stdout = sys.stdout ## stdout backup
filename = 'subtitle.txt' ## print subtitle text to this file
with open(filename, 'w') as f:
    sys.stdout = f # stdout to file

    print('youtube video_id:',video_id)
    print()
    print("haywhnk-A.K.A-@dauuricus")
    print("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -")
    print("YouTube captions")
    print("- - - - - - - - - - - - - - - - - - -  YouTube  - - - - - - - - - - - - - - - - - - -")
    print()
    print()

    line = YouTubeTranscriptApi.list_transcripts(video_id)    

    transcript = line.find_transcript(['en']) ## select English subtitle
    #print(transcript.fetch())
    
##    caption_line =[]
##    for count, dict_obj in enumerate(transcript.fetch()):
##        print( dict_obj['text'] )
##        caption_line.append(dict_obj['text'])
##    print()
##    print()
##    print("************************************************************************************")
##    print()
##    print("- - - - - - - - - - - - - - - - - - translated - - - - - - - - - - - - - - - - - - -")
##    print()
##    print()
  
##    translated = transcript.translate('ja')
##    for count, dict_obj in enumerate(translated.fetch()):# japanese
##        print( count+1, dict_obj['text'] )
##    print()
##    print("-----------------------------------------------------------------------------------")
##    print()
##    print("text compositimg ...")
##    print("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -")
##    print()
##    print()

####    text_e = [] ##2 lines to 1 line
####    i = 0
####    txt_e = ''
####    for count,l in enumerate(caption_line):
####        if i == 0:
####          txt_e += l
###          i = i + 1
###          text_e.append(txt_e)
###        elif i == 1:
###          txt_e += ' ' +l
###          text_e.pop()
###          text_e.append(txt_e)
###          i = 0
###          txt_e = ''
    
    def line_edit2(textlines): ##2 lines to 1 line same as line_edit1(textlines)
        text_compo = []
        txt = ''
        for count,l in enumerate(textlines):
          if (count+1)%2 == 0:
            txt = text_compo.pop()
            txt += ' ' +l
            text_compo.append(txt)
          else :
            txt = l
            text_compo.append(txt)
        return text_compo

    def line_edit3(textlines): ##3 lines to 1 line
        text_compo = []
        txt = ''
        i = 0
        for count,l in enumerate(textlines):
          if i == 0:
            txt += l
            i = i + 1
            text_compo.append(txt)
          elif i == 1:
            txt = text_compo.pop()
            txt += ' ' + l
            i = i + 1
            text_compo.append(txt)
          elif i == 2:
            txt = text_compo.pop()
            txt += ' ' + l
            text_compo.append(txt)
            txt = ''
            i = 0
        return text_compo

    def line_edit1(textlines): ##2 lines to 1 line
        text_compo = []
        i = 0
        txt = ''
        for count,l in enumerate(textlines):
          if i == 0:
            txt += l
            i = i + 1
            text_compo.append(txt)
          elif i == 1:
            txt += ' ' +l
            text_compo.pop()
            text_compo.append(txt)
            i = 0
            txt = ''
        return text_compo

###    text_compo = [] ##2 lines to 1 line
###    i = 0
###    txt = ''
###    for count,l in enumerate(text_list):
###        if i == 0:
###          txt += l
###          i = i + 1
###          text_compo.append(txt)
###        elif i == 1:
###          txt += ' ' +l
###          text_compo.pop()
###          text_compo.append(txt)
###          i = 0
###          txt = ''
###    print()
###    print()
###    print("************************************************************************************")
###    print()
###    print()
##    for count, l in enumerate(text_list):
##        print(count+1,' ',l)
    print()
    print()
    print("************************************************************************************")
    print("shrink text")
    print()
####    for count, l in enumerate(text_e):
####        print(count+1,l)
    print()
    print()
    text_compo = (line_edit3(text_list))
    text_compo[:] = (line_edit2(text_compo))
    #for count, l in enumerate(text_compo):
    #    print(l)
    text_compo2 = (line_edit2(text_compo))
    text_compo2[:] = (line_edit2(text_compo2))
    for count, l in enumerate(text_compo2):
        print(l)
    print()
    print()
    print()
    print("************************************************************************************")
    print()
    print("Thank you.")

sys.stdout = original_stdout # stdout back
##files.download(filename) ## if you want to download file
```

Run program ...
Now you get YouTube subtitle in your google drive which google colab can read.

cf.**extracting youtube video id from youtube URL**
https://qiita.com/dauuricus/private/9e70c4c25566fedb9c19
#2 Translate
googletrans==4.0.0-rc

```python:googlecolab
pip install googletrans==4.0.0-rc
```

```python:googlecolab
from google.colab import files
from googletrans import Translator
import sys
#import re

#####uploaded = files.upload()

####filename = ''
####for fn in uploaded.keys():
####  print('User uploaded file "{name}" with length {length} bytes'.format(
####      name=fn, length=len(uploaded[fn])))
####  filename = fn

filename = 'subtitle.txt'
#args= sys.argv
args = [('translate.py'),filename]

print('open '+args[1])
with open(args[1]) as f: # uploaded file
  line = f.readlines() 
f.close()

line[:] = [l.strip() for l in line]
line[:] = [l.rstrip('\n') for l in line]
line[:] = [a for a in line if a != '']
line[:] = [l.replace('\n',' ') for l in line]
line[:] = [l.replace('\r',' ') for l in line]
#print(line)

#print()

####for line_num,l in enumerate(line):
####  if re.search(r'.*?i'm$',l):
####    print(line_num,'   ',l)
####  elif re.search(r'.*?to/Z',l):
####    print(line_num,'   ',l)
####  if re.search(r'.*?the$',l):
####    print(line_num,'   ',l)
####  elif re.search(r'.*?the/Z',l):
####    print(line_num,'   ',l)


#for line_num,l in enumerate(line):
#    print(line_num,'   ',l)
    
translator = Translator()
num = 20
#obj_num = 1
filename = 'translated.txt'
backup_stdout = sys.stdout
print("translating...")
with open(filename,'w') as f:
    sys.stdout = f
            
    for count, l in enumerate(line):
        if count < 7:
            continue
        else:
            if count +1< num:
               translated = translator.translate(l, dest='ja')
               ##print(count+1,'  ', l) # original text
               print(translated.text)
            else:
               translated = translator.translate(l, dest='ja')
               ##print(count+1,'  ', l) # original text
               print(translated.text)        
               del translator
               num = num + 20
               #obj_num = obj_num + 1
               #print("")
               #print("--- translator :", obj_num)
               #print("")
               translator = Translator()        
    sys.stdout = backup_stdout # back
del translator
print("saving...",filename)

#files.download(filename) # translated.txt
```

1 と 2 とステップをわけなくてもいいけど、わけた方が見た感じでわかりやすいので。

 
### Language list

| 対応言語リスト |  |  |  |
| :-: | :-: | :-: | :-: |
|'af': 'afrikaans'|'sq': 'albanian'|'am': 'amharic'|'ar': 'arabic'|
|'hy': 'armenian'|'az': 'azerbaijani'|'eu': 'basque'|'be': 'belarusian'|
|'bn': 'bengali'|'bs': 'bosnian'|'bg': 'bulgarian'|'ca': 'catalan'|
|'ceb': 'cebuano'|'ny': 'chichewa'|'zh-cn': 'chinese (simplified)'|'zh-tw': 'chinese (traditional)'|
|'co': 'corsican'|'hr': 'croatian'|'cs': 'czech'|'da': 'danish'|
|'nl': 'dutch'|**'en': 'english'**|**'eo': 'esperanto'**|'et': 'estonian'|'tl': 'filipino'|
|'fi': 'finnish'|'fr': 'french'|'fy': 'frisian'|'gl': 'galician'|
|'ka': 'georgian'|'de': 'german'|'el': 'greek'|'gu': 'gujarati'|
|'ht': 'haitian creole'|'ha': 'hausa'|'haw': 'hawaiian'|**'iw': 'hebrew'**|
|**'he': 'hebrew'**|'hi': 'hindi'|'hmn': 'hmong'|'hu': 'hungarian'|
|'is': 'icelandic'|'ig': 'igbo'|'id': 'indonesian'|'ga': 'irish'|
|'it': 'italian'|**'ja': 'japanese'**|'jw': 'javanese'|'kn': 'kannada'|
|'kk': 'kazakh'|'km': 'khmer'|**'ko': 'korean'**|'ku': 'kurdish (kurmanji)'|
|'ky': 'kyrgyz'|'lo': 'lao'|'la': 'latin'|'lv': 'latvian'|
|'lt': 'lithuanian'|'lb': 'luxembourgish'|'mk': 'macedonian'|mg': 'malagasy'|
|'ms': 'malay'|'ml': 'malayalam'|'mt': 'maltese'|'mi': 'maori'|
|'mr': 'marathi'|'mn': 'mongolian'|'my': 'myanmar (burmese)'|'ne': 'nepali'|
|'no': 'norwegian'|'or': 'odia'|'ps': 'pashto'|'fa': 'persian'|
|'pl': 'polish'|'pt': 'portuguese'|'pa': 'punjabi'|'ro': 'romanian'|
|'ru': 'russian'|'sm': 'samoan'|'gd': 'scots gaelic'|'sr': 'serbian'|
|'st': 'sesotho'|'sn': 'shona'|'sd': 'sindhi'|'si': 'sinhala'|
|'sk': 'slovak'|'sl': 'slovenian'|'so': 'somali'|'es': 'spanish'|
|'su': 'sundanese'|'sw': 'swahili'|'sv': 'swedish'|'tg': 'tajik'|
|'ta': 'tamil'|'te': 'telugu'|'th': 'thai'|**'tr': 'turkish'**|
|'uk': 'ukrainian'|'ur': 'urdu'|'ug': 'uyghur'|'uz': 'uzbek'|
|'vi': 'vietnamese'|'cy': 'welsh'|'xh': 'xhosa'|'yi': 'yiddish'|
|'yo': 'yoruba'a|'zu': 'zulu'|