以前、話していることを聞き書きしたいときにYouTubeにアップロードして、一時間ほど待って、キャプションが自動でできてから、そのキャプションファイルを .str / .sbv などでダウンロードして、さらに[Subtitleedit](https://nikse.dk/About)や[Aegisub](https://github.com/Aegisub/Aegisub)など優れた機能の字幕編集エディターを使って、間違いを修正して、編集終わればテキストファイルにして書き出していました。
IBMワトソンTTSのdemo(日本語ドメインのは見たことないです。HPリンクのメンテ一切してないようなので。)や、京都大学の河原研究室の字幕生成の実験公開サーバーなども使わせていただいて、結果を比較したりしていました。
編集しやすいということでは、字幕ファイルにするのが最もスムーズだと感じました。
当時、emacsについては知りませんでしたので、mpvとemacsの組み合わせで、聞き書き、文字おこしがテキストエディター内でできるというナイスなことも知りませんでしたが、ある大学の研究で、ある場所で、そこに関連した写真を撮り続けた写真家の写真についての座談を収録してテキスト化するという1つのプロジェクトに寄り添ったアーカイヴ作りに少しだけ関わり....
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)だけでプログラムして検証していますから、画面が小さいデメリットがありますがインターネットとブラウザが 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 をローカルで使う方法も用意されているようです。
以下のプログラムコードは python 使用歴一ヶ月程度の実力( me )で簡単に 2 つを 1 つにして自動化できますが、 2 つを 1 つにしてみてから、また 2 つに分けました。
実行しているシーン
https://youtu.be/E5czxZUwygE
一応説明すると、以下の 2 つのプログラムを連結するとターゲットの YouTube 動画の URL を urltext に指定すると自動的に字幕のテキストを取りに行って、取ってきたテキストを googletrans が処理します。YouTubeの自動翻訳と googletranslate API の結果を比べるために並べて表示するようなものにするつもりだったのですが、やってみていくつか確認すると YouTube 自動翻訳というのは、キャプション(たとえば英語原文)の 2 行を 1 行にして googletranslate しているのと大差がないように見えたので、ちょっとそれは無視することにして、機械学習が得意そうな長い文章の意味づけ処理をさせるために 3 行を 1 行に、さらにそれを 2 行を 1 行に、さらに 2 行を 1 行にというようにしたものを googletranslate API へ送り出しているのです。その経緯も含めて残ったままのコードになっているので、これをもとにコピーして付足そうと考えているわけですが、春になれば忘れてしまいそうなので、ここにそのお気持ちを残します。
今のところ、機械学習やその周辺についての知識はゼロなので、どういうふうになるのか結果からエラーが出ないようプロパティを適当に変更しているだけです。
なにか別の機械翻訳と比較したい場合もあるので処理を、 2 つに分けました。
ちょっとまた付足せば、 URL のリストからバッチ処理でやっていくということもできると思います。
もちろん Android OS(pydroid3ではチェックしていますが、QPythonだとエラーになりました。もっとも、QPython だと何でもエラーになりますが。) や PC でローカルの python インタープリターでも可能ですが、以下のコードは googlecolab を想定しているので、その場合は少しだけ書き換える必要があります。といっても難しくはないはずですし、jupyter notebook の場合だとほとんど同じなんじゃないかなと思います。(やってないし知らんけどな。そのうち移行してチェックしたいけれども、QPythonで jupyter インストールするのに何時間かロスして結局失敗していて、道筋がたっていない。)
#1 Subtitle
youtube_transcript_api (0.3.1)
pip install youtube_transcript_api
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
pip install googletrans==4.0.0-rc
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' |
'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' |