Turn audio into a shareable video. forked from nypublicradio/audiogram

preview.js 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. var d3 = require("d3"),
  2. audio = require("./audio.js"),
  3. video = require("./video.js"),
  4. minimap = require("./minimap.js"),
  5. sampleWave = require("./sample-wave.js"),
  6. getRenderer = require("../renderer/"),
  7. getWaveform = require("./waveform.js"),
  8. $ = require("jquery");
  9. var context = d3.select("canvas").node().getContext("2d");
  10. var theme,
  11. caption,
  12. file,
  13. selection,
  14. newTheme,
  15. newCaption,
  16. subtitle;
  17. // captionColorPicker,
  18. // subtitleColorPicker;
  19. function _file(_) {
  20. return arguments.length ? (file = _) : file;
  21. }
  22. function _theme(_) {
  23. return arguments.length ? (theme = _, redraw()) : theme;
  24. }
  25. function _caption(_) {
  26. return arguments.length ? (caption = _, redraw()) : caption;
  27. }
  28. function _selection(_) {
  29. return arguments.length ? (selection = _) : selection;
  30. }
  31. function _newTheme(_) {
  32. return arguments.length ? (newTheme = _) : newTheme;
  33. }
  34. function _newCaption(_) {
  35. return arguments.length ? (newCaption = _) : newCaption;
  36. }
  37. function _subtitle(_) {
  38. return arguments.length ? (subtitle = _) : subtitle;
  39. }
  40. minimap.onBrush(function(extent){
  41. var duration = audio.duration();
  42. selection = {
  43. duration: duration * (extent[1] - extent[0]),
  44. start: extent[0] ? extent[0] * duration : null,
  45. end: extent[1] < 1 ? extent[1] * duration : null
  46. };
  47. d3.select("#duration strong").text(Math.round(10 * selection.duration) / 10)
  48. .classed("red", theme && theme.maxDuration && theme.maxDuration < selection.duration);
  49. });
  50. // Resize video and preview canvas to maintain aspect ratio
  51. function resize(width, height) {
  52. var widthFactor, heightFactor;
  53. if (height > width) {
  54. widthFactor = 360 / width;
  55. heightFactor = 640 / height;
  56. } else {
  57. widthFactor = 640 / width;
  58. heightFactor = 360 / height;
  59. }
  60. var factor = Math.min(widthFactor, heightFactor);
  61. d3.select("canvas")
  62. .attr("width", factor * width)
  63. .attr("height", factor * height);
  64. d3.select("#canvas")
  65. .style("width", (factor * width) + "px");
  66. d3.select("video")
  67. .attr("height", widthFactor * height);
  68. d3.select("#video")
  69. .attr("height", (widthFactor * height) + "px");
  70. context.setTransform(factor, 0, 0, factor, 0, 0);
  71. }
  72. function redraw() {
  73. resize(theme.width, theme.height);
  74. video.kill();
  75. var renderer = getRenderer(theme);
  76. renderer.backgroundImage(theme.backgroundImageFile || null);
  77. renderer.drawFrame(context, {
  78. caption: caption,
  79. waveform: sampleWave,
  80. frame: 0
  81. });
  82. if (location.pathname === "/theme") {
  83. const noPattern = (theme.noPattern === undefined) ? false : theme.noPattern;
  84. d3.select("#chkNoPattern").property("checked", noPattern);
  85. const font = theme.subtitleFont;
  86. if (font !== undefined) {
  87. var fontName = font.substring(font.indexOf("'"));
  88. fontName = fontName.substring(1, fontName.length-1);
  89. d3.select("#input-font").property("value", fontName);
  90. }
  91. const captionColor = (theme.captionColor === undefined) ? "#000" : theme.captionColor;
  92. const subtitleColor = (theme.subtitleColor === undefined) ? "#000" : theme.subtitleColor;
  93. var container = document.querySelector(".captionColorPicker");
  94. if (container.hasChildNodes()) {
  95. while (container.firstChild) {
  96. container.removeChild(container.firstChild);
  97. }
  98. }
  99. var newElement = document.createElement('div');
  100. container.appendChild(newElement);
  101. var captionColorPicker = new Pickr({
  102. el: newElement,
  103. default: captionColor ? captionColor : '#fff',
  104. theme: 'nano',
  105. lockOpacity: true,
  106. swatches: [
  107. 'rgba(244, 67, 54, 1)',
  108. 'rgba(233, 30, 99, 0.95)',
  109. 'rgba(156, 39, 176, 0.9)',
  110. 'rgba(103, 58, 183, 0.85)',
  111. 'rgba(63, 81, 181, 0.8)',
  112. 'rgba(33, 150, 243, 0.75)',
  113. 'rgba(3, 169, 244, 0.7)',
  114. 'rgba(0, 188, 212, 0.7)',
  115. 'rgba(0, 150, 136, 0.75)',
  116. 'rgba(76, 175, 80, 0.8)',
  117. 'rgba(139, 195, 74, 0.85)',
  118. 'rgba(205, 220, 57, 0.9)',
  119. 'rgba(255, 235, 59, 0.95)',
  120. 'rgba(255, 193, 7, 1)'
  121. ],
  122. components: {
  123. preview: true,
  124. opacity: true,
  125. hue: true,
  126. interaction: {
  127. hex: true,
  128. rgba: true,
  129. hsva: true,
  130. input: true,
  131. clear: true,
  132. save: true
  133. }
  134. }
  135. });
  136. captionColorPicker.on('save', (color, instance) => {
  137. if (theme.captionColor) {
  138. theme.captionColor = color.toHEXA().toString();
  139. } else {
  140. theme['captionColor'] = color.toHEXA().toString();
  141. }
  142. saveTheme();
  143. });
  144. container = document.querySelector(".subtitleColorPicker");
  145. if (container.hasChildNodes()) {
  146. while (container.firstChild) {
  147. container.removeChild(container.firstChild);
  148. }
  149. }
  150. newElement = document.createElement('div');
  151. container.appendChild(newElement);
  152. var subtitleColorPicker = new Pickr({
  153. el: newElement,
  154. default: subtitleColor ? subtitleColor : '#fff',
  155. theme: 'nano',
  156. lockOpacity: true,
  157. swatches: [
  158. 'rgba(244, 67, 54, 1)',
  159. 'rgba(233, 30, 99, 0.95)',
  160. 'rgba(156, 39, 176, 0.9)',
  161. 'rgba(103, 58, 183, 0.85)',
  162. 'rgba(63, 81, 181, 0.8)',
  163. 'rgba(33, 150, 243, 0.75)',
  164. 'rgba(3, 169, 244, 0.7)',
  165. 'rgba(0, 188, 212, 0.7)',
  166. 'rgba(0, 150, 136, 0.75)',
  167. 'rgba(76, 175, 80, 0.8)',
  168. 'rgba(139, 195, 74, 0.85)',
  169. 'rgba(205, 220, 57, 0.9)',
  170. 'rgba(255, 235, 59, 0.95)',
  171. 'rgba(255, 193, 7, 1)'
  172. ],
  173. components: {
  174. preview: true,
  175. opacity: true,
  176. hue: true,
  177. interaction: {
  178. hex: true,
  179. rgba: true,
  180. hsva: true,
  181. input: true,
  182. clear: true,
  183. save: true
  184. }
  185. }
  186. });
  187. subtitleColorPicker.on('save', (color, instance) => {
  188. if (theme.captionColor) {
  189. theme.subtitleColor = color.toHEXA().toString();
  190. } else {
  191. theme['subtitleColor'] = color.toHEXA().toString();
  192. }
  193. saveTheme();
  194. });
  195. }
  196. }
  197. function saveTheme() {
  198. $.ajax({
  199. url: "/theme/save",
  200. type: "POST",
  201. data: JSON.stringify({theme: theme}),
  202. dataType: "json",
  203. contentType: "application/json",
  204. cache: false,
  205. error: function (error) {
  206. console.log('error', error);
  207. }
  208. });
  209. }
  210. function loadAudio(f, cb) {
  211. d3.queue()
  212. .defer(getWaveform, f)
  213. .defer(audio.src, f)
  214. .await(function(err, data){
  215. if (err) {
  216. return cb(err);
  217. }
  218. file = f;
  219. minimap.redraw(data.peaks);
  220. cb(err);
  221. });
  222. }
  223. function loadNewTheme(f, cb) {
  224. d3.queue()
  225. .await(function(err, data){
  226. if (err) {
  227. return cb(err);
  228. }
  229. newTheme = f;
  230. cb(err);
  231. });
  232. }
  233. function loadSubtitle(f, cb) {
  234. d3.queue()
  235. .await(function(err, data){
  236. if (err) {
  237. return cb(err);
  238. }
  239. subtitle = f;
  240. cb(err);
  241. });
  242. }
  243. module.exports = {
  244. caption: _caption,
  245. theme: _theme,
  246. file: _file,
  247. selection: _selection,
  248. loadAudio: loadAudio,
  249. newTheme: _newTheme,
  250. newCaption: _newCaption,
  251. loadNewTheme: loadNewTheme,
  252. subtitle: _subtitle,
  253. loadSubtitle: loadSubtitle
  254. };