mediapipe.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. import * as protobuf from './protobuf.js';
  2. const mediapipe = {};
  3. mediapipe.ModelFactory = class {
  4. match(context) {
  5. const tags = context.tags('pbtxt');
  6. if (tags.has('node') && ['input_stream', 'output_stream', 'input_side_packet', 'output_side_packet'].some((key) => tags.has(key) || tags.has(`node.${key}`))) {
  7. context.type = 'mediapipe.pbtxt';
  8. }
  9. }
  10. async open(context) {
  11. // mediapipe.proto = await context.require('./mediapipe-proto');
  12. mediapipe.proto = {};
  13. let config = null;
  14. try {
  15. const reader = context.read('protobuf.text');
  16. // const config = mediapipe.proto.mediapipe.CalculatorGraphConfig.decodeText(reader);
  17. config = new mediapipe.Object(reader);
  18. } catch (error) {
  19. const message = error && error.message ? error.message : error.toString();
  20. throw new mediapipe.Error(`File text format is not mediapipe.CalculatorGraphConfig (${message.replace(/\.$/, '')}).`);
  21. }
  22. return new mediapipe.Model(config);
  23. }
  24. };
  25. mediapipe.Model = class {
  26. constructor(config) {
  27. this.format = 'MediaPipe';
  28. this.graphs = [ new mediapipe.Graph(config) ];
  29. }
  30. };
  31. mediapipe.Graph = class {
  32. constructor(config) {
  33. config = config || {};
  34. this.inputs = [];
  35. this.outputs = [];
  36. this.nodes = [];
  37. const types = new Map();
  38. const type = (list) => {
  39. list = list ? Array.isArray(list) ? list : [ list ] : [];
  40. return list.map((item) => {
  41. const parts = item.split(':');
  42. const name = parts.pop();
  43. const type = parts.join(':');
  44. if (!types.has(name)) {
  45. const value = new Set();
  46. if (type) {
  47. value.add(type);
  48. }
  49. types.set(name, value);
  50. } else if (type && !types.get(name).has(type)) {
  51. types.get(name).add(type);
  52. }
  53. return name;
  54. });
  55. };
  56. config.input_stream = type(config.input_stream);
  57. config.output_stream = type(config.output_stream);
  58. config.input_side_packet = type(config.input_side_packet);
  59. config.output_side_packet = type(config.output_side_packet);
  60. config.node = config.node ? Array.isArray(config.node) ? config.node : [ config.node ] : [];
  61. for (const node of config.node) {
  62. node.input_stream = type(node.input_stream);
  63. node.output_stream = type(node.output_stream);
  64. node.input_side_packet = type(node.input_side_packet);
  65. node.output_side_packet = type(node.output_side_packet);
  66. }
  67. const values = new Map();
  68. for (const [name, value] of types) {
  69. const type = Array.from(value).join(',');
  70. values.set(name, new mediapipe.Value(name, type || null));
  71. }
  72. const value = (name) => {
  73. return values.get(name);
  74. };
  75. for (const name of config.input_stream) {
  76. const argument = new mediapipe.Argument(name, [ value(name) ]);
  77. this.inputs.push(argument);
  78. }
  79. for (const name of config.output_stream) {
  80. const argument = new mediapipe.Argument(name, [ value(name) ]);
  81. this.outputs.push(argument);
  82. }
  83. for (const name of config.input_side_packet) {
  84. const argument = new mediapipe.Argument(name, [ value(name, type) ]);
  85. this.inputs.push(argument);
  86. }
  87. for (const output of config.output_side_packet) {
  88. const parts = output.split(':');
  89. const type = (parts.length > 1) ? parts.shift() : '';
  90. const name = parts.shift();
  91. const argument = new mediapipe.Argument(name, [ value(name, type) ]);
  92. this.outputs.push(argument);
  93. }
  94. for (const node of config.node) {
  95. this.nodes.push(new mediapipe.Node(node, value));
  96. }
  97. }
  98. };
  99. mediapipe.Node = class {
  100. constructor(node, value) {
  101. const type = node.calculator || '?';
  102. this.name = '';
  103. this.type = { name: type.replace(/Calculator$/, '') };
  104. this.inputs = [];
  105. this.outputs = [];
  106. this.attributes = [];
  107. if (node.input_stream) {
  108. const values = node.input_stream.map((name) => value(name));
  109. const argument = new mediapipe.Argument('input_stream', values);
  110. this.inputs.push(argument);
  111. }
  112. if (node.output_stream) {
  113. const values = node.output_stream.map((name) => value(name));
  114. this.outputs.push(new mediapipe.Argument('output_stream', values));
  115. }
  116. if (node.input_side_packet) {
  117. const values = node.input_side_packet.map((name) => value(name));
  118. this.inputs.push(new mediapipe.Argument('output_stream', values));
  119. }
  120. if (node.output_side_packet) {
  121. const values = node.output_side_packet.map((name) => value(name));
  122. this.outputs.push(new mediapipe.Argument('output_side_packet', values));
  123. }
  124. const options = new Map();
  125. if (node.options) {
  126. for (const key of Object.keys(node.options)) {
  127. options.set(key, node.options[key]);
  128. }
  129. }
  130. const node_options = node.node_options ? Array.isArray(node.node_options) ? node.node_options : [ node.node_options ] : [];
  131. if (mediapipe.proto.google && node_options.every((options) => options instanceof mediapipe.proto.google.protobuf.Any)) {
  132. for (const entry of node_options) {
  133. const value = new RegExp(/^\{(.*)\}\s*$/, 's').exec(entry.value);
  134. const buffer = new TextEncoder('utf-8').encode(value[1]);
  135. const reader = protobuf.TextReader.open(buffer);
  136. if (entry.type_url.startsWith('type.googleapis.com/mediapipe.')) {
  137. const type = entry.type_url.split('.').pop();
  138. if (mediapipe.proto && mediapipe.proto.mediapipe && mediapipe.proto.mediapipe[type]) {
  139. const message = mediapipe.proto.mediapipe[type].decodeText(reader);
  140. for (const key of Object.keys(message)) {
  141. options.set(key, message[key]);
  142. }
  143. continue;
  144. }
  145. }
  146. const message = new mediapipe.Object(reader);
  147. for (const [name, value] of Object.entries(message)) {
  148. options.set(name, value);
  149. }
  150. }
  151. } else {
  152. for (const option of node_options) {
  153. for (const [name, value] of Object.entries(option)) {
  154. if (name !== '__type__') {
  155. options.set(name, value);
  156. }
  157. }
  158. }
  159. }
  160. for (const [name, value] of options) {
  161. const attribute = new mediapipe.Argument(name, value);
  162. this.attributes.push(attribute);
  163. }
  164. }
  165. };
  166. mediapipe.Argument = class {
  167. constructor(name, value) {
  168. this.name = name;
  169. this.value = value;
  170. }
  171. };
  172. mediapipe.Value = class {
  173. constructor(name, type) {
  174. if (typeof name !== 'string') {
  175. throw new mediapipe.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
  176. }
  177. this.name = name;
  178. this.type = type || null;
  179. }
  180. };
  181. mediapipe.Object = class {
  182. constructor(reader, block) {
  183. if (!block) {
  184. reader.start();
  185. }
  186. const type = reader.token();
  187. if (type.startsWith('[') && type.endsWith(']')) {
  188. this.__type__ = type.substring(1, type.length - 1);
  189. reader.next();
  190. reader.match(':');
  191. reader.start();
  192. }
  193. const arrayTags = new Set();
  194. while (!reader.end()) {
  195. const tag = reader.tag();
  196. const next = reader.token();
  197. let obj = null;
  198. if (next === '{') {
  199. reader.start();
  200. obj = new mediapipe.Object(reader, true);
  201. if (obj.__type__) {
  202. while (!reader.end()) {
  203. if (!Array.isArray(obj)) {
  204. obj = [ obj ];
  205. }
  206. const token = reader.token();
  207. if (token.startsWith('[') && token.endsWith(']')) {
  208. obj.push(new mediapipe.Object(reader, true));
  209. continue;
  210. }
  211. break;
  212. }
  213. }
  214. } else if (next.startsWith('"') && next.endsWith('"')) {
  215. obj = next.substring(1, next.length - 1);
  216. reader.next();
  217. } else if (next === 'true' || next === 'false') {
  218. obj = next;
  219. reader.next();
  220. } else if (reader.first()) {
  221. obj = [];
  222. while (!reader.last()) {
  223. const data = reader.token();
  224. reader.next();
  225. if (!isNaN(data)) {
  226. obj.push(parseFloat(data));
  227. }
  228. }
  229. } else if (!isNaN(next)) {
  230. obj = parseFloat(next);
  231. reader.next();
  232. } else {
  233. obj = next;
  234. reader.next();
  235. }
  236. if (this[tag] && (!Array.isArray(this[tag]) || arrayTags.has(tag))) {
  237. this[tag] = [ this[tag] ];
  238. arrayTags.delete(tag);
  239. }
  240. if (this[tag]) {
  241. this[tag].push(obj);
  242. } else {
  243. if (Array.isArray(obj)) {
  244. arrayTags.add(tag);
  245. }
  246. this[tag] = obj;
  247. }
  248. reader.match(',');
  249. }
  250. }
  251. };
  252. mediapipe.Error = class extends Error {
  253. constructor(message) {
  254. super(message);
  255. this.name = 'Error loading MediaPipe model.';
  256. }
  257. };
  258. export const ModelFactory = mediapipe.ModelFactory;