エイバースの中の人

アプリとWEBサービスを作っているABARSのBLOGです。

UnityのAndroidビルドでx86のNative Pluginがapkに含まれない

UnityのAssets/Plugins/Android/x86に.soを置いてもUnityのInspectorが認識してくれず、apkに含まれないため、ランタイムエラーになります。

解決方法として、Assets/Plugins/Android/libs/armeabi-v7a、Assets/Plugins/Android/libs/x86に.soを置いた上で、ReimportしないとInspectorに表示されず、apkに含まれないようです。

一度、このパスに配置してmetaファイルが生成されると、自由に移動可能になります。

MacでPaintsChainerを動かす

PaintsChainerがすごすぎたので、ソースコードをダウンロードしてMacで動かしてみました。

paints_chainer


手順としては、PaintsChainerをCloneした後、学習済みデータをcgi-bin/paint_x2_unet/models/に置きます。

次に、Chainerをpip install chainerでインストールします。

PaintsChainerのPython 3.0に対して、MacのデフォルトのPythonは2.7なので、server.pyのhttp.serverをBaseHTTPServerに書き換える必要があります。


diff --git a/server.py b/server.py
index 52b37be..d6622a5 100644
--- a/server.py
+++ b/server.py
@@ -1,7 +1,11 @@
#!/usr/bin/env python

-import http.server
-import socketserver
+#import http.server
+#import socketserver
+#from urllib.parse import parse_qs
+
+import BaseHTTPServer
+import CGIHTTPServer

import os, sys
import base64
@@ -10,7 +14,6 @@ import json
import argparse

from cgi import parse_header, parse_multipart
-from urllib.parse import parse_qs


#sys.path.append('./cgi-bin/wnet')
@@ -19,13 +22,13 @@ import cgi_exe



-class MyHandler(http.server.CGIHTTPRequestHandler):
+class MyHandler(CGIHTTPServer.CGIHTTPRequestHandler):
def __init__(self,req,client_addr,server):
- http.server.CGIHTTPRequestHandler.__init__(self,req,client_addr,server)
+ CGIHTTPServer.CGIHTTPRequestHandler.__init__(self,req,client_addr,server)

def parse_POST(self):
ctype, pdict = parse_header(self.headers['content-type'])
- pdict['boundary'] = bytes(pdict['boundary'], "utf-8")
+ #pdict['boundary'] = bytes(pdict['boundary'], "utf-8")
if ctype == 'multipart/form-data':
postvars = parse_multipart(self.rfile, pdict)
elif ctype == 'application/x-www-form-urlencoded':
@@ -80,7 +83,10 @@ class MyHandler(http.server.CGIHTTPRequestHandler):
else:
paintor.colorize(id_str, blur=blur)

- content = bytes("{ 'message':'The command Completed Successfully' , 'Status':'200 OK','success':true , 'used':"+str(args.gpu)+"}", "UTF-8")
+ #content = bytes("{ 'message':'The command Completed Successfully' , 'Status':'200 OK','success':true , 'used':"+str(args.gpu)+"}", "UTF-8")
+
+ content = "{ 'message':'The command Completed Successfully' , 'Status':'200 OK','success':true , 'used':"+str(args.gpu)+"}"
+
self.send_response(200)
self.send_header("Content-type","application/json")
self.send_header("Content-Length", len(content))
@@ -102,7 +108,9 @@ print('GPU: {}'.format(args.gpu))

paintor = cgi_exe.Paintor( gpu = args.gpu )

-httpd = http.server.HTTPServer(( args.host, args.port ), MyHandler)
+#httpd = http.server.HTTPServer(( args.host, args.port ), MyHandler)
+httpd = BaseHTTPServer.HTTPServer((args.host, args.port ), MyHandler)
+
print('serving at', args.host, ':', args.port, )
httpd.serve_forever()


また、MacはAMDのGPUを搭載しておりCUDAが動かないので、GPUを切ります。cuda.と.get()をコメントアウトします。


diff --git a/cgi-bin/paint_x2_unet/cgi_exe.py b/cgi-bin/paint_x2_unet/cgi_exe.py
index 33c381b..86bc4d7 100755
--- a/cgi-bin/paint_x2_unet/cgi_exe.py
+++ b/cgi-bin/paint_x2_unet/cgi_exe.py
@@ -30,11 +30,11 @@ class Paintor:
self.gpu = gpu

print("load model")
- cuda.get_device(self.gpu).use()
+ #cuda.get_device(self.gpu).use()
self.cnn_128 = unet.UNET()
self.cnn = unet.UNET()
- self.cnn_128.to_gpu()
- self.cnn.to_gpu()
+ #self.cnn_128.to_gpu()
+ #self.cnn.to_gpu()
lnn = lnet.LNET()
#serializers.load_npz("./cgi-bin/wnet/models/model_cnn_128_df_4", cnn_128)
#serializers.load_npz("./cgi-bin/paint_x2_unet/models/model_cnn_128_f3_2", cnn_128)
@@ -56,7 +56,7 @@ class Paintor:


def liner(self, id_str):
- cuda.get_device(self.gpu).use()
+ #cuda.get_device(self.gpu).use()

image1 = cv2.imread(path1, cv2.IMREAD_GRAYSCALE)
image1 = np.asarray(image1,self._dtype)
@@ -64,16 +64,16 @@ class Paintor:
image1 = image1[:, :, np.newaxis]
img = image1.transpose(2, 0, 1)
x = np.zeros((1, 3, img.shape[1], img.shape[2] )).astype('f')
- x = cuda.to_gpu(x)
+ #x = cuda.to_gpu(x)

y = lnn.calc(Variable(x), test=True)
- output = y.data.get()
+ output = y.data#.get()

self.save_as_img( output[0], self.root + "line/"+id_str+".jpg" )


def colorize_s( self, id_str, blur=0, s_size=128):
- cuda.get_device(self.gpu).use()
+ #cuda.get_device(self.gpu).use()

dataset = ImageAndRefDataset([id_str+".png"],self.root+"line/",self.root+"ref/" )
test_in_s, test_in = dataset.get_example(0, minimize=True, blur=blur, s_size=s_size)
@@ -81,30 +81,30 @@ class Paintor:

x[0,:] = test_in_s

- x = cuda.to_gpu(x)
+ #x = cuda.to_gpu(x)
y = self.cnn_128.calc(Variable(x), test=True )
- output = y.data.get()
+ output = y.data#.get()

self.save_as_img( output[0], self.outdir_min + id_str + ".png" )

def colorize_l( self, id_str ):
- cuda.get_device(self.gpu).use()
+ #cuda.get_device(self.gpu).use()

dataset = ImageAndRefDataset([id_str+".png"],self.root+"line/",self.root+"out_min/" )
test_in, test_in_ = dataset.get_example(0,minimize=False)
x = np.zeros((1, 4, test_in.shape[1], test_in.shape[2] )).astype('f')
x[0,:] = test_in

- x = cuda.to_gpu(x)
+ #x = cuda.to_gpu(x)
y = self.cnn.calc(Variable(x), test=True )

- output = y.data.get()
+ output = y.data#.get()

self.save_as_img( output[0], self.outdir + id_str + ".jpg" )


def colorize( self, id_str, blur=0, s_size=128):
- cuda.get_device(self.gpu).use()
+ #cuda.get_device(self.gpu).use()

dataset = ImageAndRefDataset([id_str+".png"],self.root+"line/",self.root+"ref/" )
test_in_s, test_in = dataset.get_example(0,minimize=True)
@@ -117,20 +117,21 @@ class Paintor:
x[0,:] = line
input_bat[0,0,:] = line2

- x = cuda.to_gpu(x)
+ #x = cuda.to_gpu(x)
y = self.cnn_128.calc(Variable(x), test=True )

- output = y.data.get()
+ output = y.data#.get()

self.save_as_img( output[0], self.outdir_min + id_str +"_"+ str(0) + ".png" )

for ch in range(3):
input_bat[0,1+ch,:] = cv2.resize(output[0,ch,:], (test_in.shape[2], test_in.shape[1]), interpolation = cv2.INTER_CUBIC)

- x = cuda.to_gpu(input_bat)
+ #x = cuda.to_gpu(input_bat)
+ x = input_bat
y = self.cnn.calc(Variable(x), test=True )

- output = y.data.get()
+ output = y.data#.get()

self.save_as_img( output[0], self.outdir + id_str +"_"+ str(0) + ".jpg" )

diff --git a/cgi-bin/paint_x2_unet/img2imgDataset.py b/cgi-bin/paint_x2_unet/img2imgDataset.py
index d26f580..e98238e 100755
--- a/cgi-bin/paint_x2_unet/img2imgDataset.py
+++ b/cgi-bin/paint_x2_unet/img2imgDataset.py
@@ -41,11 +41,11 @@ class ImageAndRefDataset(chainer.dataset.DatasetMixin):
if minimize :
if image1.shape[0] < image1.shape[1]:
s0 = s_size
- s1 = int( image1.shape[1]*(s_size/image1.shape[0]) )
+ s1 = int( image1.shape[1]*(1.0*s_size/image1.shape[0]) )
s1 = s1-s1%16
else:
s1 = s_size
- s0 = int( image1.shape[0]*(s_size/image1.shape[1]) )
+ s0 = int( image1.shape[0]*(1.0*s_size/image1.shape[1]) )
s0 = s0-s0%16

_image1 = image1.copy()


後は、python server.pyで動作します。ブラウザからhttp://localhost:8000/static/などでアクセスします。

推論側はGPU無効でも数十秒で返ってくるため、クライアントサイドでも動かせそうな印象です。ただ、学習済みデータが134.3MBもあるので、これをそのままクライアントに転送するのが難しそうですね。負荷自体は低いので、アプリ化などは容易そうです。

テクノロジーは元記事が詳しいです。

学習側は、画像をYUV変換して、Yをベースに線に変換しているようです。また、乱数で2値化したり、左右反転したり、ノイズを足したりして、データ量を増やしていますね。

ネットワークの構造はU-netでした。Batch Normalization重要そうです。評価関数は元画像との二乗誤差+アドバイサリーロスを10:1で混合。アドバイサリーロスについては、生成した画像が偽物であると推定させることで、競わせるようです。ソースコードではtrain.pyでCNNが生成した画像をy_fake、正解画像をy_realとして、アドバイサリーネットに入力し、見分けられない画像を生成させようとしています。

ユーザによる色の入力はどう実装しているか不思議だったのですが、線画に元データからランダムに色付きピクセルを追加したものを学習データとしているようです。

いきなり512x512にしないで、128x128を経由しているのは過学習対策なんですかね。ノウハウがありそうです。

Chainerの係数列はNumPyのnpzフォーマットのようです。ダンプしてみたい。

これだけ少ないコード量で、これほどのサービスが作れるのは驚きです。ただ、ソースコードにうまく学習させるためのノウハウが散りばめられており、一朝一夕ではないなという印象です。

Firebase for UnityをUnity Cloud Buildで使用する

Firebase Analytics + Push Notificationにおける、ローカル環境でのLinker Errorの解消方法とUnity Cloud Buildでの使用方法をまとめました。

Firebase for Unityのインポート


FirebaseのUnity PackageはDownload the SDKからダウンロードすることができます。今回は、Push Notificationを実装するため、FirebaseAnalyticsとFirebaseMessagingの2つのUnity Packageをプロジェクトにインポートします。

firebase_unity


いきなり本番のプロジェクトに導入するのはリスクがあるので、サンプルプログラムをダウンロードして、そこにUnity Packageをインポートするのがオススメです。

iOSでPush通知を使用するには、APNs の SSL 証明書のプロビジョニングが必要です。AppleのProvision Portalで取得した証明書をp12に変換し、Firebaseのサイトに登録します。

その後、Firebaseのサイトから、プロジェクトのgoogle-services.jsonとGoogleService-Info.plistをダウンロードし、UnityのAssetsの中の好きなフォルダに登録します。

これで、Push通知が使用できるようになります。

ローカル環境でのLinker Error


iOSビルドでLinker Errorが出る場合は、cocoapodsのバージョンを確認します。コマンドラインからpod --versionとすると、バージョンが表示されますが、これが1.0以降でない場合に、Firebaseがうまくリンクされません。

Unityは、Xcodeのプロジェクトファイルを生成する際、Podfileが存在する場合にpodコマンドを叩いてくれます。Firebaseはpodコマンドを使って依存ライブラリをダウンロードするのですが、podコマンドに失敗してエラーが発生した場合もプロジェクトファイルが生成されてしまうため、ビルドエラーが発生するようです。

podのバージョンが古い場合は、gem update cocoapodsでバージョンアップが可能です。

Unity Cloud BuildでのLinker Error


ローカルでビルドするにはこれだけでOKですが、Unity Cloud Buildではcocoapodsが使えないため、以下のエラーが出ます。


Error running cocoapods. Please ensure you have at least version 1.0.0. You can install cocoapods with the Ruby gem package manager:


この場合、CocoaPods を使用せずに統合するからzipをダウンロードし、手動でFrameworksフォルダのFirebaseMessaging.frameworkなどをPlugin/iOSフォルダに置く必要があります。

copy


コピーする必要のあるファイルです。


## Analytics
- FirebaseAnalytics.framework
- FirebaseInstanceID.framework
- GoogleInterchangeUtilities.framework
- GoogleSymbolUtilities.framework
- GoogleUtilities.framework
## AdMob (~> Analytics)
- GoogleMobileAds.framework
## AppIndexing (~> Analytics)
- FirebaseAppIndexing.framework
## Auth (~> Analytics)
- FirebaseAuth.framework
- GoogleNetworkingUtilities.framework
- GoogleParsingUtilities.framework
## Crash (~> Analytics)
- FirebaseCrash.framework
## Database (~> Analytics)
- FirebaseDatabase.framework
## DynamicLinks (~> Analytics)
- FirebaseDynamicLinks.framework
## Invites (~> Analytics)
- FirebaseDynamicLinks.framework
- FirebaseInvites.framework
- GoogleAppUtilities.framework
- GoogleAuthUtilities.framework
- GoogleNetworkingUtilities.framework
- GoogleParsingUtilities.framework
- GooglePlusUtilities.framework
- GoogleSignIn.framework

You'll also need to add the resources in the
Resources directory into your target's main
bundle.
## Messaging (~> Analytics)
- FirebaseMessaging.framework
- GoogleIPhoneUtilities.framework
## RemoteConfig (~> Analytics)
- FirebaseRemoteConfig.framework
- GoogleIPhoneUtilities.framework
## Storage (~> Analytics)
- FirebaseStorage.framework
- GoogleNetworkingUtilities.framework


その上で、Firebase/Editor/AppDeps.csなど*Deps.csから以下のようにpodの呼び出しをコメントアウトします。


#elif UNITY_IOS
/*
Type iosResolver = Google.VersionHandler.FindClass(
"Google.IOSResolver", "Google.IOSResolver");
if (iosResolver == null) {
return;
}
Google.VersionHandler.InvokeStaticMethod(iosResolver, "AddPod", new object[] { "Firebase/Core" }, new Dictionary() { { "version", "3.10+" }, { "minTargetSdk", "7.0" } });
*/
#endif


また、Firebaseはsqlite3とAddressBook.frameworkを要求するのでEditorフォルダに以下のスクリプトを追加し、自動化します。-ObjCがないと実行時に例外が飛ぶので、これも追加します。


using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
#if UNITY_IOS
using UnityEditor.iOS.Xcode;
using System.Collections.Generic;
#endif
using System.IO;

public class PostBuildProcess {

[PostProcessBuild]
public static void OnPostProcessBuild (BuildTarget buildTarget, string path) {
#if UNITY_IOS
string projPath = Path.Combine (path, "Unity-iPhone.xcodeproj/project.pbxproj");

PBXProject proj = new PBXProject ();
proj.ReadFromString (File.ReadAllText (projPath));

string target = proj.TargetGuidByName ("Unity-iPhone");
proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-lz");
proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-lsqlite3");
proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-ObjC");

List frameworks = new List () {
"CoreData.framework",
"AddressBook.framework"
};

foreach (var framework in frameworks) {
proj.AddFrameworkToProject (target, framework, false);
}

File.WriteAllText (projPath, proj.WriteToString ());
#endif
}
}


この段階で、Firebase Analyticsは動作するのですが、Firebase Messagingは実行時に例外で落ちます。どうやら、Unity Pluginと公式サイトのzipのFrameworkのバージョンが異なることが原因のようです。そのため、Firebase Messagingを使用する場合は、ローカルでcocoapodsを使用してiOSビルドした後、プロジェクトのFrameworksフォルダに含まれるFirebaseのFrameworkで、zipからコピーしたFrameworkを置き換える必要があります。

copy


また、GTMDefines.h、GTMLogger.h、GTMLogger.m、GTMNSData+zlib.h、GTMNSData+zlib.mもPlugins/iOSにコピーした上で、InspectorでCompile Flagsに-fno-objc-arc -fobjc-exceptionsと記載する必要があります。

compile_flag


Androidでは、UnityでAndroid Buildに変更して、Assets -> Play Service Resolver -> Android Resolver -> Resolve Client Jarsを実行しておきます。

これでようやく、UnityでFirebase AnalyticsとNotificationを使用することができます。

firebase


XcodeのCapabilityにPush NotificationとBackground Modes -> Remote notificationを追加すると、Firebaseから発行した通知を受け取ることができます。ただし、Info.plistにFirebaseAppDelegateProxyEnabled=NOを設定しないと、アプリがバックグラウンドに回った後、フォアグラウンドに回ると、100%ハングします。

IMG_1873


XcodeのCapability設定とFirebaseAppDelegateProxyEnabledの設定をUnity Cloud Buildで自動化するには、Unityから自動でPush NotificationsをONにしたいを参考に、Editorスクリプトを作成する必要があります。


//Unity Cloud BuildでiOS Buildにfirebaseの依存ファイルを追加する
//加えて、CapabilityにPush Notificationを追加する

using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
#if UNITY_IOS
using UnityEditor.iOS.Xcode;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
#endif

public class PostBuildProcess {

[PostProcessBuild]
public static void OnPostProcessBuild (BuildTarget buildTarget, string path) {
#if UNITY_IOS
string projPath = Path.Combine (path, "Unity-iPhone.xcodeproj/project.pbxproj");

PBXProject proj = new PBXProject ();
proj.ReadFromString (File.ReadAllText (projPath));

string target = proj.TargetGuidByName ("Unity-iPhone");
proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-lz");
proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-ObjC");
proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-lc++");
proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-lsqlite3");

List frameworks = new List () {
"CoreData.framework",
"AddressBook.framework"
};

foreach (var framework in frameworks) {
proj.AddFrameworkToProject (target, framework, false);
}

//Add
File.WriteAllText (projPath, proj.WriteToString ());

//Set Push Notification Capability
CreateEntitlements(path,YourAppName);
SetCapabilities(path);
SetBackgroundMode(path);
#endif
}

#if UNITY_IOS
private static void SetBackgroundMode(string path)
{
var plistPath = Path.Combine(path, "Info.plist");

PlistDocument plist = new PlistDocument();
plist.ReadFromFile(plistPath);

plist.root.SetBoolean("FirebaseAppDelegateProxyEnabled",false);

PlistElementArray bgModes = plist.root.CreateArray("UIBackgroundModes");
bgModes.AddString("remote-notification");

plist.WriteToFile(plistPath);
}

private static void CreateEntitlements(string path,string your_appname)
{
XmlDocument document = new XmlDocument ();
XmlDocumentType doctype = document.CreateDocumentType ("plist", "-//Apple//DTD PLIST 1.0//EN", "http://www.apple.com/DTDs/PropertyList-1.0.dtd", null);
document.AppendChild (doctype);

XmlElement plist = document.CreateElement ("plist");
plist.SetAttribute ("version", "1.0");
XmlElement dict = document.CreateElement ("dict");
plist.AppendChild (dict);
document.AppendChild (plist);

XmlElement e = (XmlElement) document.SelectSingleNode ("/plist/dict");

XmlElement key = document.CreateElement ("key");
key.InnerText = "aps-environment";
e.AppendChild (key);

XmlElement value = document.CreateElement ("string");
value.InnerText = "development";
e.AppendChild (value);

string entitlementsPath = path + "/Unity-iPhone/"+your_appname+".entitlements";
document.Save(entitlementsPath);

string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
PBXProject proj = new PBXProject ();
proj.ReadFromString (File.ReadAllText (projPath));
string target = proj.TargetGuidByName ("Unity-iPhone");
string guid = proj.AddFile (entitlementsPath, entitlementsPath);
proj.SetBuildProperty(target, "CODE_SIGN_ENTITLEMENTS", "Unity-iPhone/"+your_appname+".entitlements");
proj.AddFileToBuild(target, guid);
proj.WriteToFile(projPath);
}

private static void SetCapabilities(string path)
{
string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
PBXProject proj = new PBXProject ();
proj.ReadFromString (File.ReadAllText (projPath));

string[] lines = proj.WriteToString().Split ('\n');
List newLines = new List ();
bool editFinish = false;

for (int i = 0; i < lines.Length; i++) {

string line = lines [i];

if (editFinish) {
newLines.Add (line);
} else if (line.IndexOf ("isa = PBXProject;") > -1) {
do {
newLines.Add (line);
line = lines [++i];
} while (line.IndexOf("TargetAttributes = {") == -1);

// 以下の内容はxcodeprojの内容にあるproject.pbxprojを参照してください
newLines.Add("TargetAttributes = {");
newLines.Add("xxxxxxxx = {");
newLines.Add("DevelopmentTeam = xxxxxxxx;");
newLines.Add("SystemCapabilities = {");
newLines.Add("com.apple.BackgroundModes = {");
newLines.Add("enabled = 1;");
newLines.Add("};");
newLines.Add("com.apple.Push = {");
newLines.Add("enabled = 1;");
newLines.Add("};");
newLines.Add("};");
newLines.Add("};");
editFinish = true;

} else {
newLines.Add (line);
}
}

File.WriteAllText(projPath, string.Join ("\n", newLines.ToArray ()));
}
#endif
}


FirebaseAppDelegateProxyEnabled=NOに設定したので、個別ユーザへのPushが必要な場合は、didRegisterForRemoteNotificationsWithDeviceTokenでFIRInstanceID.instanceID().setAPNSTokenを呼ぶ必要があります。iOSでプッシュ通知を実装する方法の超詳細まとめ(前編)の図のように、APNから取得したDeviceTokenをFirebaseに送らないと、個別ユーザへのPush通知ができないためです。Unity Cloud Buildで自動化したいので、UnityのiOSでAppDelegateに処理を追加するを参考に、iOSのPluginを書くのが順当ですが、ハングする問題は修正中とのことなので、とりあえずUnity SDK 1.2を待ってもよいかもしれません。(2017/1/19追記 Unity SDK 1.1.1がリリースされ、この問題は修正されました。SDK 1.1.1を使用する場合、上記、PostBuildScriptにUserNotifications.frameworkを追加し、plist.root.SetBoolean("FirebaseAppDelegateProxyEnabled",false)をコメントアウトします)

わりと大変なので、Unity Cloud Buildのcocoapods対応か、Firebase Pluginのzip版を期待したいです。

AppEngineのデータストアの自動バックアップ

AppEngineのデータストアのバックアップをcronで自動化する方法が公開されていました。 Scheduled Backupsでは、データストアをCloudStorageにバックアップします。

まず、バックアップ先のCloudStorageのバケットを作成します。

cloud_storage

次に、データストアの管理を有効にします。

datastore

AppEngineプロジェクトのcron.yamlにバックアップジョブを設定します。kindにはバックアップ対象のエンティティ名を、gs_bucket_nameに先ほど作成したバケットを登録します。

- description: backup
  url: /_ah/datastore_admin/backup.create?name=BackupToCloud&kind=Bookmark&filesystem=gs&gs_bucket_name=backup-tdnet-search
  schedule: every 12 hours
  target: ah-builtin-python-bundle

cronを実行すると、データストアの管理画面に表示されます。反映までに少し時間がかかります。

datastore_admin

Cloud Storage側にも反映されます。

bucket

今まで、データストアのバックアップは手動でぽちぽちするしかなかったのですが、自動化できるようになって便利です。

SBI証券の投資成績を集計するツールを作りました

SBI証券の譲渡益税明細から取得できるCSVを入力して、投資成績を集計することができるツールを作成しました。

まず、口座管理->取引履歴->譲渡益税明細から、対象期間のCSVをダウンロードします。

sbi


次に、TDnetSearchにアップロードすると、集計結果が表示されます。

csv


集計結果はテキストで取得することもできるため、簡単にBLOGに貼り付けることができます。

2016年の投資結果を分析して、2017年の投資につなげていければと思います。

TOBの貢献が大きかった2016年の株式投資成績

2016年の株式投資の成績(確定利益分)をまとめてみました。TOBされたマルキョウとニフティで65%の利益となっており、残りの銘柄は1銘柄につき5%以下の貢献ということで、TOBされるかどうかにかなり左右される結果となりました。

今年は、ネットネット株ランキングを作ったことで、ランキング外の銘柄は処分したのですが、ここで処分した愛知電機が年末に大きく上がってしまったのが惜しかったところです。

休暇などで時間が出来た時に、ちょこちょこ買った銘柄のパフォーマンスは、全体にほとんど影響を与えていないです。むしろ売り焦ってマイナスです。

来年はあまり売買せず、どれだけ売り焦らずに、TOBを待てるかという握力を意識した方が良さそうです。中部日本放送、日本ユピカ、東北新社、デコラックスに期待しています。

銘柄総利益に対する貢献度
マルキョウ 38%
ニフティ 27%
岡山製紙 5%
日本ユピカ 5%
インベスコ・オフィス・ジェイリート投資法人 投資証券 5%
東京ラヂエーター製造 4%
テクノメディカ 3%
三井住友フィナンシャルグループ 3%
グローバル・ワン不動産投資法人 投資証券 3%
旭化成 3%
東部ネットワーク 3%
中部日本放送 3%
東北新社 2%
三井物産 1%
日本アコモデーションファンド投資法人 投資証券 1%
積水ハウス・リ−ト投資法人 投資証券 1%
ナンシン 0%
ソニーフィナンシャルホールディングス 0%
オリックス 0%
日本BS放送 0%
フジ・メディア・ホールディングス 0%
トップリート投資法人 投資証券 0%
中部電力 0%
りそなホールディングス 0%
フリービット 0%
ダイハツディーゼル 0%
iシェアーズ米国リート不動産株ETFダウジョーンズ米国不動産 0%
リオン 0%
中部鋼鈑 0%
日本ヘルスケア投資法人 投資証券 0%
ビックカメラ 0%
ディー・エヌ・エー 0%
カンダホールディングス 0%
北陸電力 0%
リスクモンスター 0%
クリヤマホールディングス 0%
小田原機器 0%
ジオマテツク 0%
ハークスレイ 0%
新潟放送 0%
フージャースホールディングス 0%
北陸瓦斯 0%
空港施設 0%
iシェアーズ米国高配当株ETFモーニングスター配当フォーカス 0%
ソノコム 0%
キクカワエンタープライズ 0%
阪神内燃機工業 0%
横浜丸魚 0%
東京汽船 0%
名古屋電機工業 -1%
アルプス物流 -1%
U−NEXT -1%
カドカワ -1%
愛知電機 -1%

ちなみに、上記の表は、SBI証券の取引履歴の譲渡益税明細をCSVでダウンロードして、エクセルで銘柄名で並び替えた後、集計して保存、CSVをWEBツールでTABLEタグ変換しました。わりと簡単に今年の成績を振り返れるのでオススメです。

macOS Sierraの画面が真っ暗とカーソルだけになる

Mac Pro 2013のmacOS Sierraを10.12.2に上げたところ、画面が真っ暗とカーソルだけになりました。macOS 10.12 Sierra - 6 (268)の37さんと同様に、/Library/Preferences/loginwindow.plistを消したら治ったのでメモです。

詳しい手順は、Mac - Mac boots to a black screen with cursorにあります。起動時にcommand (⌘) + Sを押してコマンドラインモードで起動、/sbin/fsck -fyでディスクチェック、/sbin/mount -uw /で書き込み権限を設定、rm /Library/Preferences/com.apple.loginwindow.plist をした後、rebootで治りました。

どきどきします。

Unityのシーン変更でメモリを解放する

iPhone5Sと6は、メモリが1GBしかないため、UIの巨大画像がメモリを圧迫し、シーンの遷移で落ちることがあります。

SceneManager.LoadSceneの第二引数にLoadSceneMode.Singleを設定したとしても、シーン移行のタイミングでは、遷移前のシーンと、遷移後のシーンの両方のメモリを専有するようです。

最初は、テクスチャの参照カウントを0にすれば、LoadScene内でも解放されるはずだと期待して、循環参照になりそうなGameObjectにOnDestroyでnullを代入したりしていたのですが、一向に参照カウントが減少せず、仕様だと諦めました。

2シーンの合計となると、半分のメモリしか使用できないため、ゲームのクオリティが落ちてしまいます。この問題を解消するには、空のシーン、俗に言うローディング画面を間に挟むとよいようです。

遷移開始時は以下のようにします。

SceneManager.LoadScene ("loading");
Resources.UnloadUnusedAssets();
System.GC.Collect();

Updateの中で、loadingに遷移したタイミングで本当に行きたかったシーンに遷移します。

if(SceneManager.GetActiveScene().name=="loading"){
SceneManager.LoadScene("next_scene");
Resources.UnloadUnusedAssets();
System.GC.Collect();
}

これにより、iPhone5Sでも落ちなくなりました。

2016年に買って良かったもの

2016年も年末ということで、今年、買ってよかったものをリストアップしてみました。

iPhone7


IMG_1870

昨年まではiPhone5Sを使っていて、iPhone7は大きすぎるかなと思っていたのですが、Suicaに対応したということで買い替えました。薄くなった分、大きさにもすぐに慣れ、逆にiPhone5Sが小さく見えるから不思議なものです。ホームボタンも慣れると逆にiPhone5Sが深く感じますね。今年、一番、満足度が高い買い物でした。

Apple Watch Series 2


IMG_2023

こちらもSuica対応ということで買い替え。残念ながら、Suicaはロック中に動作しないためほとんど使っていませんが、IDが指紋認証なしで使えて便利です。

Sony LED電球スピーカー


IMG_2018

SonyのLED電球スピーカーの二代目です。音楽を聞くにはモノラルなので厳しいですが、ポッドキャストを聞くには最適です。天井から音が鳴るので、講演会にいるような気分になれます。RebuildFM専用機。



ルンバ870


IMG_2019

ルンバ500のサポートの終了に伴い、安く買い換えられるということで購入。ブラシが新しくなって絡みにくくなったのと、スケジュールで自動的に掃除が始まるのが便利です。



Scan Snap IX100


IMG_2020

M1500からの買い替え。USB給電とScan Snap Cloudで場所を取りません。昔は自炊のためにM1500を使っていたのですが、Kindleのおかげでクレジット明細のスキャンぐらいにしか使わなくなっていたので、コンパクトになって満足です。名刺管理もEightに移行して、ペーパーレス化が進んでいます。



なめこ日めくりカレンダー


IMG_2021

2016年は、よつばとカレンダーがなかったのですが、なめこカレンダーのおかげで精神を健全に保てました。2017年版も、もちろん買いました。来年も出て欲しい!



IMG_2014


無印リュック


IMG_2025

バイヤーも使っているとうわさの無印のリュックです。最近はビジネスシーンでもリュックを使うようになってきたので、軽くてがっつり入る無印のリュックは最適でした。

Bose Quiet Comfort 35


IMG_2022

待望のノイズキャンセリング+ワイヤレス。2個買いました。電源を入れると自動的にペアリングされる、USBで充電できる、残電池量をボイスで教えてくれる、など、完成度がとても高いです。



ヤコブセンの置き時計


IMG_2027

限定色があまりに良かったので。ブラックモデルとくらべて、ムーブメントが静かになりました。



デロンギのケトル


IMG_2026

カッパーカラーがマイブームで、ずんぐりむっくりな形状に惹かれて買ってしまいました。保温性能は低いですが、デザイン優先で。



Apple MagicKeyboard


IMG_2024

Lightningで充電できるようになり、電池交換のストレスが大幅に減りました。来年、Touch Bar付きのモデルが出ないかなと期待しています。



iPad Pro 9.7


IMG_2031

最初に大きい方を買って、大きすぎたので小さいのに買い替えました。社外での打ち合わせは、Mac Book 12ではなく、こちらがメインになりました。軽さと小ささは正義です。キーボードがあると、ビジネス感が出ます。Apple Pencilも買いましたが、あまり使ってないです。



今年は年末にいろいろな機器を買い替えました。来年もよい製品がいろいろ出て欲しいです。

OneSignalを使ってUnityにPush通知を実装する

UnityでPush通知を実装する場合、OneSignalを使うと便利です。無料で任意のセグメントのユーザに通知を送ることができます。

iOSの設定

(1) アプリケーションフォルダにあるキーチェーンアクセスを開きます
(2) メニューの「証明書アシスタント」から「認証局に証明書を要求」してディスクに保存します
(3) iOS Dev CenterのProvision Portalへ行き、Certificatesの項目で、Apple Push Notification Authentication Key (Sandbox & Production)を作成します
(4) aps.cerをダウンロードします
(5) キーチェーンアクセスに取り込んで、右クリックし、p12で書き出します(左のタブをログインに合わせておかないとp12で書き出せません)
(6) OneSignalに登録します

証明書は毎年更新が必要です。

Androidの設定

(1) Firebaseのプロジェクトを作成してFCMのサーバーキーを取得します
(2) OneSignalに登録します

Unityの設定

(1) OneSignalのUnity SDKをインポートします
(2) 以下のコールバックを任意のスクリプトに登録します(ONE_SIGNAL_API_KEYは自分のものに書き換えます)
(3) OneSignalのサイトからPush通知を発行すると、アプリを起動している場合、ダイアログが表示されたあと、HandleNotificationOpenedが呼ばれます

using System.Collections.Generic;  
void Start () {
    // Enable line below to enable logging if you are having issues setting up OneSignal. (logLevel, visualLogLevel)
    // OneSignal.SetLogLevel(OneSignal.LOG_LEVEL.INFO, OneSignal.LOG_LEVEL.INFO);
  
  OneSignal.StartInit(ONE_SIGNAL_API_KEY)
    .HandleNotificationOpened(HandleNotificationOpened)
    .EndInit();
  
  // Sync hashed email if you have a login system or collect it.
  //   Will be used to reach the user at the most optimal time of day.
  // OneSignal.syncHashedEmail(userEmail);
}

// Gets called when the player opens the notification.
private static void HandleNotificationOpened(OSNotificationOpenedResult result) {
}

尚、Unityからアプリを書き出した後、XcodeのCapabilityでPushNotificationとBackground ModesのRemote notificationsを有効化する必要があります。従って、Unity Cloud Buildではビルドは通りますが、動作しません。

UnityのAndroid向けビルドにおいて、Google.JarResolver.ResolutionException: Cannot resolve com.google.android.gms:play-services-gcm:9+() というエラーが出る場合は、Android SDK managerでAndroid Support RepositoryとGoogle Play servicesとGoogle Repositoryをアップデートして下さい。 また、JAVA_HOMEにJREへのパスを設定する必要があります。

Apple Watch Series 2を買いました

Apple Watchのステンレススチール+ミラネーゼループを使用していたのですが、Apple Watch Series 2のNikeモデルを買いました。

IMG_1985

今回もステンレスモデルにするか迷ったのですが、結局、2年に一回は買い替えそうなのと、軽さ優先ということで、アルミニウムモデルにしました。心配していたミラネーゼループとの質感の違いは、思ったよりも気になりませんでした。それよりも軽くなったメリットの方が大きかったです。

ただ、当初の購入目的であったSuicaの使用は、iPhone7と異なり、ロック状態では改札を通れないという問題から、実用性は微妙でした。対して、コンビニでのID支払いは、iPhone7で必要な指紋認証が不要な分、便利でした。なので、コンビニでのID支払い専用機というのが、現在の評価です。

Apple Watchそのものの機能としては、時計・アクティビティトラッカー・電話の着信に気づかない問題の解消・当日のスケジュールの確認、あたりを便利に使っています。外出や打ち合わせが増えてくると、時計があった方が便利ですね。

結局、Nikeモデルにした意味はありませんでしたので、通常モデルでも十分かと思います。

UnityのuGUIの座標をスクリプトで指定する

UnityでuGUIを使用する場合、複数の解像度に対応するため、Canvas Scalerを割り当てます。

canvas_scaler

しかし、Canvas Scalerを与えたCanvasにおいて、子要素の座標をスクリプトから制御しようとした場合、transform.positionにCanvas Scalerの拡大率が考慮されないため、うまく制御できません。これは、Unity Editor上では、Canvas Scalerで指定した座標系で設定できるのが、スクリプトでは実座標系で設定することになるのが原因です。

スクリプトからも、Canvas Scalerで指定した座標系で値を設定するには、anchoredPosition3Dを使用します。

具体的に、
 gameObject.transform.position=v;
ではなく、
 gameObject.GetComponent<RectTransform>().anchoredPosition3D=v;
と記述することで、Canvas Scalerを適用しても、適切な位置に子要素を設定することができます。

Unityでコードを書かずにアプリ内課金が可能に

2016年10月31日にリリースされたUnity IAP Store package 1.9から、コードを書かずにアプリ内課金を行えるようになりました。



- [Beta] Codeless IAP tools. Implement IAP by adding IAP Buttons to your project (Window > Unity IAP > Create IAP Button) and configure your catalog of IAP products without writing a line of code (Window > Unity IAP > IAP Catalog). Preliminary documentation is available [here](https://docs.google.com/document/d/1597oxEI1UkZ1164j1lR7s-2YIrJyidbrfNwTfSI1Ksc/edit).

Unity IAP Store package 1.9 is now available

ドキュメントはCodeless IAP Getting Started Guideにあります。

ServicesからUnity IAPを1.9に更新すると、ウィンドウメニューに、Window -> Unity IAP -> Create IAP Buttonが追加されます。選択すると、IAP Button(Script)の追加された、uGUIのボタンが配置されます。

shop

次に、IAP Catalogに、iTunes Connectで設定した課金アイテムのIDを設定します。エディターで実行すると、Fake Storeの購入ダイアログが出ます。

editor

課金が成功すると、On Purchase Completeが、課金がキャンセルされると、On Purchase Failedが呼ばれます。

次に、実機で実行してみたのですが、ボタンを押しても反応がありません。Unity Cloud Buildでは詳細なエラーが分からないので、Xcodeプロジェクトを書き出してみた所、Add the In-App Purchase feature to your App IDというエラーが出ていました。


unity_iap


どうやら、iTunes ConnectのConrtacts、Tax、Bankingを登録しないと、課金APIのテストはできないようです。


You will not be able to test any StoreKit functionality until you have an iOS Paid Applications contract – StoreKit calls in your code will fail until Apple has processed your Contracts, Tax, and Banking information.

Part 1 - In-App Purchase Basics and Configuration


登録が終わったら、実機テストをします。iTunesConnectでの審査前でもテストは可能です。ただし、iOSでの実機テストでは、SandBoxテスターのアカウントでないと、以下のようなエラーで失敗します。



2017-01-06 16:46:29.171848 appid[261:19234] UnityIAP:Requesting product data...
2017-01-06 16:46:30.103910 appid[261:19234] UnityIAP:Received 1 products
2017-01-06 16:46:30.144066 appid[261:19234] UnityIAP:No App Receipt found
2017-01-06 16:46:30.150693 appid[261:19234] UnityIAP:addTransactionObserver
IAPButton.PurchaseProduct() with product ID: gacha1
UnityEngine.Purchasing.IAPButton:PurchaseProduct()
UnityEngine.Events.InvokableCallList:Invoke(Object[])
UnityEngine.EventSystems.ExecuteEvents:Execute(GameObject, BaseEventData, EventFunction`1)
UnityEngine.EventSystems.StandaloneInputModule:ProcessTouchPress(PointerEventData, Boolean, Boolean)
UnityEngine.EventSystems.StandaloneInputModule:ProcessTouchEvents()
UnityEngine.EventSystems.StandaloneInputModule:Process()

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

2017-01-06 16:46:30.310534 appid[261:19234] UnityIAP:PurchaseProduct: gacha1
2017-01-06 16:46:30.331487 appid[261:19234] UnityIAP:UpdatedTransactions
purchase({0}): gacha1
UnityEngine.Events.InvokableCallList:Invoke(Object[])
UnityEngine.EventSystems.ExecuteEvents:Execute(GameObject, BaseEventData, EventFunction`1)
UnityEngine.EventSystems.StandaloneInputModule:ProcessTouchPress(PointerEventData, Boolean, Boolean)
UnityEngine.EventSystems.StandaloneInputModule:ProcessTouchEvents()
UnityEngine.EventSystems.StandaloneInputModule:Process()

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

2017-01-06 16:46:31.806437 appid[261:19234] UnityIAP:UpdatedTransactions
2017-01-06 16:46:31.806652 appid[261:19234] UnityIAP:PurchaseFailed: 0
onPurchaseFailedEvent({0}): gacha1
UnityEngine.Purchasing.PurchasingManager:OnPurchaseFailed(PurchaseFailureDescription)
UnityEngine.Purchasing.AppleStoreImpl:ProcessMessage(String, String, String, String)
UnityEngine.Purchasing.Extension.UnityUtil:Update()

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

IAPButton.OnPurchaseFailed(Product UnityEngine.Purchasing.Product, PurchaseFailureReason Unknown)
UnityEngine.Purchasing.IAPButton:OnPurchaseFailed(Product, PurchaseFailureReason)
UnityEngine.Purchasing.IAPButtonStoreManager:OnPurchaseFailed(Product, PurchaseFailureReason)
UnityEngine.Purchasing.AppleStoreImpl:ProcessMessage(String, String, String, String)
UnityEngine.Purchasing.Extension.UnityUtil:Update()

(Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)


iTunes ConnectでSandBoxテスターを作成する際は、gmailのエイリアスを使うと便利です。iPhoneの設定のiTunesからApple IDをログアウトした後、テストアプリを起動、IAP Buttonをタップするとログインを要求されるので、SandBoxテスターとしてログインします。これで、課金のテストができます。

参考サイト:Unity IAPを使ってて思ったこと審査前にテスト課金をする

コンピュートシェーダとピクセルシェーダの違い

GPUを使用してフィルタリングを実装する場合、ピクセルシェーダもしくはコンピュートシェーダを使用することができます。

ピクセルシェーダは最も基本的なシェーダであり、テクスチャとパラメータを入力して演算した後、1画素を出力することができます。例えば、ピクセルシェーダで5x5タップのフィルタを実装する場合、25画素を取得して、1画素を返すことになります。テクスチャはランダムリードはできますが、ランダムライトはできません。また、出力は1画素に限定されます。

コンピュートシェーダでは、ピクセルシェーダの機能に加えて、テクスチャへのランダムライトと、共有メモリが使用可能です。また、出力は1画素に限定されません。コンピュートシェーダで5x5タップのフィルタを実装する場合、複数画素の処理をまとめて行うことで、テクスチャのフェッチ回数を削減し、ピクセルシェーダよりも高速化が可能です。例えば、16x16画素をまとめて共有メモリに格納した後、スレッドIDに応じて、5x5タップのフィルタを並列で実行し、12x12画素を書き込むことができます。これにより、ピクセルシェーダでは25in:1outだったテクスチャフェッチ比率を、256in:144out=1.7in:1outまで落とすことができます。

compute

コンピュートシェーダの処理の単位はスレッドで、複数のスレッドが集まってスレッドグループを構成します。共有メモリはスレッドグループ内でのみアクセス可能です。シェーダコードには、3次元で何個のスレッドでグループを構成するかを記述します。共有メモリのアクセス管理はメモリバリアで、GroupMemoryBarrierWithGroupSync();を読んだタイミングで全てのスレッドが完了するまで待ちます。テクスチャのアクセス座標は、スレッドに割り当てられたIDから計算します。

コンピュートシェーダは、ピクセルシェーダよりも初期設定が面倒ですが、Unityを使うと簡単に実験することができます。テクスチャへのランダムアクセスもRenderTextureにrandomAccessフラグを付けるだけでよく、簡単です。ただし、Unityでコンパイル済みのシェーダを使用する方法が存在しないため、シェーダプログラムを秘匿したい場合は、Render PluginとしてDLLを呼び出して、中でfxcを読み込むしかなさそうです。

Render Pluginなど、Unityを使用せず、直接、Direct Computeを使用する場合、ランダムライトが発生するテクスチャには、ShaderResourceViewではなく、UnalignedResourceViewが必要です。UnalignedResourceViewはピクセルシェーダでは読めないので、1つのテクスチャに対して両方のビューを作っておくことで、コンピュートシェーダで書き込み、ピクセルシェーダで読み込むことが可能です。UnityのNativeTexturePtrからもUnalignedResourceViewは作成可能です。

少人数でソーシャルゲームを作るツールチェイン

フェイスブックが買収したWhatsAppが社員数50人で4億5000万人に対してサービスを提供できたように、開発環境の進化とクラウド化によって、少人数でも大規模なアプリケーションが開発できるようになってきました。

今回は、エンジニア視点で、少人数で大規模なソーシャルゲームの開発を行うために最適なツールチェインを考えてみます。

Unity5


昔はゲームを作るときはゲームエンジンから作っていたものですが、Unityの登場によって、ゲームのコアだけを記述すればよくなりました。iOSとAndroidのマルチプラットフォーム化が必須な今、開発環境としてのWindowsとMacの対応も考えると、Unityを使わないという選択肢はない気がします。nGUIとIAPの対応で、外部プラグイン不要で完結するようになってきましたし、どうしても不足する機能は自分でプラグインを書けば良いという安心感もあります。また、最新のMAYAのfbxへの対応など、何もしなくてもゲームエンジンがメンテナンスされていくというのは、自社エンジンにはない魅力です。

Maya LT でローポリキャラクタモデリングに挑戦して Unity で動かしてみた

Unity Cloud Build


Unity Cloud Buildを使うと、リポジトリにpushするだけで、自動的にiOSとAndroidのアプリをビルドすることができます。これにより、物理的に離れた場所にいたとしても、チームメンバー全員がいつでも最新版で動作確認することができます。開発はPCだけで行えばよく、実機を有線で接続する必要もないので、単純な開発効率も上がります。iOSアプリをWindowsだけで開発できるというのは革命的です。

Unity Cloud Buildの使い方

Bitbucket


GitのPrivateリポジトリを無料で作ることができます。Unity Cloud Buildとの連携を考えると、リポジトリはクラウドに持った方が便利です。

Unity向け .gitignoreの設定について

Slack


メールだと定型句が必要ですが、チャットだと重要な点だけを書けるのでコミュニケーションの効率が上がります。コミュニケーションの手段としてのメールは今後、衰退していく気がします。ChatWork、HipChatとも比較しましたが、Slackが一番、アプリの出来がよくできていました。

Google Docs


仕様書やドキュメントなどは、複数人同時に編集できるGoogle Docsで管理すると便利です。日付付きのWordファイルやExcelファイルをメールで送る必要はありません。ゲームのリソースもGoogle Driveでやり取りするとスムーズです。

Google AppEngine


少人数で運営することを考えると、サーバ運営をしているリソースはありません。Google AppEngineはDataStoreなど、プログラム側にかなり強い制約がかかりますが、その制約によって、原理的にAppEngineで動けば必ずスケールすることが保証されます。また、マネージドサービスなため、脆弱性の発覚による依存ライブラリのバージョンアップなども不要です。すなわち、サーバの保守が不要になります。

唯一、国内での実績が乏しいのが採用のネックだったのですが、メルカリ アッテがAppEngineを採用したことで、その障壁もなくなりました。さらに、2016年9月には待望の東京リュージョンが開設され、遅延が減ります。証明書なしでSSLが使えるのも魅力です。ゲームサーバなら独自ドメインがいらないので手軽です。

ゲームサーバへのサーバレスアーキテクチャの適用は、今後のトレンドになるのではないでしょうか。

SSL の設定
サーバーレスアーキテクチャという技術分野についての簡単な調査

第27回IGポート株主総会レポート(2016/08/26)

場所は久々の三鷹産業プラザです。参加者は80人程度。株主総会で初めてパワーポイントが使われました。パワーポイントの表紙の写真は丸井のIGストア、まほよ、攻殻VRの三つでした。

IMG_1842

業績概要の説明。映像制作赤字、出版と版権が黒字です。出版はまほよが250万部、劇場版効果でARIA関連グッズが好調。電子書籍比率が30%増加で利益率向上。来期は、映像制作とともに、VRアプリや渋谷丸井のIGストアでのグッズ販売をやっていく。

以下、質疑応答です。

ニコニコでの配信は収益目的なのか、盛り上げ目的なのか。アベマTVはどうか。


ニコニコでの配信の目的は先行配信の場合は宣伝。ニコニコユーザはアニメと親和性が高い。有料課金は収益目的。アベマTVも視野に入れている。先行配信の場合はロイヤリティを高く積んで頂ける会社もあるので総合的に勘案。

映像制作、制作期間の長期化による悪影響は今期限り?株価への影響の大きいVRの方針は?


映像制作は、製作委員会から依頼を受けて制作するが、クオリティが要求される。ここ2年、赤字が続いている。今期も。海外からの依頼は予算が大きい。今後は交渉によって制作費を上げてもらうなど、映像制作で利益を上げられるよう目指す。

VRは制作完了している。攻殻はさまざまな反響を頂いており、タッチポイントを増やす。アプリとしてはPSVRでの配信を予定しているが、カメラワークの酔いの問題を修正中。他のプラットフォームも検討。スマホアプリは12GBになってしまった。どのようにエンコードすればよいか調整中。

クオリティを高く置いているので制作費で収めるのは難しくなっている。グループとして、マッグガーデンが9年目にして、利益を出す仕組みを作れた。利益の循環を生み出すエンジン。多少、利益を超えたとしても、株主の期待に応えられる。

どの部分に権利を持っているかわからないと投資しにくい。開示予定は?


機関投資家からも同様の依頼を頂く。制作委員会のシステムで守秘義務がある。開示してしまうと、他社にわかり、業績に悪影響を与える。業績改善でいろいろな契約上の手を打っているが、他社に手の内がわかってしまう。

海外における事業展開について。


Netflix向けはIGが100%版権を持ってやる。フリクリをカートゥーンネットワークでという話など、版権ビジネスのよいスタートラインに着いている。

ガルムウォーズの収益は?


海外は18カ国。75万ドル。国内は動員1万2000人。2千万円弱。カナダの税制優遇を使って制作、ワールドワイドのプリセールスをやったが、見込んだ収益はあげられなかった。国内はDVDで回収していく。営業マスターは償却済み。

特別に力を入れていることは?


和田、森下が答える。IGポートの原動力となるウィットスタジオとシグナルエムディーの社長。

ウィットスタジオは企画を作ることに集中。面白いものを作るには面白い人が必要。そのためには企画がいる。カバネリをリリース。IGポートのサポートを受けながら人を集める場所を作る。

シグナルエムディーは設立から1年弱。デジタル作画を中心として人を集めて人を育てている。ひるねひめは、デジタル作画中心。意図としては、少子化が進む中で、より省力化、効率化が必要。デジタル作画はひとつのきっかけ。正社員という形でアニメーターを雇用。将来の中核になってもらうべく進めている。ビジネススキームとしては、舞台やスマホアプリなども設立時にかかげている。しかし、まずはデジタル作画に資本を投下している。

VRアプリのキャンペーンは?リターンは?


攻殻VRはカメラがかなり動く。ご年配が酔われる。ソニーが認めるか次第。出口としては他のHMDでの配信は開始している。戦略は、全世界配信を目指すために、制作委員会から離れた仕組みを作る。音楽や脚本の権利を買いきりにしておくことで、世界配信をできるようにする。

出版の社員数が2名減少しているが?


先月、募集をかけた。作家性を備えた若い編集者で社内でも企画を立てていきたい。関西に事務所を作った。作家の発掘。作品数を昨年はしぼったが、秋から増やす。ネットマガジンで連載予定。

その他事業の赤字の原因は?


その他事業の赤字は販管費の増加と、NHK向けのアニメのグッズの売上がよくなく、在庫を処分した。

映像制作はどの会社も赤字なのか?プロセスやツールの問題か?


制作の利益は、大きくは業界の問題。作画崩れや、お客の目、工程の労力が増えている。本数増加でクリエーターの奪い合いも起こっており、制作期間を超過する原因となっている。

プリプロの期間が延びてきている。工程を分析して管理する必要がある。1つの場所に集まって、内製化の方向に向かう。プリプロは出版事業で作ったものをアニメ化する方向になっている。

ウィットスタジオは進撃、カバネリ、まほよと、クオリティに関しては信頼がある。社長の陣頭指揮で数カ所にあったスタジオを集約。来期は絶対に利益を出すという言葉をもらっている。

株主優待について


2007年にSACのクオカードを出したことがある。その際、一般投資家から批判もあった。業績が大事。イラストを描きおろして権利処理するコストを考えると2000万円ぐらいかかる。本業のクリエーターが書き下ろすので本業への影響もあった。

ARIA劇場版、BD BOXのみで単品販売しない。なぜこうなったのか。


制作委員会の調整によるもので、我々は詳細を把握できていない。まほよは原作と制作、100%、IGが出資。貴重な意見を反映させたい。

フリクリ2の国内展開の予定は。フリクリ1の再販はIGができるのか。


契約上の機密保持。海外はカートゥーンネットワークで配信。国内は製作委員会で行う。

「とつくにの少女」と「もののべ古書店」はどうか?


9月に2巻が出る。コミックは2巻で新規ユーザーが入ってくる。アニメ化できる可能性を感じる。


全体としては、ここ8年ぐらいの、IGポートで原作を作ってアニメ化まで一気通貫で行うという夢が、最近のマッグガーデンのコミックの好調と、ウィットスタジオのアニメの好調によって、魔法使いの嫁でついに実現しそうというのが大きいかと思います。ようやく手にした理想のビジネスモデル、あとはいかに原作を生み出し続けられるかが問われそうです。

IMG_1850

今年のお土産はクリアファイルでした。

バランスシート分析サービス BS Analyzerを公開しました

企業のバランスシートを分析してランキングを作成することができるBS Analyzerを公開しました。

bs_analyzer

BS Analyzerは、企業の四半期決算を分析して、任意の計算式でランキングを作成できるサービスです。計算式には時価総額を入力できるため、ネットネット倍率でもランキングすることができます。

これにより、時価総額よりも多くの現金を持っている銘柄を簡単に抽出することができます。

まだ2016年5月以降に公開されたXBRLしか対応していませんが、3ヶ月後には各企業のXBRLが出揃い、実用的になってくるのではないかと考えています。

XBRLからネットネット株を探す

ネットネット株とは、企業の換金性の高い資産から、負債を引いた値が、時価総額よりも大きい銘柄のことです。PBRには、在庫やのれんなど、換金性の低い資産が含まれるため、これらの影響を除外して、安全側に寄せて考えることで、真の割安株を抽出することができます。

ネットネット株かどうかを判定するには、企業が公開しているバランスシートを使って、手動で計算する必要があります。しかし、東証は適時開示でXBRLを公開しているので、これを解析することで、バランスシートの分析を自動化することができます。

XBRLはzipファイルとなっており、展開すると、複数のhtmlファイルが出てきます。この中にある、bsというIDを持つファイルがバランスシートです。

XBRLにはタクソノミという定義があり、勘定項目ごとに、タグに設定するnameが規定されています。例えば、現金及び預金はjppfs_cor:CashAndDepositsです。これを利用して、htmlから正規表現で勘定項目の値を取得することができます。

今回は、以下のような正規表現を用いました。

it = re.finditer("<ix:.*contextRef=\"Current[a-zA-Z_]*\".*name=\""+item+"\".*?>([ 0-9,]+)<", line, re.DOTALL)

また、千円単位か、百万円単位かというのは、scaleという要素に設定されています。

it2 = re.finditer("scale=\"([0-9]+)\"", m.group(0), re.DOTALL)

これを使って、東証のXBRLを解析した例は以下です。

オリバー - 平成28年10月期 第2四半期決算短信〔日本基準〕(連結)

TDnetSearchでは、XBRLを含む決算短信に、ANALYZEリンクを表示します。これをクリックするだけで、バランスシートを読まずに、ネットネット株かどうかを判別することができます。pbrクエリである程度絞り込んだ後に、ネットネット株かどうかの分析をして確度を上げるのがオススメです。

今後は、四半期決算を蓄積したタイミングで、割安度ランキングを作成してみたいと思います。

---
2016/6/5追記
ネットネット倍率のランキングを計算できるBS Analyzerを公開しました。

名証の割安銘柄

TOPIXなどのインデックスファンドが投資先を東証に限定しているため、名証の銘柄は割安に放置される傾向があります。そのため、名証から東証に市場変更すると水準訂正が起きる可能性が高く、好業績の名証銘柄を買っておくというのは、長期保有できるのであれば、よい戦略なのではないかと思います。

ということで、名証の割安銘柄です。どの銘柄も、財務良好で、割安です。

中部日本放送

TBS系の放送事業者。不動産賃貸も。
株価631円、PER8.16、PBR0.34、配当利回り3.49%、自己資本比率75.6%。

愛知電機

中部電力系の変圧器メーカ。モータ製造も。
株価330円、PER6.3、PBR0.36、配当利回り3.64%、自己資本比率50.9%。

中部鋼鈑

国内最大級の電炉保有の厚板専業メーカー。産業機械向けが主力。
株価504円、PER6.77、PBR0.28、配当利回り2.78%、自己資本比率88.1%。

ちなみに、リストにはエスラインもいたのですが、原油安に伴う好業績のため株価が高騰してしまいました。エスラインは、東証への市場変更の布石か、株主数を増やすため、株主優待の新設を発表しています。

東北新社の分析

東北新社はCM制作、衛星放送のスターチャンネルの運営、映画の制作・配給を行っている企業です。宇宙戦艦ヤマトとGAROの版権を持っていることで有名です。

株価は620円、PER10.62、PBR0.42、配当利回り2.74%です。

時価総額は289億円。現金341億円、売掛165億円、土地129億円、本社建物15億円、投資有価証券100億円に対して、負債321億円です。流動性の高い資産で429億円あるため、時価総額に対して割安です。さらに、無形資産の版権が上乗せされます。

土地については、港区の赤坂の本社が1094平米で34億円(建物は15億円)、港区のスタジオが1579平米で10億円です。本社については路線価とほぼ同等です。スタジオの方は、含み益があるかもしれません。ロサンゼルスにも4303平米で、3億円計上されています。その他の合計11787平米、69億円の詳細は不明で、ここが若干の不安要素になります。

BSにはのれんが22億円と、前期から急増していますが、スターチャンネルの持ち分を伊藤忠から60%まで買い増し、連結子会社としたためです。

ビジネスの中心はCMということで、売上が景気に左右されますが、売上が下がると外注費も下がるため、利益はわりと安定しています。

PBRがこれだけ下がると、MBOも期待できるので、ポートフォリオの5%程度は保有してもよいのではないかと思います。

TDnetSearchで東北新社の開示情報を検索
Search
Profile
Twitter
TopHatenar
HotEntry
Counter

アクセス解析付きカウンター。あなたのBLOGにもどうですか?登録はこちらから。

TOP/ BLOG/ LECTURE/ ONLINE/ RUINA/ ADDON/ THREAD/ METHUSELAYZE/ IPHONE/ MET_IPHONE/ ENGLISH/ RANKING