Python

YouTube Liveの切り抜きポイントをMACD(バースト検知)などを使用することで自動推薦する[Python]

こんにちは,しまさん(@nitkcdadon)です.

最近周囲の人達の影響もあってVTuberの動画を見るようになったり,そのおかげで分析をするようになったりもしました.

「新人VTuber」のTwitterアカウントデータから見る「新人」についての一考察新人VTuberのアカウント作成日からの経過日数を見ることでVTuberにおける「新人」がどのような分布になっているか確認する.今回は新人VTuber1021人のデータを用いて分析をしている....
hololive所属ユーザのフォロー類似度をjaccard係数で可視化する話hololive所属ユーザのフォロー類似度をjaccard係数で可視化する話. PythonとTwitterAPIを使って可視化しました....
VTuber(ユーザ)の属性を決めているのは一体誰なのか~Twitterデータから分析する~VTuberの属性を決めるのは誰なのか,Twitterのデータを使用して分析します.フォロー,フォロワーを集合とすることで6種類のデータを作成し,wordcloudで可視化しています.これはソーシャルメディアでユーザの属性を決める際の1つの事例として十分な知見を得ることができます....

私の見るVTuberの人達は基本的にYouTubeでLive配信をしているんですが個々の人達の時間が1時間~耐久なら1x時間配信をしているので正直全部追うのは難しいです.

沼に片足つっこみはじめた私ですらこれですから前から様々なVTuberを追っている人は1日が24hでは足らないと常々思っているのではないでしょうか.

そこでそんなVTuberのコンテンツを消費したいが全て消費することが制約的に厳しい人達に「光あれ…」といった文化が「切り抜き動画」です.

これはTwitterやYouTube上にVTuberの配信における面白かったポイントを切り出して投稿するタイプの動画です.配信に比べて非常に短い時間で構成されているために複数視聴することができるだけでなく,コンテンツとして高い完成度を誇っているためについついたくさん見てしまいます.

そんな切り抜き動画をあげてくださる人にはいつも感謝しているのですが,問題点がいくつかあります.

まず,切り抜き動画とアーカイブされた動画との1対1関係を見出すのが難しい点です.

切り抜き動画は通常インパクトあるタイトルであることが多く,この配信の切り抜きである!ということはその切り抜き動画を開き,概要欄を見る必要があります.タイトルのおかげで吸い寄せられることも助かることもしばしばですが,好きな配信者の動画のこの切り抜き!というのを探すことが困難になっているのが現状です.

そして切り抜きをあげるユーザは個人であるという点です.これは切り抜きを見ているくせに文句を言う筋合いがないのは承知していますが,個人で好きな場面を切り抜いている以上,その配信を見ていたユーザの多くが盛り上がった部分を反映していない可能性があります.

そこで今回はアーカイブされたYouTube LiveのLive Chatにおけるコメントの時系列変化からバーストを検知し,バースト検知部分=みんなが良いと思ったと対応付けすることでその動画における盛り上がり部分を推薦します.

これを使用することで任意の動画の切り抜きポイント=盛り上がり部分を得ることができるので,そこを見れば1配信に対応付けられた切り抜き動画を見たことと同じ効果が期待されます.

今回のバースト検知には単純な単位時間あたりのコメント数で判断する方法とMACDを用いたバースト検知の2つを試していきます.

環境

  • macOS Catalina 10.15.x
  • Python 3.6.8

ブログの仕様上[1]が王冠になってるので読み替えていただけると嬉しいです.

スポンサーリンク

ライブラリのインポート

※ 使っていないライブラリもあります

from pprint import pprint
import os
import sys
import glob
import json
from bs4 import BeautifulSoup
from pathlib import Path
import re
import math
import requests
import collections
import numpy as np
import japanize_matplotlib
import matplotlib.pyplot as plt
import datetime
import time

matplotlibで日本語対応が面倒な方はjapanize_matplotlibがオススメです.

macOS Mojaveでmatplotlibの豆腐フォントを日本語にする方法(Python)macOS Mojaveでmatplotlibの豆腐フォントを日本語にする方法について紹介しています.Pythonでmatplotlibを使う際にmacで文字化けするのでその対処をしています.matplotlibrcを編集しています....

スポンサーリンク

今回対象の動画

今回はhololiveという企業に所属するVTuber二人の配信を対象にします.約60分弱の動画ですが,これが好きなVTuberの数あると思うと時間足らないのも仕方ないですね.

LiveChatの取得

YouTubeのLive放送のLiveChat取得は以下のサイトのコードを参考にさせてもらっています.

ただ,video-idをtarget_urlから雑に取り出し,その名前のフォルダを作成,そこに「vid+”/archives_live_chat_”+vid+”.txt”」でコメントデータを保存しています.

target_url = "https://www.youtube.com/watch?v=up-LUs9c6Y8"

vid = re.search("=.*",target_url)
vid = vid.group()
vid = re.sub("=","",vid)

os.makedirs(vid,exist_ok=True)

スポンサーリンク

取得のしたコメントからテキストと時間を抜き出す

ここでは先程得たjson形式のテキストからコメントとその時間(msec -> sec)を取り出します.

なお,予備調査では全てのコメントを拾っていたのですが今回は面白さに起因する言葉がテキストに入っていれば追加するようにしています.

w_word = ["w","W","w","W","笑","草"]

とりあえずw系と笑,草を入れています.ここは改良できるポイントですね.盛り上がり部分を取るんであれば「おおおおおおおおおおお」とか「ああああああああああ」みたいな同じ文字のn文字続きも拾うのはありかもしれません.

with open(vid+"/archives_live_chat_"+vid+".txt","r",encoding="utf-8_sig") as f:
    c = 0
    text_l = []
    time_l = []
    for line in f:
        line = re.sub("\n","",line)
        comdata = json.loads(line)
        text = comdata["replayChatItemAction"]["actions"][0]["addChatItemAction"]["item"]
        if not text.get("liveChatTextMessageRenderer"):
            continue
        text = text.get("liveChatTextMessageRenderer")
        text = text["message"]["runs"][0]["text"]
        result = re.search("(w|W|w|W|笑|草)",text)
        if not result:
            continue
        time = comdata["replayChatItemAction"]["videoOffsetTimeMsec"]
        time = int(int(time)/1000)
        
        text_l.append(text)
        time_l.append(time)

単位時間で可視化

データの外観を掴むために単位時間あたりのコメント数で雑に可視化してみます.

time_co = collections.Counter(time_l)
time_co = sorted(time_co.items())
x = [x for x,y in time_co]
y = [y for x,y in time_co]

plt.figure(figsize=(16, 10), dpi=60)
plt.xlabel('sec',fontsize = 24)
plt.ylabel('count',fontsize = 24)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.plot(x,y)
plt.show()

これでも多いところを推薦できそうですが,配信者の設定で遅延があることやチャットを打つユーザの遅延を考えるともう少し幅をもたせてあげるのがよさそうです.

15sec単位で集計して可視化・推薦

ここでは15sec毎にコメント数を集計してあげることで先述した問題を解決したいと思います.とりあえず雑に書いてるので申し訳無いですが.

xx =[]
yy =[]
gdic = dict()
c=15
s=0
count = 0
for line in time_co:
    t,tc = line
    if t <= c:
        s += tc
        count += 1
    else:
        if c ==15:
            for i in range(15):
                xx.append(i)
                yy.append(s)
        
            gdic[str(0)+"-"+str(c)] = s
        else:
            for i in range(count):
                xx.append(c+i)
                yy.append(s)
            gdic[str(c)+"-"+str(c+20)] = s
        count = 0
        c += 15
        s = tc
plt.figure(figsize=(16, 10), dpi=60)
plt.xlabel('sec',fontsize = 24)
plt.ylabel('count',fontsize = 24)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.plot(xx,yy)

目立ってコメントが多いところがいくつか見られますね.バースト検知の1つとして単純にコメントが多かった部分5件を表示してあげたいと思います.

highlight_list = sorted(gdic.items(), key=lambda x: -x)

recommend_count = 0
base_url = "https://www.youtube.com/watch?v="+vid

for highlight in highlight_list:
    timerange , _ = highlight
    time = timerange.split("-")
    s = int(time[0])
    e = int(time)
    if s == 0:
        continue
    if recommend_count == 5:
        break
    sm,ss = divmod(s,60)
    em,ee = divmod(e,60)
    print("recommend_point : ",str(sm)+":"+str(ss)+" ~ "+str(em)+":"+str(ee)," +-30s" , " count : ",_)
    print("\t",base_url+"&feature=youtu.be&t="+str(s-30)+"&end="+str(e+30))
        
    recommend_count += 1

得られたバーストポイントから前後30sを多めに取っています.endパラメータは埋め込みの際に使えるので採用しているだけで特に必要はないです.

recommend_point :  46:0 ~ 46:20  +-30s  count :  65
     推薦URL&t=2730&end=2810
recommend_point :  14:45 ~ 15:5  +-30s  count :  57
     推薦URL&t=855&end=935
recommend_point :  17:45 ~ 18:5  +-30s  count :  57
     推薦URL&t=1035&end=1115
recommend_point :  54:15 ~ 54:35  +-30s  count :  50
    推薦URL&t=3225&end=3305
recommend_point :  54:30 ~ 54:50  +-30s  count :  50
     推薦URL&t=3240&end=3320

top5であることだけをわかっている状態でいいのなら時系列順に並べたくなるのが人間なのでそうします.

recommend_count = 0
base_url = "https://www.youtube.com/watch?v="+vid
recommend_dic = dict()
for highlight in highlight_list:
    timerange , _ = highlight
    time = timerange.split("-")
    s = int(time[0])
    e = int(time)
    if s == 0:
        continue
    if recommend_count == 5:
        break
    
    recommend_dic[s] = dict()
    recommend_dic[s]["t_range"] = timerange
    recommend_dic[s]["s_t"] = s
    recommend_dic[s]["e_t"] = e
    recommend_dic[s]["count"] = _
        
    recommend_count += 1

s_recom_l = sorted(recommend_dic.items())

for recpoint in s_recom_l:
    _ , rec_dic = recpoint
    print("recommend_point : ",rec_dic["t_range"]," +-30s" , " count : ",rec_dic["count"])
    print("\t",base_url+"&feature=youtu.be&t="+str(rec_dic["s_t"]-30))
recommend_point :  885-905  +-30s  count :  57
     推薦URL&t=855
recommend_point :  1065-1085  +-30s  count :  57
     推薦URL&t=1035
recommend_point :  2760-2780  +-30s  count :  65
     推薦URL&t=2730
recommend_point :  3255-3275  +-30s  count :  50
     推薦URL&t=3225
recommend_point :  3270-3290  +-30s  count :  50
     推薦URL&t=3240

MACDを用いて可視化・推薦

MACDはテクニカル分析でよく知られる指標の1つです.バースト検知といえばKleinbergの提案手法が有名ですが,MACDのほうがシンプルかつ計算量が少ないとのことで今回採用にいたりました.

MACDに関しては以下のサイトのClassを参考にさせていただきました.

class MACDData():
    def __init__(self,f,s,t):
        self.f = f
        self.s = s
        self.t = t

    def calc_macd(self, freq_list):
        n = len(freq_list)
        self.macd_list = []
        self.signal_list = []
        self.histgram_list = []

        for i in range(n):

            if i < self.f:
                self.macd_list.append(0)
                self.signal_list.append(0)
                self.histgram_list.append(0)
            else :
                macd = sum(freq_list[i-self.f+1:i+1])/len(freq_list[i-self.f+1:i+1]) - sum(freq_list[max(0,i-self.s):i+1])/len(freq_list[max(0,i-self.s):i+1])
                self.macd_list.append(macd)
                signal = sum(self.macd_list[max(0,i-self.t+1):i+1])/len(self.macd_list[max(0,i-self.t+1):i+1])
                self.signal_list.append(signal)
                histgram = macd - signal
                self.histgram_list.append(histgram)  

MACD(f,s,t)を論文と同じく4,8,5に設定しています.

f = 4
s = 8
t = 5
macd_data = MACDData(f,s,t)
macd_data.calc_macd(yy)
my = np.array(macd_data.histgram_list)

plt.figure(figsize=(16, 10), dpi=60)
plt.xlabel('sec',fontsize = 24)
plt.ylabel('MACD',fontsize = 24)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.plot(xx,my)

15s足とでもいえばいいんでしょうか.とりあえず出た結果から推薦していきます.

ly = list(macd_data.histgram_list)

xx =[]
yy =[]
gdic = dict()
c=15
s=0
count = 0
lyz = 0
for line in time_co:
    t,tc = line
    if t <= c:
        s += tc
        count += 1
    else:
        if c ==15:
            for i in range(15):
                xx.append(i)
                yy.append(s)
        
            gdic[str(0)+"-"+str(c)] = ly[lyz]
            lyz+=1
        else:
            for i in range(count):
                xx.append(c+i)
                yy.append(s)
            gdic[str(c)+"-"+str(c+20)] = ly[lyz]
            lyz+=1
        count = 0
        c += 15
        s = tc

highlight_list = sorted(gdic.items(), key=lambda x: -x)

recommend_count = 0
base_url = "https://www.youtube.com/watch?v="+vid

for highlight in highlight_list:
    timerange , _ = highlight
    time = timerange.split("-")
    s = int(time[0])
    e = int(time)
    if s == 0:
        continue
    if recommend_count == 5:
        break
    sm,ss = divmod(s,60)
    em,ee = divmod(e,60)
    print("recommend_point : ",str(sm)+":"+str(ss)+" ~ "+str(em)+":"+str(ee)," +-30s" , " macd_histgram : ",_)
    print("\t",base_url+"&feature=youtu.be&t="+str(s-30)+"&end="+str(e+30))
        
    recommend_count += 1
recommend_point :  34:45 ~ 35:5  +-30s  macd_histgram :  11.650000000000002
     推薦URL&t=2055&end=2135
recommend_point :  34:30 ~ 34:50  +-30s  macd_histgram :  9.977777777777776
     推薦URL&t=2040&end=2120
recommend_point :  15:15 ~ 15:35  +-30s  macd_histgram :  8.5
     推薦URL&t=885&end=965
recommend_point :  10:45 ~ 11:5  +-30s  macd_histgram :  8.122222222222222
     推薦URL&t=615&end=695
recommend_point :  15:0 ~ 15:20  +-30s  macd_histgram :  7.999999999999998
     推薦URL&t=870&end=950

先程と同様でtop5であることだけをわかっている状態でいいのなら時系列順に並べたくなるのが人間なのでそうします.

recommend_count = 0
base_url = "https://www.youtube.com/watch?v="+vid
recommend_dic = dict()
for highlight in highlight_list:
    timerange , _ = highlight
    time = timerange.split("-")
    s = int(time[0])
    e = int(time)
    if s == 0:
        continue
    if recommend_count == 5:
        break
    
    recommend_dic[s] = dict()
    recommend_dic[s]["t_range"] = timerange
    recommend_dic[s]["s_t"] = s
    recommend_dic[s]["e_t"] = e
    recommend_dic[s]["count"] = _
        
    recommend_count += 1

s_recom_l = sorted(recommend_dic.items())
for recpoint in s_recom_l:
    _ , rec_dic = recpoint
    print("recommend_point : ",rec_dic["t_range"]," +-30s" , " macd_histgram : ",rec_dic["count"])
    print("\t",base_url+"&feature=youtu.be&t="+str(rec_dic["s_t"]-30))
recommend_point :  645-665  +-30s  macd_histgram :  8.122222222222222
     推薦URL&t=615
recommend_point :  900-920  +-30s  macd_histgram :  7.999999999999998
     推薦URL&t=870
recommend_point :  915-935  +-30s  macd_histgram :  8.5
     推薦URL&t=885
recommend_point :  2070-2090  +-30s  macd_histgram :  9.977777777777776
     推薦URL&t=2040
recommend_point :  2085-2105  +-30s  macd_histgram :  11.650000000000002
     推薦URL&t=2055

結果の比較

単純にコメント数で推薦した場合とMACDで推薦した場合を再掲します.

今回の場合,推薦URLは「https://www.youtube.com/watch?v=up-LUs9c6Y8&feature=youtu.be」となっていますので&tの部分を足して是非確認してみてください.

recommend_point :  885-905  +-30s  count :  57
     推薦URL&t=855
recommend_point :  1065-1085  +-30s  count :  57
     推薦URL&t=1035
recommend_point :  2760-2780  +-30s  count :  65
     推薦URL&t=2730
recommend_point :  3255-3275  +-30s  count :  50
     推薦URL&t=3225
recommend_point :  3270-3290  +-30s  count :  50
     推薦URL&t=3240
recommend_point :  645-665  +-30s  macd_histgram :  8.122222222222222
     推薦URL&t=615
recommend_point :  900-920  +-30s  macd_histgram :  7.999999999999998
     推薦URL&t=870
recommend_point :  915-935  +-30s  macd_histgram :  8.5
     推薦URL&t=885
recommend_point :  2070-2090  +-30s  macd_histgram :  9.977777777777776
     推薦URL&t=2040
recommend_point :  2085-2105  +-30s  macd_histgram :  11.650000000000002
     推薦URL&t=2055

5件だけを取り出しているからかもしれませんが,コメントの方は後半部分の時間も推薦されていますが,MACDは5件だけでは最後部分の推薦はなかったですね.

推薦部分における面白さは主観によるところが大きいので判断が難しいですね.効果検証もおいおいできたら嬉しいです.

さいごに

今回は2つのバースト検知方法でアーカイブされたYouTube Liveの切り抜きポイント=面白い部分を推薦するプログラムを書きました.

ただ動画における面白さには大きくは2パターン考えられ,単純に笑えるという意味での面白さとコンテンツ内における達成(ゲームにおける難関ステージクリアなど)の面白さがあると思うのでこれをどう分離するかは今後の課題ですね.

推薦部分を埋め込むだけのシンプルなWebアプリケーションを作ってもいいかもしれません.

MACDプログラムに関しては適用を間違えているかもしれないのでそこは申し訳無いです.

バースト検知を機械学習的アプローチで攻めるのもありですし,そこらへんもおいおい頑張っていこうと思います.

ABOUT ME
しまさん
てくてくぷれいす運営者のしまさんです. 高専→大学編入してから行動的な大学生  自身の変化を求めてブログを始める グレープフルーツと本が大好物 IT系のことやブログ,高専や編入,大学生活に関することを発信中!!詳しいプロフィールはこちら≫ 投げ銭はコチラへ YouTubeはコチラへ 質問はコチラ