import titleParser from 'parse-torrent-title'; import { Type } from './types.js'; import { mapLanguages } from './languages.js'; import { enrichStreamSources, getSources } from './magnetHelper.js'; import { getSubtitles } from './subtitles.js'; const ADDON_NAME = 'Torrentio-sh'; const SIZE_DELTA = 0.02; const UNKNOWN_SIZE = 300000000; const CAM_SOURCES = ['CAM', 'TeleSync', 'TeleCine', 'SCR']; export function toStreamInfo(record) { const torrentInfo = titleParser.parse(record.torrent.title); const fileInfo = titleParser.parse(record.title); const sameInfo = !Number.isInteger(record.fileIndex) || Math.abs(record.size / record.torrent.size - 1) < SIZE_DELTA || record.title.includes(record.torrent.title); const quality = getQuality(record, torrentInfo, fileInfo); const hdrProfiles = torrentInfo.hdr || fileInfo.hdr || [] const title = joinDetailParts( [ joinDetailParts([record.torrent.title.replace(/[, ]+/g, ' ')]), joinDetailParts([!sameInfo && record.title || undefined]), joinDetailParts([ joinDetailParts([record.torrent.seeders], '👤 '), joinDetailParts([formatSize(record.size)], '💾 '), joinDetailParts([record.torrent.provider], '⚙️ ') ]), joinDetailParts(getLanguages(record, torrentInfo, fileInfo), '', ' / '), ], '', '\n' ); const name = joinDetailParts( [ joinDetailParts([ADDON_NAME]), joinDetailParts([quality, joinDetailParts(hdrProfiles, '', ' | ')]) ], '', '\n' ); const bingeGroupParts = getBingeGroupParts(record, sameInfo, quality, torrentInfo, fileInfo); const bingeGroup = joinDetailParts(bingeGroupParts, "torrentio|", "|") const behaviorHints = bingeGroup ? { bingeGroup } : undefined; return cleanOutputObject({ name: name, title: title, infoHash: record.infoHash, fileIdx: record.fileIndex, behaviorHints: behaviorHints, sources: getSources(record.torrent.trackers, record.infoHash), subtitles: getSubtitles(record) }); } function getQuality(record, torrentInfo, fileInfo) { if (CAM_SOURCES.includes(fileInfo.source)) { return fileInfo.source; } if (CAM_SOURCES.includes(torrentInfo.source)) { return torrentInfo.source; } const resolution = fileInfo.resolution || torrentInfo.resolution || record.torrent.resolution; const source = fileInfo.source || torrentInfo.source; return resolution || source; } function getLanguages(record, torrentInfo, fileInfo) { const providerLanguages = record.torrent.languages && titleParser.parse(record.torrent.languages + '.srt').languages || []; const torrentLanguages = torrentInfo.languages || []; const fileLanguages = fileInfo.languages || []; const dubbed = torrentInfo.dubbed || fileInfo.dubbed; let languages = Array.from(new Set([].concat(torrentLanguages).concat(fileLanguages).concat(providerLanguages))); if (record.kitsuId || record.torrent.type === Type.ANIME) { // no need to display japanese for anime languages = languages.concat(dubbed ? ['dubbed'] : []) .filter(lang => lang !== 'japanese'); } if (languages.length === 1 && languages.includes('english')) { // no need to display languages if only english is present languages = []; } if (languages.length === 0 && dubbed) { // display dubbed only if there are no other languages defined for non anime languages = ['dubbed']; } return mapLanguages(languages); } function joinDetailParts(parts, prefix = '', delimiter = ' ') { const filtered = parts.filter((part) => part !== undefined && part !== null).join(delimiter); return filtered.length > 0 ? `${prefix}${filtered}` : undefined; } function formatSize(size) { if (!size) { return undefined; } if (size === UNKNOWN_SIZE) { return undefined; } const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); return Number((size / Math.pow(1024, i)).toFixed(2)) + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; } export function applyStaticInfo(streams) { return streams.map(stream => enrichStaticInfo(stream)); } function enrichStaticInfo(stream) { return enrichSubtitles(enrichStreamSources({ ...stream })); } function enrichSubtitles(stream) { if (stream.subtitles?.length) { stream.subtitles = stream.subtitles.map(subtitle =>{ if (subtitle.url) { return subtitle; } return { id: `${subtitle.fileIndex}`, lang: subtitle.lang, url: `http://localhost:11470/${subtitle.infoHash}/${subtitle.fileIndex}/${subtitle.title.split('/').pop()}` }; }); } return stream; } function getBingeGroupParts(record, sameInfo, quality, torrentInfo, fileInfo) { if (record.torrent.type === Type.MOVIE) { const source = torrentInfo.source || fileInfo.source return [quality] .concat(source !== quality ? source : []) .concat(torrentInfo.codec || fileInfo.codec) .concat(torrentInfo.bitDepth || fileInfo.bitDepth) .concat(torrentInfo.hdr || fileInfo.hdr); } else if (sameInfo) { return [quality] .concat(fileInfo.hdr) .concat(fileInfo.group); } return [record.infoHash]; } function cleanOutputObject(object) { return Object.fromEntries(Object.entries(object).filter(([_, v]) => v != null)); }