2
0

mediapipe.js 10 KB

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