@@ -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`)
})
})