Advertisement
Guest User

Untitled

a guest
Oct 16th, 2018
139
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.16 KB | None | 0 0
  1. const PlexAPI = require('plex-api');
  2. const path = require('path');
  3. const fs = require('fs');
  4. const https = require('https');
  5. const {spawn} = require('child_process');
  6. const cookie = require('cookie');
  7.  
  8. require('dotenv').config()
  9. const argv = require('minimist')(process.argv.slice(2));
  10.  
  11. const historyPath = path.join(__dirname, 'history.json');
  12. const history = require(historyPath);
  13.  
  14. const plex = new PlexAPI({
  15. hostname: process.env.PLEX_HOST,
  16. username: process.env.PLEX_USER,
  17. password: process.env.PLEX_PASSWORD,
  18. managedUser: {
  19. name: process.env.PLEX_MANAGED_USER,
  20. },
  21. options: {
  22. identifier: 'plex-stream',
  23. deviceName: 'Stream',
  24. product: 'Stream',
  25. },
  26. });
  27.  
  28. plex.query('/library/sections/1/all')
  29. .then(result => result.MediaContainer.Metadata
  30. .map(metadata => ({
  31. ...metadata,
  32. HDMedia: metadata.Media
  33. .filter(media => media.bitrate > 3500 && media.height >= 720),
  34. }))
  35. .filter(({type, HDMedia}) => type === 'movie' && HDMedia.length > 0))
  36. .then(movies => {
  37. const previous = history.previous && !argv['ignore-previous']
  38. ? movies.find(movie => movie.key === history.previous)
  39. : movies[Math.floor(Math.random() * movies.length)];
  40.  
  41. playNext(previous, movies);
  42. })
  43. .catch(err => console.log({err}));
  44.  
  45. function playNext(prev, movies) {
  46. const next = selectNext(prev, movies);
  47.  
  48. history.previous = next.key;
  49. history[next.key] = next.title;
  50. fs.writeFileSync(historyPath, JSON.stringify(history, null, 2));
  51.  
  52. plex.query(next.key).then(result => {
  53. const media = result.MediaContainer.Metadata[0].Media.sort((a, b) => a.bitrate - b.bitrate).pop();
  54. const part = media.Part[0];
  55. const video = part.Stream.filter(stream => stream.streamType === 1).shift();
  56. const audio = part.Stream.filter(stream => stream.streamType === 2 && stream.languageCode === 'eng').shift();
  57.  
  58. if (!video || !audio) {
  59. playNext(next, movies);
  60. return;
  61. }
  62.  
  63. const titleDrawText = formatDrawText(`${next.title} (${next.year})`, 10, 10);
  64.  
  65. const now = new Date();
  66. const timestamp = String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0');
  67. const timeDrawText = formatDrawText(`${timestamp} ${process.env.TIME_ZONE}`, 10, 36)
  68.  
  69. const keyInterval = Math.round(video.frameRate) * 2;
  70.  
  71. const options = [
  72. '-re',
  73. '-i', part.file,
  74. '-vf', `${titleDrawText}, ${timeDrawText}`,
  75. '-af', 'aresample=async=1000',
  76. '-c:v', 'libx264',
  77. '-pix_fmt', 'yuv420p',
  78. '-preset', 'veryfast',
  79. '-map', `0:${video.index}`,
  80. '-map', `0:${audio.index}`,
  81. '-b:v', '3500k',
  82. '-maxrate', '3500k',
  83. '-x264-params', `keyint=${keyInterval}`,
  84. '-c:a', 'aac',
  85. '-strict', '-2',
  86. '-ar', '44100',
  87. '-b:a', '160k',
  88. '-ac', '2',
  89. '-bufsize', '7000k',
  90. '-f', 'flv', process.env.ANGELTHUMP_INGEST,
  91. ];
  92. const ffmpeg = spawn('ffmpeg', options);
  93. ffmpeg.stdout.pipe(process.stdout);
  94. ffmpeg.stderr.pipe(process.stderr);
  95. ffmpeg.on('close', () => playNext(next, movies));
  96. ffmpeg.on('error', err => console.log(err));
  97.  
  98. angelthumpLogin().then(accessToken => {
  99. const req = https.request({
  100. hostname: 'angelthump.com',
  101. path: '/api/title',
  102. method: 'POST',
  103. headers: {
  104. 'Content-Type': 'application/json',
  105. 'Cookie': cookie.serialize('angelthump-jwt', accessToken),
  106. },
  107. }, res => res.pipe(process.stdout));
  108. req.on('error', e => console.error(e));
  109. req.write(JSON.stringify({title: `${next.title} (${next.year})`}));
  110. req.end();
  111. });
  112. });
  113. }
  114.  
  115. function formatDrawText(text, x, y) {
  116. const sanitizedTitle = text.replace(/(\:)/g, '\\$1').replace(/\'/g, '');
  117. return `drawtext=text='${sanitizedTitle}': fontcolor=gray@0.4: fontsize=18: x=${x}: y=${y}`
  118. }
  119.  
  120. const toLookupTable = values => (values || []).reduce((t, {tag}) => ({...t, [tag]: true}), {});
  121. const countMatches = (values, lookupTable) => (values || []).reduce((n, {tag}) => lookupTable[tag] ? n + 1 : n, 0);
  122.  
  123. function generateWeights(prev, movies) {
  124. const director = toLookupTable(prev.Director);
  125. const genre = toLookupTable(prev.Genre);
  126. const writer = toLookupTable(prev.Writer);
  127. const country = toLookupTable(prev.Country);
  128. const role = toLookupTable(prev.Role);
  129. const {year, rating} = prev;
  130.  
  131. return movies
  132. .map((movie, i) => {
  133. let weight = 0;
  134. weight += countMatches(movie.Director, director) * (+process.env.DIRECTOR_WEIGHT || 1);
  135. weight += countMatches(movie.Genre, genre) * (+process.env.GENRE_WEIGHT || 1);
  136. weight += countMatches(movie.Writer, writer) * (+process.env.WRITER_WEIGHT || 1);
  137. weight += countMatches(movie.Country, country) * (+process.env.COUNTRY_WEIGHT || 1);
  138. weight += countMatches(movie.Role, role) * (+process.env.ROLE_WEIGHT || 1);
  139. weight += (movie.year === year ? 1 : 0) * (+process.env.YEAR_WEIGHT || 1);
  140. weight *= rating;
  141. return [weight, i];
  142. })
  143. .sort(([a], [b]) => b - a);
  144. }
  145.  
  146. function selectNext(prev, movies) {
  147. const weights = generateWeights(prev, movies);
  148. const weightSum = weights.reduce((sum, [weight]) => sum + weight, 0);
  149.  
  150. const rand = (1 - Math.pow(Math.random(), 10)) * weightSum;
  151.  
  152. let runningSum = weightSum;
  153. const next = weights.find(([weight, i]) => {
  154. runningSum -= weight;
  155. return runningSum <= rand
  156. && movies[i].key !== prev.key
  157. && history[movies[i].key] === undefined;
  158. });
  159.  
  160. return movies[next[1]];
  161. }
  162.  
  163. function angelthumpLogin() {
  164. return new Promise(resolve => {
  165. const req = https.request({
  166. hostname: 'angelthump.com',
  167. path: '/authentication',
  168. method: 'POST',
  169. headers: {
  170. 'Content-Type': 'application/json',
  171. },
  172. }, res => res.on('data', data => {
  173. const {accessToken} = JSON.parse(data);
  174. resolve(accessToken);
  175. }));
  176. req.on('error', e => console.error(e));
  177. req.write(JSON.stringify({
  178. strategy: 'local-username',
  179. username: process.env.ANGELTHUMP_USER,
  180. password: process.env.ANGELTHUMP_PASSWORD,
  181. }));
  182. req.end();
  183. });
  184. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement