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,33 +18,70 @@
<div class="flex flex-col w-full">
<div class="flex flex-row h-full">
<div class="flex flex-col">
<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" />
{{ p.status }}
</div>
<div v-if="p.status === 'waiting'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
<Icon name="mdi:clock" class="h-3.5 w-3.5 text-white" />
{{ p.status }}
</div>
<div v-if="p.status === 'preparing'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
<Icon name="mdi:clock" class="h-3.5 w-3.5 text-white" />
{{ 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">
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ p.status }}
</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">
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ p.status }}
</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">
<Icon name="mdi:loading" class="h-3.5 w-3.5 text-white animate-spin" />
{{ p.status }}
</div>
<div v-if="p.status === 'completed'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#266326] rounded-lg">
<Icon name="material-symbols:check" class="h-3.5 w-3.5 text-white" />
{{ p.status }}
<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">
<Icon name="bitcoin-icons:cross-filled" class="h-3.5 w-3.5 text-white" />
{{ p.status }}
</div>
<div v-if="p.status === 'waiting'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
<Icon name="mdi:clock" class="h-3.5 w-3.5 text-white" />
{{ p.status }}
</div>
<div v-if="p.status === 'preparing'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#866332] rounded-lg">
<Icon name="mdi:clock" class="h-3.5 w-3.5 text-white" />
{{ p.status }}
</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">
<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'" 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 === '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" />
{{ p.status }}
</div>
<div v-if="p.status === 'completed'" class="flex flex-row items-center justify-center gap-1 text-xs capitalize p-1.5 bg-[#266326] rounded-lg">
<Icon name="material-symbols:check" class="h-3.5 w-3.5 text-white" />
{{ p.status }}
</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 class="text-xs capitalize ml-auto">
@ -63,7 +100,7 @@
<div class="text-xs">{{ p.quality }}p</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 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 v-if="p.totaldownloaded && p.status === 'downloading'" class="text-xs ml-auto"
>{{ (p.totaldownloaded / Math.pow(1024, 2)).toFixed(2) }} MB</div
@ -103,6 +140,10 @@ const playlist = ref<
quality: number
service: string
format: string
audiosdownloading: {
status: string,
audio: string
}[]
}>
>()
@ -123,6 +164,10 @@ const getPlaylist = async () => {
quality: number
service: string
format: string
audiosdownloading: {
status: string,
audio: string
}[]
}>
>('http://localhost:9941/api/service/playlist')

View File

@ -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<string>
sub: Array<string>
@ -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'
}

View File

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

View File

@ -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<string>,
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<string> = []
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<string> = []
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`

View File

@ -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<void>((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<void>((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<string> = []
@ -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`)
})
})