added more downloading info (audio downloading, more statuses)

This commit is contained in:
stratuma 2024-05-23 15:17:01 +02:00
parent 60fde780fe
commit 20fc3c2ac5
5 changed files with 222 additions and 58 deletions

View File

@ -18,6 +18,7 @@
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">
<div class="flex flex-row h-full"> <div class="flex flex-row h-full">
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex flex-row">
<div v-if="p.status === 'failed'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#863232] rounded-lg"> <div v-if="p.status === 'failed'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#863232] rounded-lg">
<Icon name="bitcoin-icons:cross-filled" class="h-3.5 w-3.5 text-white" /> <Icon name="bitcoin-icons:cross-filled" class="h-3.5 w-3.5 text-white" />
{{ p.status }} {{ p.status }}
@ -30,15 +31,44 @@
<Icon name="mdi:clock" class="h-3.5 w-3.5 text-white" /> <Icon name="mdi:clock" class="h-3.5 w-3.5 text-white" />
{{ p.status }} {{ p.status }}
</div> </div>
<div
v-if="p.status === 'waiting for playlist'"
class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg"
>
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ p.status }}
</div>
<div
v-if="p.status === 'waiting for sub playlist'"
class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg"
>
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ p.status }}
</div>
<div
v-if="p.status === 'waiting for dub playlist'"
class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg"
>
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ p.status }}
</div>
<div v-if="p.status === 'downloading'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#60501b] rounded-lg"> <div v-if="p.status === 'downloading'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#60501b] rounded-lg">
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" /> <Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ p.status }} {{ p.status }}
</div> </div>
<div v-if="p.status === 'merging'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg"> <div v-if="p.status === 'merging video'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" /> <Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ p.status }} {{ p.status }}
</div> </div>
<div v-if="p.status === 'decrypting'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg"> <div v-if="p.status === 'decrypting video'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ p.status }}
</div>
<div v-if="p.status === 'awaiting all dubs downloaded'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ p.status }}
</div>
<div v-if="p.status === 'merging video & audio'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" /> <Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ p.status }} {{ p.status }}
</div> </div>
@ -46,6 +76,13 @@
<Icon name="material-symbols:check" class="h-3.5 w-3.5 text-white" /> <Icon name="material-symbols:check" class="h-3.5 w-3.5 text-white" />
{{ p.status }} {{ p.status }}
</div> </div>
<div v-for="a in p.audiosdownloading">
<div v-if="a.status && a.audio && a.status !== 'finished'" class="ml-2 flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ a.status }} Audio {{ a.audio }}
</div>
</div>
</div>
</div> </div>
<div class="text-xs capitalize ml-auto"> <div class="text-xs capitalize ml-auto">
{{ p.service === 'CR' ? 'Crunchyroll' : 'ADN' }} {{ p.service === 'CR' ? 'Crunchyroll' : 'ADN' }}
@ -63,7 +100,7 @@
<div class="text-xs">{{ p.quality }}p</div> <div class="text-xs">{{ p.quality }}p</div>
<div class="text-xs uppercase">{{ p.format }}</div> <div class="text-xs uppercase">{{ p.format }}</div>
<div class="text-xs">Dubs: {{ p.dub.map((t) => t.name).join(', ') }}</div> <div class="text-xs">Dubs: {{ p.dub.map((t) => t.name).join(', ') }}</div>
<div class="text-xs mr-14">Subs: {{ p.sub.length !== 0 ? p.sub.map((t) => t.name).join(', ') : '-' }}</div> <div class="text-xs mr-20">Subs: {{ p.sub.length !== 0 ? p.sub.map((t) => t.name).join(', ') : '-' }}</div>
<div class="absolute flex flex-col ml-auto gap-0.5 right-0 bottom-0"> <div class="absolute flex flex-col ml-auto gap-0.5 right-0 bottom-0">
<div v-if="p.totaldownloaded && p.status === 'downloading'" class="text-xs ml-auto" <div v-if="p.totaldownloaded && p.status === 'downloading'" class="text-xs ml-auto"
>{{ (p.totaldownloaded / Math.pow(1024, 2)).toFixed(2) }} MB</div >{{ (p.totaldownloaded / Math.pow(1024, 2)).toFixed(2) }} MB</div
@ -103,6 +140,10 @@ const playlist = ref<
quality: number quality: number
service: string service: string
format: string format: string
audiosdownloading: {
status: string,
audio: string
}[]
}> }>
>() >()
@ -123,6 +164,10 @@ const getPlaylist = async () => {
quality: number quality: number
service: string service: string
format: string format: string
audiosdownloading: {
status: string,
audio: string
}[]
}> }>
>('http://localhost:9941/api/service/playlist') >('http://localhost:9941/api/service/playlist')

View File

@ -29,7 +29,7 @@ interface AccountCreateAttributes {
interface PlaylistAttributes { interface PlaylistAttributes {
id: number 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 media: CrunchyEpisode | ADNEpisode
dub: Array<string> dub: Array<string>
sub: Array<string> sub: Array<string>
@ -49,7 +49,7 @@ interface PlaylistCreateAttributes {
dir: string dir: string
quality: 1080 | 720 | 480 | 360 | 240 quality: 1080 | 720 | 480 | 360 | 240
hardsub: boolean 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' service: 'CR' | 'ADN'
format: 'mp4' | 'mkv' format: 'mp4' | 'mkv'
} }

View File

@ -4,6 +4,7 @@ import { addEpisodeToPlaylist, deleteAccountID, getAllAccounts, getDownloading,
import { CrunchyEpisodes } from '../../types/crunchyroll' import { CrunchyEpisodes } from '../../types/crunchyroll'
import { adnLogin } from '../adn/adn.service' import { adnLogin } from '../adn/adn.service'
import { server } from '../../api' import { server } from '../../api'
import { getDownloadingAudio } from '../../services/audio'
export async function checkLoginController( export async function checkLoginController(
request: FastifyRequest<{ request: FastifyRequest<{
@ -121,8 +122,9 @@ export async function getPlaylistController(request: FastifyRequest, reply: Fast
if (!playlist) return if (!playlist) return
for (const v of playlist) { for (const v of playlist) {
if (v.dataValues.status === 'downloading') { if (v.dataValues.status !== 'completed') {
const found = await getDownloading(v.dataValues.id) const found = await getDownloading(v.dataValues.id)
const foundAudio = await getDownloadingAudio(v.dataValues.id)
if (found) { if (found) {
;(v as any).dataValues = { ;(v as any).dataValues = {
...v.dataValues, ...v.dataValues,
@ -132,6 +134,12 @@ export async function getPlaylistController(request: FastifyRequest, reply: Fast
totaldownloaded: found.totalDownloaded totaldownloaded: found.totalDownloaded
} }
} }
if (foundAudio) {
;(v as any).dataValues = {
...v.dataValues,
audiosdownloading: foundAudio
}
}
} }
} }

View File

@ -164,7 +164,7 @@ deletePlaylistandTMP();
// Update Playlist Item // Update Playlist Item
export async function updatePlaylistByID( export async function updatePlaylistByID(
id: number, 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, quality?: 1080 | 720 | 480 | 360 | 240,
installedDir?: string installedDir?: string
) { ) {
@ -199,7 +199,7 @@ export async function addEpisodeToPlaylist(
d: Array<string>, d: Array<string>,
dir: string, dir: string,
hardsub: boolean, 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, quality: 1080 | 720 | 480 | 360 | 240,
service: 'CR' | 'ADN', service: 'CR' | 'ADN',
format: 'mp4' | 'mkv' format: 'mp4' | 'mkv'
@ -222,6 +222,7 @@ export async function addEpisodeToPlaylist(
// Define Downloading Array // Define Downloading Array
var downloading: Array<{ var downloading: Array<{
id: number id: number
status: string
downloadedParts: number downloadedParts: number
partsToDownload: number partsToDownload: number
downloadSpeed: number downloadSpeed: number
@ -318,6 +319,7 @@ export async function downloadADNPlaylist(
) { ) {
downloading.push({ downloading.push({
id: downloadID, id: downloadID,
status: 'downloading',
downloadedParts: 0, downloadedParts: 0,
partsToDownload: 0, partsToDownload: 0,
downloadSpeed: 0, downloadSpeed: 0,
@ -462,13 +464,6 @@ export async function downloadCrunchyrollPlaylist(
format: 'mp4' | 'mkv', format: 'mp4' | 'mkv',
geo: string | undefined geo: string | undefined
) { ) {
downloading.push({
id: downloadID,
downloadedParts: 0,
partsToDownload: 0,
downloadSpeed: 0,
totalDownloaded: 0
})
const accmaxstream = await checkAccountMaxStreams(); const accmaxstream = await checkAccountMaxStreams();
@ -476,7 +471,7 @@ export async function downloadCrunchyrollPlaylist(
maxLimit = accmaxstream maxLimit = accmaxstream
} }
await updatePlaylistByID(downloadID, 'downloading') await updatePlaylistByID(downloadID, 'waiting for playlist')
await incrementPlaylistCounter(); await incrementPlaylistCounter();
var playlist = await crunchyGetPlaylist(e, geo) var playlist = await crunchyGetPlaylist(e, geo)
@ -562,6 +557,8 @@ export async function downloadCrunchyrollPlaylist(
isDub: boolean isDub: boolean
}> = [] }> = []
await updatePlaylistByID(downloadID, 'waiting for sub playlist')
for (const s of subs) { for (const s of subs) {
var subPlaylist var subPlaylist
@ -612,6 +609,8 @@ export async function downloadCrunchyrollPlaylist(
decrementPlaylistCounter() decrementPlaylistCounter()
} }
await updatePlaylistByID(downloadID, 'waiting for dub playlist')
for (const d of dubs) { for (const d of dubs) {
var found var found
if (playlist.data.versions) { if (playlist.data.versions) {
@ -665,6 +664,8 @@ export async function downloadCrunchyrollPlaylist(
} }
} }
await updatePlaylistByID(downloadID, 'downloading')
const subDownload = async () => { const subDownload = async () => {
const sbs: Array<string> = [] const sbs: Array<string> = []
for (const sub of subDownloadList) { 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) audios.push(path as string)
} }
@ -764,6 +765,16 @@ export async function downloadCrunchyrollPlaylist(
} }
const downloadVideo = async () => { const downloadVideo = async () => {
downloading.push({
id: downloadID,
status: 'Waiting for Playlist',
downloadedParts: 0,
partsToDownload: 0,
downloadSpeed: 0,
totalDownloaded: 0
})
var code var code
if (!playlist) return if (!playlist) return
@ -949,6 +960,8 @@ export async function downloadCrunchyrollPlaylist(
const file = await downloadParts(p, downloadID, videoFolder, keys ? keys : undefined) const file = await downloadParts(p, downloadID, videoFolder, keys ? keys : undefined)
await updatePlaylistByID(downloadID, 'awaiting all dubs downloaded')
return file return file
} }
@ -956,6 +969,7 @@ export async function downloadCrunchyrollPlaylist(
if (!audios) return 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 mergeVideoFile(file as string, audios, subss, seasonFolder, `${name.replace(/[/\\?%*:|"<>]/g, '')} Season ${season} Episode ${episode}`, format, downloadID)
await updatePlaylistByID(downloadID, 'completed') await updatePlaylistByID(downloadID, 'completed')
@ -1039,7 +1053,7 @@ async function mergeParts(parts: { filename: string; url: string }[], downloadID
try { try {
const list: Array<string> = [] const list: Array<string> = []
await updatePlaylistByID(downloadID, 'merging') await updatePlaylistByID(downloadID, 'merging video')
isDownloading-- isDownloading--
for (const [index, part] of parts.entries()) { for (const [index, part] of parts.entries()) {
@ -1057,7 +1071,7 @@ async function mergeParts(parts: { filename: string; url: string }[], downloadID
await concatenateTSFiles(list, concatenatedFile) await concatenateTSFiles(list, concatenatedFile)
if (drmkeys) { if (drmkeys) {
await updatePlaylistByID(downloadID, 'decrypting') await updatePlaylistByID(downloadID, 'decrypting video')
console.log('Video Decryption started') console.log('Video Decryption started')
const inputFilePath = `${tmp}/temp-main.m4s` const inputFilePath = `${tmp}/temp-main.m4s`
const outputFilePath = `${tmp}/main.m4s` const outputFilePath = `${tmp}/main.m4s`

View File

@ -8,11 +8,39 @@ import { getMP4DecryptPath } from '../services/mp4decrypt'
const ffmpegP = getFFMPEGPath() const ffmpegP = getFFMPEGPath()
const mp4e = getMP4DecryptPath() const mp4e = getMP4DecryptPath()
import util from 'util' import util from 'util'
import { server } from '../api'
const exec = util.promisify(require('child_process').exec) 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() const path = await createFolder()
downloading.push({
id: downloadID,
status: `downloading`,
audio: name
})
const maxParallelDownloads = 5 const maxParallelDownloads = 5
const downloadPromises = [] const downloadPromises = []
@ -42,11 +70,41 @@ 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) { async function fetchAndPipe(url: string, stream: fs.WriteStream, index: number) {
const { body } = await fetch(url) try {
const response = await fetch(url)
// 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'
})
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) const readableStream = Readable.from(body as any)
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
@ -57,12 +115,44 @@ async function fetchAndPipe(url: string, stream: fs.WriteStream, index: number)
resolve() resolve()
}) })
.on('error', (error) => { .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) 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,
downloadID: number,
drmkeys?: { kid: string; key: string }[] | undefined
) {
const dn = downloading.find((i) => i.id === downloadID && i.audio === name)
if (dn) {
dn.status = 'merging'
} }
async function mergePartsAudio(parts: { filename: string; url: string }[], tmp: string, dir: string, name: string, drmkeys?: { kid: string; key: string }[] | undefined) {
try { try {
const list: Array<string> = [] const list: Array<string> = []
@ -81,6 +171,9 @@ async function mergePartsAudio(parts: { filename: string; url: string }[], tmp:
await concatenateTSFiles(list, concatenatedFile) await concatenateTSFiles(list, concatenatedFile)
if (drmkeys) { if (drmkeys) {
if (dn) {
dn.status = 'decrypting'
}
console.log(`Audio Decryption started`) console.log(`Audio Decryption started`)
const inputFilePath = `${tmp}/temp-main.m4s` const inputFilePath = `${tmp}/temp-main.m4s`
const outputFilePath = `${tmp}/main.m4s` const outputFilePath = `${tmp}/main.m4s`
@ -105,6 +198,10 @@ async function mergePartsAudio(parts: { filename: string; url: string }[], tmp:
console.log('Merging finished') console.log('Merging finished')
await deleteFolder(tmp) await deleteFolder(tmp)
if (dn) {
dn.status = 'finished'
}
return resolve(`${dir}/${name}.aac`) return resolve(`${dir}/${name}.aac`)
}) })
}) })