UNPKG

13.1 kBJavaScriptView Raw
1/*jslint node: true, esversion: 6, maxlen: 180 */
2"use strict";
3
4const assert = require('assert');
5const Path = require('path');
6const Async = require('async');
7
8const debug = require('debug')('upnpserver:contentHandlers:Tmdb');
9const logger = require('../logger');
10
11const MetasImages = require('./metas.images');
12
13const TmdbAPI = require('./tmdbAPI');
14
15const tmdbKey = /__tmdb(\d+)[^A-Za-z]/i;
16
17const tmtvKey = /__tmtv(\d+)[^A-Za-z]/i;
18
19class tmdb extends MetasImages {
20 constructor(configuration) {
21 super(configuration);
22
23 var apiKey = configuration.TMDB_API_KEY || process.env.TMDB_API_KEY;
24 if (apiKey) {
25 this._tmdbAPI = new TmdbAPI(apiKey, configuration);
26 }
27
28 this._basePath = this._basePath || process.env.TMDB_REPOSITORY;
29 }
30
31 get name() {
32 return "tmdb";
33 }
34
35 get domainKey() {
36 return "themoviedb.org";
37 }
38
39 _getKeyFromFileName(basename) {
40 var reg = tmdbKey.exec(basename);
41 debug("_getKeyFromFileName", "basename=", basename, " tmdbKey=>", reg);
42 if (reg) {
43 return {
44 key: reg[1],
45 type: 'movie'
46 };
47 }
48
49 reg = tmtvKey.exec(basename);
50 debug("_getKeyFromFileName", "basename=", basename, " tmdbKey=>", reg);
51 if (reg) {
52 return {
53 key: reg[1],
54 type: 'tvShow'
55 };
56 }
57
58 return null;
59 }
60
61 _getKeyFromDirectoryName(basename) {
62 var reg = tmtvKey.exec(basename);
63 debug("_getKeyFromDirectoryName", "basename=", basename, " tmdbKey=>", reg);
64 if (reg) {
65 return {
66 key: reg[1],
67 type: 'tvShow'
68 };
69 }
70
71 return null;
72 }
73
74 _computeJSONPathInBase(key, fileInfos, callback) {
75 debug("_computeJSONPathInBase", "key=", key);
76
77 if (!this._baseURL) {
78 return callback();
79 }
80
81 var jsonURL = this._baseURL.join(key.type + key.key + ".json");
82
83 debug("_computeJSONPathInBase", "key=", key, "fileInfos=", fileInfos, "=>", jsonURL);
84
85 callback(null, jsonURL, fileInfos);
86 }
87
88 _processTvShow(contentInfos, metasContext, jsonContext, fileInfos, callback) {
89 var season = fileInfos.season;
90 var episode = fileInfos.episode;
91
92 debug("_processTvShow", "Process file", contentInfos.contentURL, "jsonContext=", jsonContext, "season", season, "episode=", episode);
93
94 var json = jsonContext.content;
95
96 var tmdb = json; // ['themoviedb.org'];
97 if (tmdb.type !== "tvShow" || !tmdb.tvShow) {
98 return callback();
99 }
100
101 var tvShow = tmdb.tvShow;
102
103 var seasonJson = (tvShow.seasons || []).find((s) => s.season_number === season);
104 debug("_processTvShow", "SeasonJson=", seasonJson);
105 if (!seasonJson) {
106 return callback();
107 }
108
109 var episodeJson = (seasonJson.episodes || []).find((e) => e.episode_number === episode);
110 debug("_processTvShow", "EpisodeJson=", episodeJson);
111 if (!episodeJson) {
112 return callback();
113 }
114
115 var metas = {};
116 metas.season = episodeJson.season_number;
117 metas.episode = episodeJson.episode_number;
118 metas.episodeCount = seasonJson.episode_count;
119
120 if (episodeJson.name) {
121 metas.title = formatSeasonEpisode(episodeJson.name, season, episode);
122 metas.titleAlsoKnownAs = episodeJson.name;
123 } else {
124 metas.title = formatSeasonEpisode(null, season, episode);
125 }
126 if (episodeJson.overview) {
127 metas.longDescription = episodeJson.overview;
128 }
129 if (episodeJson.air_date) {
130 metas.airDate = episodeJson.air_date;
131 }
132
133 var statistics = episodeJson.vote_count;
134 if (statistics) {
135 metas.ratings = metas.ratings || [];
136 metas.ratings.push({
137 type: "af_user",
138 rating: episodeJson.vote_average / 2 // 5 stars
139 });
140 }
141
142 var tasks = [];
143
144 var isBaseURL = jsonContext.isBaseURL;
145 var resourcesURL;
146 if (isBaseURL) {
147 resourcesURL = jsonContext.url.join('..');
148
149 } else {
150 resourcesURL = jsonContext.url.join('..', this.name);
151 }
152
153
154 if (episodeJson.posters) {
155 episodeJson.posters.forEach((poster, idx) => {
156 tasks.push((callback) => {
157 var posterURL = resourcesURL.join(poster.path);
158 debug("_processTvShow", "try posterURL=", posterURL);
159
160 this._addImage(metas, posterURL, poster.path, poster.width, poster.height, "poster", idx, true, isBaseURL, callback);
161 });
162 });
163 }
164
165 if (episodeJson.stills) {
166 episodeJson.stills.forEach((poster, idx) => {
167 tasks.push((callback) => {
168 var ix = idx++;
169 var stillURL = resourcesURL.join(poster.path);
170 debug("_processTvShow", "try stillURL=", stillURL);
171
172 this._addImage(metas, stillURL, poster.path, poster.width, poster.height, "still", ix, true, isBaseURL, callback);
173 });
174 });
175 }
176
177 if (seasonJson.posters) {
178 seasonJson.posters.forEach((poster, idx) => {
179 tasks.push((callback) => {
180 var posterURL = resourcesURL.join(poster.path);
181 debug("_processTvShow", "try season posterURL=", posterURL);
182
183 this._addImage(metas, posterURL, poster.path, poster.width, poster.height, "season-poster", idx, true, isBaseURL, callback);
184 });
185 });
186 }
187
188 if (tvShow.posters) {
189 tvShow.posters.forEach((poster, idx) => {
190 tasks.push((callback) => {
191 var posterURL = resourcesURL.join(poster.path);
192 debug("_processTvShow", "try serie posterURL=", posterURL);
193
194 this._addImage(metas, posterURL, poster.path, poster.width, poster.height, "tv-poster", idx, true, isBaseURL, callback);
195 });
196 });
197 }
198
199 Async.series(tasks, (error) => {
200 if (error) {
201 return callback(error);
202 }
203
204 callback(null, metas);
205 });
206 }
207
208 _processFile(contentInfos, metasContext, jsonContext, fileInfos, callback) {
209 debug("_processFile", "Process file", contentInfos.contentURL, "json=", jsonContext, "infos=", fileInfos);
210 if (fileInfos.type === "tvShow") {
211 this._processTvShow(contentInfos, metasContext, jsonContext, fileInfos, callback);
212 return;
213 }
214
215 this._processMovie(contentInfos, metasContext, jsonContext, fileInfos, callback);
216 }
217
218 _processMovie(contentInfos, metasContext, jsonContext, fileInfos, callback) {
219 debug("_processMovie", "Process movie", contentInfos.contentURL, "json=", jsonContext, "fileInfos=", fileInfos);
220 var json = jsonContext.content;
221 var tmdb = json['themoviedb.org'];
222 if (!tmdb || tmdb.type !== "movie" || !tmdb.movieInfo) {
223 debug("_processMovie", "No tmdb", tmdb);
224 return callback();
225 }
226
227 callback();
228 }
229
230 _processFolder(contentInfos, metasContext, jsonContext, directoryInfo, callback) {
231 debug("_processFolder", "Process folder", contentInfos.contentURL, "json=", jsonContext, "directoryInfo=", directoryInfo);
232 var json = jsonContext.content;
233 var tmdb = json; // ['themoviedb.org'];
234 if (!tmdb || tmdb.type !== "tvShow") {
235 debug("_processFolder", "No tmdb", tmdb);
236 return callback();
237 }
238
239 var tvInfo = tmdb.tvShow;
240
241 if (!tvInfo) {
242 // No tmdb infos, download it ?
243 debug("_processFolder", "No tmdb key or apiKey", tmdb);
244 return callback();
245 }
246
247 var metas = {};
248 metas.seasons = tvInfo.number_of_seasons;
249 metas.episodes = tvInfo.number_of_episodes;
250
251 var genres = tvInfo.genres;
252 if (genres) {
253 metas.genres = metas.genres || [];
254
255 genres.forEach((genre) => {
256 metas.genres.push({
257 id: "tmdb_" + genre.id,
258 name: genre.name
259 });
260 });
261 }
262
263 var tasks = [];
264
265 var isBaseURL = jsonContext.isBaseURL;
266 var resourcesURL;
267 if (isBaseURL) {
268 resourcesURL = jsonContext.url.join('..');
269
270 } else {
271 resourcesURL = jsonContext.url.join('..', this.name);
272 }
273
274 if (tvInfo.posters) {
275 tvInfo.posters.forEach((poster, idx) => {
276 tasks.push((callback) => {
277 var posterURL = resourcesURL.join(poster.path);
278 debug("_processFolder", "try serie posterURL=", posterURL);
279
280 this._addImage(metas, posterURL, poster.path, poster.width, poster.height, "poster", idx, true, isBaseURL, callback);
281 });
282 });
283 }
284
285 if (tvInfo.backdrops) {
286 tvInfo.backdrops.forEach((poster, idx) => {
287 tasks.push((callback) => {
288 var backdropURL = resourcesURL.join(poster.path);
289 debug("_processFolder", "try serie backdropURL=", backdropURL);
290
291 this._addImage(metas, backdropURL, poster.path, poster.width, poster.height, "backdrop", idx, false, isBaseURL, callback);
292 });
293 });
294 }
295
296 Async.series(tasks, (error) => {
297 if (error) {
298 return callback(error);
299 }
300
301 callback(null, metas);
302 });
303 }
304
305 _loadTmdbImage(url, path, callback) {
306 url.stat((error, stats) => {
307 debug("_loadTmdbImage", "Stat of url=", url, "stats=", stats, "error=", error);
308 if (error) {
309 if (error.code === 'ENOENT') {
310 // Try to load it !
311
312 if (this._tmdbAPI) {
313 this._tmdbAPI.loadImage(url, path, false, callback);
314 return;
315 }
316 }
317
318 return callback(error);
319 }
320
321 if (!stats.size && this._tmdbAPI) {
322 this._tmdbAPI.loadImage(url, path, false, callback);
323 return;
324 }
325
326 callback(null, stats);
327 });
328 }
329
330 _getResourceContentURL(node, type, key, parameters, res, callback) {
331 debug("_getResourceContentURL", "node=", node.id, "type=", type, "key=", key, "parameters=", parameters);
332 var attributes = node.attributes;
333
334 var resKey = key;
335 var idx = 1;
336
337 var reg = /(\d+)/.exec(parameters[idx]);
338 if (reg) {
339 idx++;
340
341 var pi = parseInt(reg[1], 10);
342 resKey = parameters[0] + "/" + pi;
343
344 res = node.attributes.res.find((r) => r.contentHandlerKey === this.name && r.key === resKey);
345
346 debug("_getResourceContentURL", "newResKey=", resKey, "=>", res);
347 }
348
349 if (!res || !res.imagePath) {
350 return callback("Can not find resource #" + resKey);
351 }
352
353 debug("_getResourceContentURL", "Return resource path=", res.imagePath, "key=", key);
354
355 var url;
356 if (res.baseURL) {
357 url = this._baseURL.join(res.imagePath);
358
359 } else {
360 url = this._getBaseDirectoryFromNode(node).join(res.imagePath);
361 }
362
363 this._loadTmdbImage(url, res.imagePath, (error, stats) => {
364 debug("_getResourceContentURL", "loadTmdbImage url=", url, "stats=", stats, "error=", error);
365 if (error || !stats) {
366 logger.error(error);
367 return callback("Can not find resource #" + resKey);
368 }
369
370 if (parameters[idx]) {
371 reg = /w(\d+)/.exec(parameters[idx]);
372 if (reg) {
373 idx++;
374 debug("_getResourceContentURL", "param _wX", reg);
375
376 var requestedWidth = parseInt(reg[1], 10);
377 var requestedHeight = {
378 4096: 4096,
379 1024: 768,
380 640: 480,
381 160: 160
382 }[requestedWidth];
383 if (!requestedHeight) {
384 return callback("Invalid requested size !");
385 }
386
387 var r2 = /([^\/]+)\.([^.]+)$/.exec(url.basename);
388
389 debug("_getResourceContentURL", "Split path=", r2);
390 if (r2) {
391 var sizeSuffix = '_' + requestedWidth + "x" + requestedHeight;
392
393 var url2 = url.changeBasename(r2[1] + sizeSuffix + "." + r2[2]);
394
395 url2.stat((error, stats2) => {
396 debug("_getResourceContentURL", "url2=", url2, "error=", error, "stats=", stats2);
397
398 if (stats2 && stats2.size && stats2.mtime.getTime() > stats.mtime.getTime()) {
399 callback(null, url2);
400 return;
401 }
402
403 var sz = null;
404 if (res.width) {
405 sz = {
406 width: res.width,
407 height: res.height
408 };
409 }
410
411 debug("_getResourceContentURL", "url=", url, "Size=", sz, "requestWidth=", requestedWidth, "requestHeight=", requestedHeight);
412
413 this._convertImageSize(null, url, stats, sz, sizeSuffix, requestedWidth, requestedHeight, (error, imageURL, stats, json) => {
414 debug("_getResourceContentURL", "Return resized image url=", imageURL, "stats=", stats, "error=", error);
415 if (error) {
416 return callback(error);
417 }
418
419 callback(null, imageURL, stats);
420 });
421 });
422 return;
423 }
424 }
425 }
426
427 callback(null, url, stats);
428 });
429 }
430
431 _updateInfos(key, type, previousInfos, callback) {
432 assert(typeof (callback) === "function", "Invalid callback parameter");
433 var api = this._tmdbAPI;
434 if (!api) {
435 return callback();
436 }
437
438 if (type === 'tvShow') {
439 api.loadTvShow(key, previousInfos, (error, infos) => {
440 callback(error, infos);
441 });
442 return;
443 }
444
445 callback();
446 }
447
448 _searchForKey(name, type, callback) {
449 assert(typeof (callback) === "function", "Invalid callback parameter");
450 var api = this._tmdbAPI;
451 if (!api) {
452 return callback();
453 }
454
455 var ys = name.match(/d+/g);
456 if (ys) {
457 ys = ys.map(d => parseInt(d, 10)).filter(d => d > 1900);
458 }
459
460 var reg = /^([^.]+)/.exec(name);
461 if (reg) {
462 name = reg[1];
463 }
464
465 if (type === 'tvShow') {
466 api.searchTvShow(name, ys, (error, key) => {
467 callback(error, key);
468 });
469 return;
470 }
471
472 callback();
473 }
474}
475
476function formatSeasonEpisode(name, season, episode) {
477 var s = "";
478 if (typeof (season) === "number") {
479 s += "S" + ((season < 10) ? "0" : "") + season;
480 }
481 if (typeof (episode) === "number") {
482 s += "E" + ((episode < 10) ? "0" : "") + episode;
483 }
484
485 if (name) {
486 if (s) {
487 s += " ";
488 }
489
490 s += name;
491 }
492 return s;
493}
494
495module.exports = tmdb;