Allow for byterange mpd/dash downloads

Prep for new service
This commit is contained in:
AnimeDL 2024-04-10 22:03:17 -07:00
parent 84ebabffc8
commit b1bae92308
4 changed files with 107 additions and 8 deletions

View file

@ -7,11 +7,40 @@ declare module 'mpd-parser' {
map: { map: {
uri: string, uri: string,
resolvedUri: string, resolvedUri: string,
byterange?: {
length: number,
offset: number
}
},
byterange?: {
length: number,
offset: number
}, },
number: number, number: number,
presentationTime: number presentationTime: number
} }
export type Sidx = {
uri: string,
resolvedUri: string,
byterange: {
length: number,
offset: number
},
map: {
uri: string,
resolvedUri: string,
byterange: {
length: number,
offset: number
}
},
duration: number,
timeline: number,
presentationTime: number,
number: number
}
export type Playlist = { export type Playlist = {
attributes: { attributes: {
NAME: string, NAME: string,
@ -45,6 +74,7 @@ declare module 'mpd-parser' {
} }
} }
segments: Segment[] segments: Segment[]
sidx?: Sidx
} }
export type Manifest = { export type Manifest = {

View file

@ -1563,7 +1563,7 @@ export default class Crunchy implements ServiceClass {
const streamPlaylistBody = await streamPlaylistsReq.res.text(); const streamPlaylistBody = await streamPlaylistsReq.res.text();
if (streamPlaylistBody.match('MPD')) { if (streamPlaylistBody.match('MPD')) {
//Parse MPD Playlists //Parse MPD Playlists
const streamPlaylists = parse(streamPlaylistBody, langsData.findLang(langsData.fixLanguageTag(pbData.meta.audio_locale as string) || ''), curStream.url.match(/.*\.urlset\//)[0]); const streamPlaylists = await parse(streamPlaylistBody, langsData.findLang(langsData.fixLanguageTag(pbData.meta.audio_locale as string) || ''), curStream.url.match(/.*\.urlset\//)[0]);
//Get name of CDNs/Servers //Get name of CDNs/Servers
const streamServers = Object.keys(streamPlaylists); const streamServers = Object.keys(streamPlaylists);

View file

@ -1014,7 +1014,7 @@ export default class Hidive implements ServiceClass {
console.info('\tSubs : ' + availableSubs.map(a => langsData.languages.find(b => b.new_hd_locale == a.language)?.name).join('\n\t\t')); console.info('\tSubs : ' + availableSubs.map(a => langsData.languages.find(b => b.new_hd_locale == a.language)?.name).join('\n\t\t'));
console.info(`[INFO] Selected dub(s): ${options.dubLang.join(', ')}`); console.info(`[INFO] Selected dub(s): ${options.dubLang.join(', ')}`);
const baseUrl = playbackData.dash[0].url.split('master')[0]; const baseUrl = playbackData.dash[0].url.split('master')[0];
const parsedmpd = parse(mpd, undefined, baseUrl); const parsedmpd = await parse(mpd, undefined, baseUrl);
const res = await this.downloadMPD(parsedmpd, availableSubs, selectedEpisode, options); const res = await this.downloadMPD(parsedmpd, availableSubs, selectedEpisode, options);
if (res === undefined || res.error) { if (res === undefined || res.error) {
console.error('Failed to download media list'); console.error('Failed to download media list');
@ -1103,7 +1103,7 @@ export default class Hidive implements ServiceClass {
console.info('\tSubs : ' + availableSubs.map(a => langsData.languages.find(b => b.new_hd_locale == a.language)?.name).join('\n\t\t')); console.info('\tSubs : ' + availableSubs.map(a => langsData.languages.find(b => b.new_hd_locale == a.language)?.name).join('\n\t\t'));
console.info(`[INFO] Selected dub(s): ${options.dubLang.join(', ')}`); console.info(`[INFO] Selected dub(s): ${options.dubLang.join(', ')}`);
const baseUrl = playbackData.dash[0].url.split('master')[0]; const baseUrl = playbackData.dash[0].url.split('master')[0];
const parsedmpd = parse(mpd, undefined, baseUrl); const parsedmpd = await parse(mpd, undefined, baseUrl);
const res = await this.downloadMPD(parsedmpd, availableSubs, selectedEpisode, options); const res = await this.downloadMPD(parsedmpd, availableSubs, selectedEpisode, options);
if (res === undefined || res.error) { if (res === undefined || res.error) {
console.error('Failed to download media list'); console.error('Failed to download media list');

View file

@ -7,9 +7,17 @@ type Segment = {
duration: number; duration: number;
map: { map: {
uri: string; uri: string;
byterange?: {
length: number,
offset: number
}; };
number: number; };
presentationTime: number; byterange?: {
length: number,
offset: number
};
number?: number;
presentationTime?: number;
} }
export type PlaylistItem = { export type PlaylistItem = {
@ -38,19 +46,49 @@ export type MPDParsed = {
} }
} }
export function parse(manifest: string, language?: LanguageItem, url?: string) { export async function parse(manifest: string, language?: LanguageItem, url?: string) {
if (!manifest.includes('BaseURL') && url) { if (!manifest.includes('BaseURL') && url) {
manifest = manifest.replace(/(<MPD*\b[^>]*>)/gm, `$1<BaseURL>${url}</BaseURL>`); manifest = manifest.replace(/(<MPD*\b[^>]*>)/gm, `$1<BaseURL>${url}</BaseURL>`);
} }
const parsed = mpdParse(manifest); const parsed = mpdParse(manifest);
const ret: MPDParsed = {}; const ret: MPDParsed = {};
// Audio Loop
for (const item of Object.values(parsed.mediaGroups.AUDIO.audio)){ for (const item of Object.values(parsed.mediaGroups.AUDIO.audio)){
for (const playlist of item.playlists) { for (const playlist of item.playlists) {
const host = new URL(playlist.resolvedUri).hostname; const host = new URL(playlist.resolvedUri).hostname;
if (!Object.prototype.hasOwnProperty.call(ret, host)) if (!Object.prototype.hasOwnProperty.call(ret, host))
ret[host] = { audio: [], video: [] }; ret[host] = { audio: [], video: [] };
if (playlist.sidx) {
const item = await fetch(playlist.sidx.uri, {
'method': 'head'
});
const byteLength = parseInt(item.headers.get('content-length') as string);
let currentByte = playlist.sidx.map.byterange.length;
while (currentByte <= byteLength) {
playlist.segments.push({
'duration': 0,
'map': {
'uri': playlist.resolvedUri,
'resolvedUri': playlist.resolvedUri,
'byterange': playlist.sidx.map.byterange
},
'uri': playlist.resolvedUri,
'resolvedUri': playlist.resolvedUri,
'byterange': {
'length': 500000,
'offset': currentByte
},
timeline: 0,
number: 0,
presentationTime: 0
});
currentByte = currentByte + 500000;
}
}
//Find and add audio language if it is found in the MPD //Find and add audio language if it is found in the MPD
let audiolang: LanguageItem; let audiolang: LanguageItem;
const foundlanguage = findLang(languages.find(a => a.code === item.language)?.cr_locale ?? 'unknown'); const foundlanguage = findLang(languages.find(a => a.code === item.language)?.cr_locale ?? 'unknown');
@ -68,10 +106,11 @@ export function parse(manifest: string, language?: LanguageItem, url?: string) {
const map_uri = segment.map.resolvedUri; const map_uri = segment.map.resolvedUri;
return { return {
duration: segment.duration, duration: segment.duration,
map: { uri: map_uri }, map: { uri: map_uri, byterange: segment.map.byterange },
number: segment.number, number: segment.number,
presentationTime: segment.presentationTime, presentationTime: segment.presentationTime,
timeline: segment.timeline, timeline: segment.timeline,
byterange: segment.byterange,
uri uri
}; };
}) })
@ -85,11 +124,40 @@ export function parse(manifest: string, language?: LanguageItem, url?: string) {
} }
} }
// Video Loop
for (const playlist of parsed.playlists) { for (const playlist of parsed.playlists) {
const host = new URL(playlist.resolvedUri).hostname; const host = new URL(playlist.resolvedUri).hostname;
if (!Object.prototype.hasOwnProperty.call(ret, host)) if (!Object.prototype.hasOwnProperty.call(ret, host))
ret[host] = { audio: [], video: [] }; ret[host] = { audio: [], video: [] };
if (playlist.sidx) {
const item = await fetch(playlist.sidx.uri, {
'method': 'head'
});
const byteLength = parseInt(item.headers.get('content-length') as string);
let currentByte = playlist.sidx.map.byterange.length;
while (currentByte <= byteLength) {
playlist.segments.push({
'duration': 0,
'map': {
'uri': playlist.resolvedUri,
'resolvedUri': playlist.resolvedUri,
'byterange': playlist.sidx.map.byterange
},
'uri': playlist.resolvedUri,
'resolvedUri': playlist.resolvedUri,
'byterange': {
'length': 2000000,
'offset': currentByte
},
timeline: 0,
number: 0,
presentationTime: 0
});
currentByte = currentByte + 2000000;
}
}
const pItem: VideoPlayList = { const pItem: VideoPlayList = {
bandwidth: playlist.attributes.BANDWIDTH, bandwidth: playlist.attributes.BANDWIDTH,
quality: playlist.attributes.RESOLUTION!, quality: playlist.attributes.RESOLUTION!,
@ -98,10 +166,11 @@ export function parse(manifest: string, language?: LanguageItem, url?: string) {
const map_uri = segment.map.resolvedUri; const map_uri = segment.map.resolvedUri;
return { return {
duration: segment.duration, duration: segment.duration,
map: { uri: map_uri }, map: { uri: map_uri, byterange: segment.map.byterange },
number: segment.number, number: segment.number,
presentationTime: segment.presentationTime, presentationTime: segment.presentationTime,
timeline: segment.timeline, timeline: segment.timeline,
byterange: segment.byterange,
uri uri
}; };
}) })