diff --git a/pages/index.vue b/pages/index.vue index f6ee7d4..34ad988 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -18,33 +18,70 @@
-
- - {{ p.status }} -
-
- - {{ p.status }} -
-
- - {{ p.status }} -
-
- - {{ p.status }} -
-
- - {{ p.status }} -
-
- - {{ p.status }} -
-
- - {{ p.status }} +
+
+ + {{ p.status }} +
+
+ + {{ p.status }} +
+
+ + {{ p.status }} +
+
+ + {{ p.status }} +
+
+ + {{ p.status }} +
+
+ + {{ p.status }} +
+
+ + {{ p.status }} +
+
+ + {{ p.status }} +
+
+ + {{ p.status }} +
+
+ + {{ p.status }} +
+
+ + {{ p.status }} +
+
+ + {{ p.status }} +
+
+
+ + {{ a.status }} Audio {{ a.audio }} +
+
@@ -63,7 +100,7 @@
{{ p.quality }}p
{{ p.format }}
Dubs: {{ p.dub.map((t) => t.name).join(', ') }}
-
Subs: {{ p.sub.length !== 0 ? p.sub.map((t) => t.name).join(', ') : '-' }}
+
Subs: {{ p.sub.length !== 0 ? p.sub.map((t) => t.name).join(', ') : '-' }}
{{ (p.totaldownloaded / Math.pow(1024, 2)).toFixed(2) }} MB
>() @@ -123,6 +164,10 @@ const getPlaylist = async () => { quality: number service: string format: string + audiosdownloading: { + status: string, + audio: string + }[] }> >('http://localhost:9941/api/service/playlist') diff --git a/src/api/db/database.ts b/src/api/db/database.ts index fcf8f55..6517512 100644 --- a/src/api/db/database.ts +++ b/src/api/db/database.ts @@ -29,7 +29,7 @@ interface AccountCreateAttributes { interface PlaylistAttributes { id: number - status: 'waiting' | 'preparing' | 'downloading' | 'merging' | 'decrypting' | 'completed' | 'failed' + status: 'waiting' | 'preparing' | 'waiting for playlist' | 'waiting for sub playlist' | 'waiting for dub playlist' | 'downloading' | 'merging video' | 'decrypting video' | 'awaiting all dubs downloaded' | 'merging video & audio' | 'completed' | 'failed' media: CrunchyEpisode | ADNEpisode dub: Array sub: Array @@ -49,7 +49,7 @@ interface PlaylistCreateAttributes { dir: string quality: 1080 | 720 | 480 | 360 | 240 hardsub: boolean - status: 'waiting' | 'preparing' | 'downloading' | 'merging' | 'decrypting' | 'completed' | 'failed' + status: 'waiting' | 'preparing' | 'waiting for playlist' | 'waiting for sub playlist' | 'waiting for dub playlist' | 'downloading' | 'merging video' | 'decrypting video' | 'awaiting all dubs downloaded' | 'merging video & audio' | 'completed' | 'failed' service: 'CR' | 'ADN' format: 'mp4' | 'mkv' } diff --git a/src/api/routes/service/service.controller.ts b/src/api/routes/service/service.controller.ts index 67b6e41..91316e7 100644 --- a/src/api/routes/service/service.controller.ts +++ b/src/api/routes/service/service.controller.ts @@ -4,6 +4,7 @@ import { addEpisodeToPlaylist, deleteAccountID, getAllAccounts, getDownloading, import { CrunchyEpisodes } from '../../types/crunchyroll' import { adnLogin } from '../adn/adn.service' import { server } from '../../api' +import { getDownloadingAudio } from '../../services/audio' export async function checkLoginController( request: FastifyRequest<{ @@ -121,8 +122,9 @@ export async function getPlaylistController(request: FastifyRequest, reply: Fast if (!playlist) return for (const v of playlist) { - if (v.dataValues.status === 'downloading') { + if (v.dataValues.status !== 'completed') { const found = await getDownloading(v.dataValues.id) + const foundAudio = await getDownloadingAudio(v.dataValues.id) if (found) { ;(v as any).dataValues = { ...v.dataValues, @@ -132,6 +134,12 @@ export async function getPlaylistController(request: FastifyRequest, reply: Fast totaldownloaded: found.totalDownloaded } } + if (foundAudio) { + ;(v as any).dataValues = { + ...v.dataValues, + audiosdownloading: foundAudio + } + } } } diff --git a/src/api/routes/service/service.service.ts b/src/api/routes/service/service.service.ts index dc7fb45..20ec149 100644 --- a/src/api/routes/service/service.service.ts +++ b/src/api/routes/service/service.service.ts @@ -164,7 +164,7 @@ deletePlaylistandTMP(); // Update Playlist Item export async function updatePlaylistByID( id: number, - status?: 'waiting' | 'preparing' | 'downloading' | 'merging' | 'decrypting' | 'completed' | 'failed', + status?: 'waiting' | 'preparing' | 'waiting for playlist' | 'waiting for sub playlist' | 'waiting for dub playlist' | 'downloading' | 'merging video' | 'decrypting video' | 'awaiting all dubs downloaded' | 'merging video & audio' | 'completed' | 'failed', quality?: 1080 | 720 | 480 | 360 | 240, installedDir?: string ) { @@ -199,7 +199,7 @@ export async function addEpisodeToPlaylist( d: Array, dir: string, hardsub: boolean, - status: 'waiting' | 'preparing' | 'downloading' | 'merging' | 'decrypting' | 'completed' | 'failed', + status: 'waiting' | 'preparing' | 'waiting for playlist' | 'waiting for sub playlist' | 'waiting for dub playlist' | 'downloading' | 'merging video' | 'decrypting video' | 'awaiting all dubs downloaded' | 'merging video & audio' | 'completed' | 'failed', quality: 1080 | 720 | 480 | 360 | 240, service: 'CR' | 'ADN', format: 'mp4' | 'mkv' @@ -222,6 +222,7 @@ export async function addEpisodeToPlaylist( // Define Downloading Array var downloading: Array<{ id: number + status: string downloadedParts: number partsToDownload: number downloadSpeed: number @@ -318,6 +319,7 @@ export async function downloadADNPlaylist( ) { downloading.push({ id: downloadID, + status: 'downloading', downloadedParts: 0, partsToDownload: 0, downloadSpeed: 0, @@ -462,13 +464,6 @@ export async function downloadCrunchyrollPlaylist( format: 'mp4' | 'mkv', geo: string | undefined ) { - downloading.push({ - id: downloadID, - downloadedParts: 0, - partsToDownload: 0, - downloadSpeed: 0, - totalDownloaded: 0 - }) const accmaxstream = await checkAccountMaxStreams(); @@ -476,7 +471,7 @@ export async function downloadCrunchyrollPlaylist( maxLimit = accmaxstream } - await updatePlaylistByID(downloadID, 'downloading') + await updatePlaylistByID(downloadID, 'waiting for playlist') await incrementPlaylistCounter(); var playlist = await crunchyGetPlaylist(e, geo) @@ -562,6 +557,8 @@ export async function downloadCrunchyrollPlaylist( isDub: boolean }> = [] + await updatePlaylistByID(downloadID, 'waiting for sub playlist') + for (const s of subs) { var subPlaylist @@ -612,6 +609,8 @@ export async function downloadCrunchyrollPlaylist( decrementPlaylistCounter() } + await updatePlaylistByID(downloadID, 'waiting for dub playlist') + for (const d of dubs) { var found if (playlist.data.versions) { @@ -665,6 +664,8 @@ export async function downloadCrunchyrollPlaylist( } } + await updatePlaylistByID(downloadID, 'downloading') + const subDownload = async () => { const sbs: Array = [] for (const sub of subDownloadList) { @@ -756,7 +757,7 @@ export async function downloadCrunchyrollPlaylist( }) } - const path = await downloadMPDAudio(p, audioFolder, list.data.audioLocale, keys ? keys : undefined) + const path = await downloadMPDAudio(p, audioFolder, list.data.audioLocale, downloadID, keys ? keys : undefined) audios.push(path as string) } @@ -764,6 +765,16 @@ export async function downloadCrunchyrollPlaylist( } const downloadVideo = async () => { + + downloading.push({ + id: downloadID, + status: 'Waiting for Playlist', + downloadedParts: 0, + partsToDownload: 0, + downloadSpeed: 0, + totalDownloaded: 0 + }) + var code if (!playlist) return @@ -949,6 +960,8 @@ export async function downloadCrunchyrollPlaylist( const file = await downloadParts(p, downloadID, videoFolder, keys ? keys : undefined) + await updatePlaylistByID(downloadID, 'awaiting all dubs downloaded') + return file } @@ -956,6 +969,7 @@ export async function downloadCrunchyrollPlaylist( if (!audios) return + await updatePlaylistByID(downloadID, 'merging video & audio') await mergeVideoFile(file as string, audios, subss, seasonFolder, `${name.replace(/[/\\?%*:|"<>]/g, '')} Season ${season} Episode ${episode}`, format, downloadID) await updatePlaylistByID(downloadID, 'completed') @@ -1039,7 +1053,7 @@ async function mergeParts(parts: { filename: string; url: string }[], downloadID try { const list: Array = [] - await updatePlaylistByID(downloadID, 'merging') + await updatePlaylistByID(downloadID, 'merging video') isDownloading-- for (const [index, part] of parts.entries()) { @@ -1057,7 +1071,7 @@ async function mergeParts(parts: { filename: string; url: string }[], downloadID await concatenateTSFiles(list, concatenatedFile) if (drmkeys) { - await updatePlaylistByID(downloadID, 'decrypting') + await updatePlaylistByID(downloadID, 'decrypting video') console.log('Video Decryption started') const inputFilePath = `${tmp}/temp-main.m4s` const outputFilePath = `${tmp}/main.m4s` diff --git a/src/api/services/audio.ts b/src/api/services/audio.ts index a0f0df5..1f7cb0d 100644 --- a/src/api/services/audio.ts +++ b/src/api/services/audio.ts @@ -8,11 +8,39 @@ import { getMP4DecryptPath } from '../services/mp4decrypt' const ffmpegP = getFFMPEGPath() const mp4e = getMP4DecryptPath() import util from 'util' +import { server } from '../api' const exec = util.promisify(require('child_process').exec) -export async function downloadMPDAudio(parts: { filename: string; url: string }[], dir: string, name: string, drmkeys?: { kid: string; key: string }[] | undefined) { +// Define Downloading Array +var downloading: Array<{ + id: number + status: string + audio: string +}> = [] + +export async function getDownloadingAudio(id: number) { + const found = downloading.filter((i) => i.id === id) + + if (found) return found + + return null +} + +export async function downloadMPDAudio( + parts: { filename: string; url: string }[], + dir: string, + name: string, + downloadID: number, + drmkeys?: { kid: string; key: string }[] | undefined +) { const path = await createFolder() + downloading.push({ + id: downloadID, + status: `downloading`, + audio: name + }) + const maxParallelDownloads = 5 const downloadPromises = [] @@ -42,27 +70,89 @@ export async function downloadMPDAudio(parts: { filename: string; url: string }[ } } - return await mergePartsAudio(parts, path, dir, name, drmkeys) + return await mergePartsAudio(parts, path, dir, name, downloadID, drmkeys) } async function fetchAndPipe(url: string, stream: fs.WriteStream, index: number) { - const { body } = await fetch(url) - const readableStream = Readable.from(body as any) + try { + const response = await fetch(url) - return new Promise((resolve, reject) => { - readableStream - .pipe(stream) - .on('finish', () => { - console.log(`Fragment ${index} downloaded`) - resolve() + // Check if fetch was successful + if (!response.ok) { + server.logger.log({ + level: 'error', + message: 'Error while downloading an Audio Fragment', + fragment: index, + error: await response.text(), + timestamp: new Date().toISOString(), + section: 'audiofragmentCrunchyrollFetch' }) - .on('error', (error) => { - reject(error) + throw new Error(`Failed to fetch URL: ${response.statusText}`) + } + + const body = response.body + + // Check if the body exists and is readable + if (!body) { + server.logger.log({ + level: 'error', + message: 'Error while downloading an Audio Fragment', + fragment: index, + error: 'Response body is not a readable stream', + timestamp: new Date().toISOString(), + section: 'audiofragmentCrunchyrollFetch' }) - }) + throw new Error('Response body is not a readable stream') + } + + const readableStream = Readable.from(body as any) + + return new Promise((resolve, reject) => { + readableStream + .pipe(stream) + .on('finish', () => { + console.log(`Fragment ${index} downloaded`) + resolve() + }) + .on('error', (error) => { + server.logger.log({ + level: 'error', + message: 'Error while downloading an Audio Fragment', + fragment: index, + error: error, + timestamp: new Date().toISOString(), + section: 'audiofragmentCrunchyrollFetch' + }) + reject(error) + }) + }) + } catch (error) { + server.logger.log({ + level: 'error', + message: 'Error while downloading an Audio Fragment', + fragment: index, + error: error, + timestamp: new Date().toISOString(), + section: 'audiofragmentCrunchyrollFetch' + }) + throw error + } } -async function mergePartsAudio(parts: { filename: string; url: string }[], tmp: string, dir: string, name: string, drmkeys?: { kid: string; key: string }[] | undefined) { +async function mergePartsAudio( + parts: { filename: string; url: string }[], + tmp: string, + dir: string, + name: string, + downloadID: number, + drmkeys?: { kid: string; key: string }[] | undefined +) { + const dn = downloading.find((i) => i.id === downloadID && i.audio === name) + + if (dn) { + dn.status = 'merging' + } + try { const list: Array = [] @@ -81,6 +171,9 @@ async function mergePartsAudio(parts: { filename: string; url: string }[], tmp: await concatenateTSFiles(list, concatenatedFile) if (drmkeys) { + if (dn) { + dn.status = 'decrypting' + } console.log(`Audio Decryption started`) const inputFilePath = `${tmp}/temp-main.m4s` const outputFilePath = `${tmp}/main.m4s` @@ -105,6 +198,10 @@ async function mergePartsAudio(parts: { filename: string; url: string }[], tmp: console.log('Merging finished') await deleteFolder(tmp) + if (dn) { + dn.status = 'finished' + } + return resolve(`${dir}/${name}.aac`) }) })