まだちょっとだけ。
2017-12-15 追記: 以下参照:
ダウンロードという行為そのものが youtube の利用規約に反することを知りませんでした。(なお、ダウンロードするだけで著作権法違反となる、という話ではない。)以下一応読めるようにはしておくけれど、出来れば読まないで。
少なくとも紹介したこの検索の動画を実際に見てみた人は気付いたかもしれないが、たとえばこれ:
動画の最初と最後に余分なものが入っている。また、(3)では音声は background 側からしか取ってない。green screen 側動画から音声を取りたいこともある。
というわけで:
1 #
2 import sys
3 import argparse
4 import logging
5 import json
6
7 import numpy as np
8 import av
9 from PIL import Image
10
11 if __name__ == '__main__':
12 logging.basicConfig(stream=sys.stderr, level=logging.INFO)
13 parser = argparse.ArgumentParser()
14 parser.add_argument("background")
15 parser.add_argument("greenback")
16 parser.add_argument("outfile")
17 parser.add_argument("--pick", type=str, default="[0, 0]")
18 parser.add_argument("--atol", type=int, default=30)
19 parser.add_argument("--bg-start", help="in secs", type=float, default=0)
20 parser.add_argument("--gb-start", help="in secs", type=float, default=0)
21 parser.add_argument("--gb-end", help="in secs", type=float, default=0)
22 parser.add_argument("--audio-from-gb", action="store_true")
23 parser.add_argument("--gb-base-alpha", type=int, default=0)
24 args = parser.parse_args()
25 pick = tuple(json.loads(args.pick))
26 #
27
28 icontbg = av.open(args.background)
29 icontgb = av.open(args.greenback)
30 ivstrbg = next(s for s in icontbg.streams if s.type == b'video')
31 ivstrgb = next(s for s in icontgb.streams if s.type == b'video')
32 iastrbg = next(s for s in icontbg.streams if s.type == b'audio')
33 iastrgb = next(s for s in icontbg.streams if s.type == b'audio')
34 ocont = av.open(args.outfile, "w")
35 ovstr = ocont.add_stream(codec_name="h264", rate=ivstrbg.rate)
36 oastr = ocont.add_stream(codec_name="aac", rate=iastrbg.rate)
37 ovstr.width = ivstrbg.width
38 ovstr.height = ivstrbg.height
39 #
40 if args.bg_start:
41 bg_start_pts_v = int(args.bg_start / float(ivstrbg.time_base) + ivstrbg.start_time)
42 bg_start_pts_a = int(args.bg_start / float(iastrbg.time_base) + iastrbg.start_time)
43 ivstrbg.seek(bg_start_pts_v)
44 iastrbg.seek(bg_start_pts_a)
45 if args.gb_start:
46 gb_start_pts_v = int(args.gb_start / float(ivstrgb.time_base) + ivstrgb.start_time)
47 gb_start_pts_a = int(args.gb_start / float(iastrgb.time_base) + iastrgb.start_time)
48 ivstrgb.seek(gb_start_pts_v)
49 iastrgb.seek(gb_start_pts_a)
50 if args.gb_end:
51 gb_end_pts_v = int(args.gb_end / float(ivstrgb.time_base) + ivstrgb.start_time)
52 else:
53 gb_end_pts_v = 0
54 #
55
56 if not args.audio_from_gb:
57 avbothpackets = icontbg.demux()
58 vidonlypackets = icontgb.demux(ivstrgb)
59 else:
60 avbothpackets = icontgb.demux()
61 vidonlypackets = icontbg.demux(ivstrbg)
62 done = False
63 while not done:
64 try:
65 avbothpacket = next(avbothpackets)
66 if avbothpacket.stream.type == 'video':
67 if not args.audio_from_gb:
68 iframebgs = avbothpacket.decode()
69 iframegbs = next(vidonlypackets).decode()
70 else:
71 iframebgs = next(vidonlypackets).decode()
72 iframegbs = avbothpacket.decode()
73 for iframes in zip(iframebgs, iframegbs):
74 logging.info(iframes)
75 imgbg = iframes[0].to_image()
76 imggb = iframes[1].to_image().resize(imgbg.size)
77 keyclr = imggb.getpixel(pick)
78 r = np.array(imggb.getdata(0))
79 g = np.array(imggb.getdata(1))
80 b = np.array(imggb.getdata(2))
81 a = np.ones(r.shape) * args.gb_base_alpha
82 #
83 a[np.logical_and(
84 np.isclose(r, keyclr[0], atol=args.atol),
85 np.isclose(g, keyclr[1], atol=args.atol),
86 np.isclose(b, keyclr[2], atol=args.atol))] = 255
87 alpha = Image.new("L", imggb.size)
88 alpha.putdata(a.flatten())
89 mask = imggb.copy()
90 mask.putalpha(alpha)
91
92 dimg = Image.composite(imgbg, imggb, mask)
93
94 ofr = av.VideoFrame.from_image(dimg)
95 for p in ovstr.encode(ofr):
96 ocont.mux(p)
97 if gb_end_pts_v and iframes[1].pts >= gb_end_pts_v:
98 done = True
99 else:
100 for iframe in avbothpacket.decode():
101 logging.info(iframe)
102 iframe.pts = None
103 for p in oastr.encode(iframe):
104 ocont.mux(p)
105 except StopIteration:
106 break
107
108 ocont.close()
シークで飛ばせるようにし、指定時間で打ち切れるようにし、音声をどちらから取るかを選べるようにしている。なおついでなので --gb-base-alpha
も追加しているがこれは green screen 側の「抜かない」部分のアルファ値。ベースとして若干透かしたい場合もある。で、以下:
これとさっきのビール缶と合成したのがこれ:
ま…こういう単純なものはなんてことないんだけどね、わかってる人には自明なのかもしれんけれど、ずっと誤魔化してきたことがある。「期待のものと違う」ものについてはやっぱ言っといた方がいいよね。こういう処理の基礎についてあんまよくわかってないんで、相手がこういうのだとちょっとね:
実際にやってみればわかる。これは本当にやりたいことと違うだろう:
これは「カラーキー(?)」を完全透過にするだけの単純な処理のみではどうしようもなくて、多分残った「透けた緑」そのものを色加工しないといけないんだと思うんだけれど、素養がないのでどうすりゃいいのか良くわかんない。
この対応をしようと頑張るかどうかはわかんない。少なくともワタシ的に「本題」じゃないからね、このお遊び。所詮アタシの「エクササイズ」に過ぎない。悔しい気持ちはないではないので、気が向いて、対応出来たら書くかも。