mirror of
https://github.com/Inside4ndroid/TMDB-Embed-API.git
synced 2026-10-22 09:35:11 +00:00
docs: update README (unified stream schema, MoviesClub, registry mapping, docker run)\nchore(release): 1.0.4 tag prep\nchore: ignore restart.trigger and .vscode
This commit is contained in:
+88
-106
@@ -16,7 +16,7 @@ function processEpisode(episode, baseServer, serverName, serverNumber) {
|
||||
label: sub.label,
|
||||
url: `${baseServer}${sub.src}`
|
||||
})) : [];
|
||||
|
||||
|
||||
return {
|
||||
title: episode.show_title || episode.title,
|
||||
episode: episode.title,
|
||||
@@ -33,23 +33,23 @@ function processEpisode(episode, baseServer, serverName, serverNumber) {
|
||||
async function getMP4HydraStreams(tmdbId, mediaType, seasonNum = null, episodeNum = null) {
|
||||
try {
|
||||
console.log(`[MP4Hydra] Fetching streams for TMDB ID: ${tmdbId}, Type: ${mediaType}, Season: ${seasonNum}, Episode: ${episodeNum}`);
|
||||
|
||||
|
||||
// Get TMDB details
|
||||
const details = await getTMDBDetails(tmdbId, mediaType);
|
||||
if (!details) {
|
||||
console.log(`[MP4Hydra] Could not fetch details for TMDB ID: ${tmdbId}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
console.log(`[MP4Hydra] Found title: ${details.title} (${details.year})`);
|
||||
|
||||
|
||||
// Generate slug in the format "movie-name-year"
|
||||
let slug = details.slug;
|
||||
if (mediaType === 'movie' && details.year) {
|
||||
slug = `${details.slug}-${details.year}`;
|
||||
}
|
||||
console.log(`[MP4Hydra] Using slug: ${slug}`);
|
||||
|
||||
|
||||
// Create form data for multipart/form-data request
|
||||
const formData = new FormData();
|
||||
formData.append('v', '8');
|
||||
@@ -59,7 +59,7 @@ async function getMP4HydraStreams(tmdbId, mediaType, seasonNum = null, episodeNu
|
||||
se: seasonNum,
|
||||
ep: episodeNum
|
||||
}]));
|
||||
|
||||
|
||||
// Make request to MP4Hydra API
|
||||
const response = await axios({
|
||||
method: 'post',
|
||||
@@ -78,64 +78,61 @@ async function getMP4HydraStreams(tmdbId, mediaType, seasonNum = null, episodeNu
|
||||
},
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
|
||||
// Process response
|
||||
if (response.data && response.data.playlist && response.data.playlist.length > 0) {
|
||||
const playlist = response.data.playlist;
|
||||
const servers = response.data.servers;
|
||||
|
||||
|
||||
console.log(`[MP4Hydra] Found ${playlist.length} videos`);
|
||||
console.log(`[MP4Hydra] Available servers: ${Object.keys(servers).join(', ')}`);
|
||||
|
||||
|
||||
// For TV shows, find the specific episode
|
||||
if (mediaType === 'tv' && seasonNum && episodeNum) {
|
||||
const paddedSeason = seasonNum.toString().padStart(2, '0');
|
||||
const paddedEpisode = episodeNum.toString().padStart(2, '0');
|
||||
const seasonEpisode = `S${paddedSeason}E${paddedEpisode}`;
|
||||
|
||||
const targetEpisode = playlist.find(item =>
|
||||
|
||||
const targetEpisode = playlist.find(item =>
|
||||
item.title && item.title.toUpperCase() === seasonEpisode.toUpperCase()
|
||||
);
|
||||
|
||||
|
||||
if (!targetEpisode) {
|
||||
console.log(`[MP4Hydra] Could not find ${seasonEpisode}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
console.log(`[MP4Hydra] Found episode: ${targetEpisode.show_title || targetEpisode.title}`);
|
||||
|
||||
|
||||
// Process streams from main servers
|
||||
const streams = [];
|
||||
const serverConfig = [
|
||||
{ name: 'Beta', number: '#1' },
|
||||
{ name: 'Beta#3', number: '#2' }
|
||||
];
|
||||
|
||||
|
||||
serverConfig.forEach(server => {
|
||||
const serverName = server.name;
|
||||
const serverNumber = server.number;
|
||||
|
||||
|
||||
if (servers[serverName]) {
|
||||
const baseServer = servers[serverName];
|
||||
console.log(`[MP4Hydra] Processing server: ${serverName} (${baseServer})`);
|
||||
|
||||
|
||||
const processedEpisode = processEpisode(targetEpisode, baseServer, serverName, serverNumber);
|
||||
|
||||
|
||||
// Convert to standard stream format
|
||||
streams.push({
|
||||
title: `${details.title} - ${seasonEpisode} - ${processedEpisode.quality} [MP4Hydra ${serverNumber}]`,
|
||||
url: processedEpisode.videoUrl,
|
||||
quality: processedEpisode.quality,
|
||||
provider: "MP4Hydra",
|
||||
size: "Unknown size",
|
||||
behaviorHints: {
|
||||
notWebReady: true,
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
provider: "mp4hydra",
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Add subtitle tracks if available
|
||||
if (processedEpisode.subtitles && processedEpisode.subtitles.length > 0) {
|
||||
streams[streams.length - 1].subtitles = processedEpisode.subtitles.map(sub => ({
|
||||
@@ -145,43 +142,40 @@ async function getMP4HydraStreams(tmdbId, mediaType, seasonNum = null, episodeNu
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
|
||||
// For movies, process all videos
|
||||
const streams = [];
|
||||
const serverConfig = [
|
||||
{ name: 'Beta', number: '#1' },
|
||||
{ name: 'Beta#3', number: '#2' }
|
||||
];
|
||||
|
||||
|
||||
serverConfig.forEach(server => {
|
||||
const serverName = server.name;
|
||||
const serverNumber = server.number;
|
||||
|
||||
|
||||
if (servers[serverName]) {
|
||||
const baseServer = servers[serverName];
|
||||
console.log(`[MP4Hydra] Processing server: ${serverName} (${baseServer})`);
|
||||
|
||||
|
||||
playlist.forEach(item => {
|
||||
const processedItem = processEpisode(item, baseServer, serverName, serverNumber);
|
||||
|
||||
|
||||
// Convert to standard stream format
|
||||
streams.push({
|
||||
title: `${details.title} - ${processedItem.quality} [MP4Hydra ${serverNumber}]`,
|
||||
url: processedItem.videoUrl,
|
||||
quality: processedItem.quality,
|
||||
provider: "MP4Hydra",
|
||||
size: "Unknown size",
|
||||
behaviorHints: {
|
||||
notWebReady: true,
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
provider: "mp4hydra",
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Add subtitle tracks if available
|
||||
if (processedItem.subtitles && processedItem.subtitles.length > 0) {
|
||||
streams[streams.length - 1].subtitles = processedItem.subtitles.map(sub => ({
|
||||
@@ -192,19 +186,19 @@ async function getMP4HydraStreams(tmdbId, mediaType, seasonNum = null, episodeNu
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return streams;
|
||||
} else {
|
||||
// Try with original title if the first attempt failed
|
||||
if (details.title !== details.original_title) {
|
||||
return await tryAlternativeTitle(details, mediaType, seasonNum, episodeNum);
|
||||
}
|
||||
|
||||
|
||||
// Try without year for movies
|
||||
if (mediaType === 'movie' && details.year) {
|
||||
return await tryWithoutYear(details, mediaType, seasonNum, episodeNum);
|
||||
}
|
||||
|
||||
|
||||
console.log('[MP4Hydra] No streaming links found');
|
||||
return [];
|
||||
}
|
||||
@@ -220,11 +214,11 @@ async function tryAlternativeTitle(details, mediaType, seasonNum, episodeNum) {
|
||||
console.log(`[MP4Hydra] Retrying with original title: ${details.original_title}`);
|
||||
const originalSlug = generateSlug(details.original_title);
|
||||
let originalFullSlug = originalSlug;
|
||||
|
||||
|
||||
if (mediaType === 'movie' && details.year) {
|
||||
originalFullSlug = `${originalSlug}-${details.year}`;
|
||||
}
|
||||
|
||||
|
||||
// Create new form data with original title
|
||||
const formData = new FormData();
|
||||
formData.append('v', '8');
|
||||
@@ -234,7 +228,7 @@ async function tryAlternativeTitle(details, mediaType, seasonNum, episodeNum) {
|
||||
se: seasonNum,
|
||||
ep: episodeNum
|
||||
}]));
|
||||
|
||||
|
||||
const response = await axios({
|
||||
method: 'post',
|
||||
url: 'https://mp4hydra.org/info2?v=8',
|
||||
@@ -252,61 +246,58 @@ async function tryAlternativeTitle(details, mediaType, seasonNum, episodeNum) {
|
||||
},
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
|
||||
// Process response
|
||||
if (response.data && response.data.playlist && response.data.playlist.length > 0) {
|
||||
const playlist = response.data.playlist;
|
||||
const servers = response.data.servers;
|
||||
|
||||
|
||||
console.log(`[MP4Hydra] Found ${playlist.length} videos with original title`);
|
||||
|
||||
|
||||
// For TV shows, find the specific episode
|
||||
if (mediaType === 'tv' && seasonNum && episodeNum) {
|
||||
const paddedSeason = seasonNum.toString().padStart(2, '0');
|
||||
const paddedEpisode = episodeNum.toString().padStart(2, '0');
|
||||
const seasonEpisode = `S${paddedSeason}E${paddedEpisode}`;
|
||||
|
||||
const targetEpisode = playlist.find(item =>
|
||||
|
||||
const targetEpisode = playlist.find(item =>
|
||||
item.title && item.title.toUpperCase() === seasonEpisode.toUpperCase()
|
||||
);
|
||||
|
||||
|
||||
if (!targetEpisode) {
|
||||
console.log(`[MP4Hydra] Could not find ${seasonEpisode} with original title`);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
// Process streams from main servers
|
||||
const streams = [];
|
||||
const serverConfig = [
|
||||
{ name: 'Beta', number: '#1' },
|
||||
{ name: 'Beta#3', number: '#2' }
|
||||
];
|
||||
|
||||
|
||||
serverConfig.forEach(server => {
|
||||
const serverName = server.name;
|
||||
const serverNumber = server.number;
|
||||
|
||||
|
||||
if (servers[serverName]) {
|
||||
const baseServer = servers[serverName];
|
||||
console.log(`[MP4Hydra] Processing server: ${serverName} (${baseServer})`);
|
||||
|
||||
|
||||
const processedEpisode = processEpisode(targetEpisode, baseServer, serverName, serverNumber);
|
||||
|
||||
|
||||
// Convert to standard stream format
|
||||
streams.push({
|
||||
title: `${details.original_title} - ${seasonEpisode} - ${processedEpisode.quality} [MP4Hydra ${serverNumber}]`,
|
||||
url: processedEpisode.videoUrl,
|
||||
quality: processedEpisode.quality,
|
||||
provider: "MP4Hydra",
|
||||
size: "Unknown size",
|
||||
behaviorHints: {
|
||||
notWebReady: true,
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
provider: "mp4hydra",
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Add subtitle tracks if available
|
||||
if (processedEpisode.subtitles && processedEpisode.subtitles.length > 0) {
|
||||
streams[streams.length - 1].subtitles = processedEpisode.subtitles.map(sub => ({
|
||||
@@ -316,43 +307,36 @@ async function tryAlternativeTitle(details, mediaType, seasonNum, episodeNum) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
|
||||
// For movies, process all videos
|
||||
const streams = [];
|
||||
const serverConfig = [
|
||||
{ name: 'Beta', number: '#1' },
|
||||
{ name: 'Beta#3', number: '#2' }
|
||||
];
|
||||
|
||||
|
||||
serverConfig.forEach(server => {
|
||||
const serverName = server.name;
|
||||
const serverNumber = server.number;
|
||||
|
||||
|
||||
if (servers[serverName]) {
|
||||
const baseServer = servers[serverName];
|
||||
console.log(`[MP4Hydra] Processing server: ${serverName} (${baseServer})`);
|
||||
|
||||
|
||||
playlist.forEach(item => {
|
||||
const processedItem = processEpisode(item, baseServer, serverName, serverNumber);
|
||||
|
||||
|
||||
// Convert to standard stream format
|
||||
streams.push({
|
||||
name: `${details.original_title}`,
|
||||
title: `${details.original_title} - ${processedItem.quality} [MP4Hydra ${serverNumber}]`,
|
||||
url: processedItem.videoUrl,
|
||||
quality: processedItem.quality,
|
||||
provider: "MP4Hydra",
|
||||
size: "Unknown size",
|
||||
behaviorHints: {
|
||||
notWebReady: true,
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
}
|
||||
quality: processedItem.quality
|
||||
});
|
||||
|
||||
|
||||
// Add subtitle tracks if available
|
||||
if (processedItem.subtitles && processedItem.subtitles.length > 0) {
|
||||
streams[streams.length - 1].subtitles = processedItem.subtitles.map(sub => ({
|
||||
@@ -363,10 +347,10 @@ async function tryAlternativeTitle(details, mediaType, seasonNum, episodeNum) {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error(`[MP4Hydra] Error with alternative title:`, error.message);
|
||||
@@ -379,7 +363,7 @@ async function tryWithoutYear(details, mediaType, seasonNum, episodeNum) {
|
||||
try {
|
||||
console.log(`[MP4Hydra] Retrying with title only (without year): ${details.title}`);
|
||||
const titleOnlySlug = details.slug;
|
||||
|
||||
|
||||
// Create new form data with title only
|
||||
const formData = new FormData();
|
||||
formData.append('v', '8');
|
||||
@@ -389,7 +373,7 @@ async function tryWithoutYear(details, mediaType, seasonNum, episodeNum) {
|
||||
se: seasonNum,
|
||||
ep: episodeNum
|
||||
}]));
|
||||
|
||||
|
||||
const response = await axios({
|
||||
method: 'post',
|
||||
url: 'https://mp4hydra.org/info2?v=8',
|
||||
@@ -407,47 +391,45 @@ async function tryWithoutYear(details, mediaType, seasonNum, episodeNum) {
|
||||
},
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
|
||||
// Process response
|
||||
if (response.data && response.data.playlist && response.data.playlist.length > 0) {
|
||||
const playlist = response.data.playlist;
|
||||
const servers = response.data.servers;
|
||||
|
||||
|
||||
console.log(`[MP4Hydra] Found ${playlist.length} videos with title only`);
|
||||
|
||||
|
||||
// For movies, process all videos
|
||||
const streams = [];
|
||||
const serverConfig = [
|
||||
{ name: 'Beta', number: '#1' },
|
||||
{ name: 'Beta#3', number: '#2' }
|
||||
];
|
||||
|
||||
|
||||
serverConfig.forEach(server => {
|
||||
const serverName = server.name;
|
||||
const serverNumber = server.number;
|
||||
|
||||
|
||||
if (servers[serverName]) {
|
||||
const baseServer = servers[serverName];
|
||||
console.log(`[MP4Hydra] Processing server: ${serverName} (${baseServer})`);
|
||||
|
||||
|
||||
playlist.forEach(item => {
|
||||
const processedItem = processEpisode(item, baseServer, serverName, serverNumber);
|
||||
|
||||
|
||||
// Convert to standard stream format
|
||||
streams.push({
|
||||
name: `${details.title}`,
|
||||
title: `${details.title} - ${processedItem.quality} [MP4Hydra ${serverNumber}]`,
|
||||
url: processedItem.videoUrl,
|
||||
quality: processedItem.quality,
|
||||
provider: "MP4Hydra",
|
||||
size: "Unknown size",
|
||||
behaviorHints: {
|
||||
notWebReady: true,
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
provider: "mp4hydra",
|
||||
headers: {
|
||||
'Referer': 'https://mp4hydra.org/'
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Add subtitle tracks if available
|
||||
if (processedItem.subtitles && processedItem.subtitles.length > 0) {
|
||||
streams[streams.length - 1].subtitles = processedItem.subtitles.map(sub => ({
|
||||
@@ -458,10 +440,10 @@ async function tryWithoutYear(details, mediaType, seasonNum, episodeNum) {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error(`[MP4Hydra] Error with title-only:`, error.message);
|
||||
@@ -473,15 +455,15 @@ async function tryWithoutYear(details, mediaType, seasonNum, episodeNum) {
|
||||
async function getTMDBDetails(tmdbId, mediaType) {
|
||||
const { getTmdbApiKey } = require('../utils/tmdbKey');
|
||||
const TMDB_API_KEY = getTmdbApiKey();
|
||||
if(!TMDB_API_KEY){
|
||||
if (!TMDB_API_KEY) {
|
||||
console.warn('[MP4Hydra] TMDB API key missing; skipping TMDB metadata fetch');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
console.log(`[MP4Hydra] Fetching ${mediaType} details for TMDB ID: ${tmdbId}`);
|
||||
const response = await axios.get(`https://api.themoviedb.org/3/${mediaType}/${tmdbId}?api_key=${TMDB_API_KEY}`);
|
||||
|
||||
const response = await axios.get(`https://api.themoviedb.org/3/${mediaType}/${tmdbId}?api_key=${TMDB_API_KEY}`);
|
||||
|
||||
if (mediaType === 'movie') {
|
||||
return {
|
||||
title: response.data.title,
|
||||
|
||||
Reference in New Issue
Block a user