added hardsub and dub subtitle download

This commit is contained in:
Daniel Haller 2024-04-17 17:46:08 +02:00
parent 33899b4d42
commit e74c822f49
7 changed files with 185 additions and 71 deletions

View File

@ -1,5 +1,5 @@
<template>
<div class="flex flex-row bg-[#111111] h-16" style="-webkit-app-region: drag">
<div class="fixed w-full flex flex-row bg-[#111111] h-16 z-10" style="-webkit-app-region: drag">
<div class="w-full flex gap-10 flex-row items-center justify-center px-">
<button @click="openAddAnime" class="px-6 py-0.5 border-2 border-[#ce6104] rounded-xl" style="-webkit-app-region: no-drag">
<Icon name="ph:plus-bold" class="h-7 w-7 text-[#ce6104]" />
@ -56,7 +56,7 @@ async function openAddAnime() {
title: "Add Anime",
url: isProduction ? 'http://localhost:8079/addanime' : 'http://localhost:3000/addanime',
width: 700,
height: 400,
height: 450,
backgroundColor: "#111111"
})
}

View File

@ -165,12 +165,34 @@
</button>
</div>
</div>
<div class="relative flex flex-col">
<select v-model="hardsub" name="episode" class="bg-[#5c5b5b] focus:outline-none px-3 py-2 rounded-xl text-sm text-center cursor-pointer">
<option
:value="false"
class="text-sm text-slate-200"
>Hardsub: false</option
>
<option
:value="true"
class="text-sm text-slate-200"
>Hardsub: true</option
>
</select>
<div
class="absolute w-full h-9 bg-[#afadad] rounded-xl transition-all flex flex-row items-center justify-center gap-1 text-black"
:class="isFetchingEpisodes ? 'opacity-100' : 'opacity-0 pointer-events-none'"
>
<Icon name="mdi:loading" class="h-6 w-6 animate-spin" />
<div class="text-sm">Loading</div></div
>
</div>
<!-- {{ CRselectedShow?.Subs.map(s=> { return locales.find(l => l.locale === s)?.name }) }}
{{ CRselectedShow?.Dubs.map(s=> { return locales.find(l => l.locale === s)?.name }) }} -->
<div class="relative flex flex-col mt-auto">
<button @click="addToPlaylist" class="relative py-3 border-2 rounded-xl flex flex-row items-center justify-center">
<div class="flex flex-row items-center justify-center transition-all" :class="isFetchingSeasons ? 'opacity-0' : 'opacity-100'">
<div class="text-xl">Add to Queue</div>
<div class="text-xl">Add to Download</div>
</div>
<div class="absolute flex flex-row items-center justify-center gap-1 transition-all" :class="isFetchingSeasons ? 'opacity-100' : 'opacity-0'">
<Icon name="mdi:loading" class="h-6 w-6 animate-spin" />
@ -236,6 +258,7 @@ const episodes = ref<CrunchyEpisodes>()
const selectedSeason = ref<CrunchySeason>()
const selectedStartEpisode = ref<CrunchyEpisode>()
const selectedEndEpisode = ref<CrunchyEpisode>()
const hardsub = ref<boolean>(false)
const isFetchingSeasons = ref<number>(0)
const isFetchingEpisodes = ref<number>(0)
@ -402,7 +425,8 @@ const addToPlaylist = async () => {
episodes: [selectedStartEpisode.value],
dubs: selectedDubs.value,
subs: selectedSubs.value,
dir: path.value
dir: path.value,
hardsub: hardsub.value
}
const { error } = await useFetch('http://localhost:8080/api/crunchyroll/playlist', {

View File

@ -39,7 +39,7 @@ const openAddAnime = () => {
title: "Add Anime",
url: isProduction ? 'http://localhost:8079/addanime' : 'http://localhost:3000/addanime',
width: 700,
height: 400,
height: 450,
backgroundColor: "#111111"
})
}

View File

@ -68,13 +68,13 @@ const getPlaylist = async () => {
return
}
playlist.value = data.value.reverse()
playlist.value = data.value
}
onMounted(() => {
getPlaylist()
setInterval(getPlaylist, 1000)
setInterval(getPlaylist, 2000)
})
</script>

View File

@ -43,7 +43,8 @@ interface PlaylistCreateAttributes {
media: CrunchyEpisode
dub: Array<string>
sub: Array<string>
dir: string
dir: string,
hardsub: boolean,
}
const Account: ModelDefined<AccountAttributes, AccountCreateAttributes> = sequelize.define('Accounts', {
@ -103,6 +104,10 @@ const Playlist: ModelDefined<PlaylistAttributes, PlaylistCreateAttributes> = seq
allowNull: false,
type: DataTypes.STRING
},
hardsub: {
allowNull: false,
type: DataTypes.BOOLEAN
},
partsdownloaded: {
allowNull: true,
type: DataTypes.BOOLEAN,

View File

@ -66,7 +66,8 @@ export async function addPlaylistController(
episodes: CrunchyEpisodes
dubs: Array<string>
subs: Array<string>
dir: string
dir: string,
hardsub: boolean
}
}>,
reply: FastifyReply
@ -75,7 +76,7 @@ export async function addPlaylistController(
const body = request.body;
for (const e of body.episodes) {
await addEpisodeToPlaylist(e, body.subs, body.dubs, body.dir)
await addEpisodeToPlaylist(e, body.subs, body.dubs, body.dir, body.hardsub)
}
return reply.code(201).send()
@ -88,6 +89,6 @@ export async function getPlaylistController(
const playlist = await getPlaylist()
return reply.code(200).send(playlist)
return reply.code(200).send(playlist.reverse())
}

View File

@ -22,16 +22,18 @@ const crErrors = [
]
export async function crunchyLogin(user: string, passw: string) {
const cachedData: {
access_token: string
refresh_token: string
expires_in: number
token_type: string
scope: string
country: string
account_id: string
profile_id: string
} | undefined = server.CacheController.get('crtoken')
const cachedData:
| {
access_token: string
refresh_token: string
expires_in: number
token_type: string
scope: string
country: string
account_id: string
profile_id: string
}
| undefined = server.CacheController.get('crtoken')
if (!cachedData) {
var { data, error } = await crunchyLoginFetch(user, passw)
@ -129,12 +131,13 @@ export async function safeLoginData(user: string, password: string, service: str
return login?.get()
}
export async function addEpisodeToPlaylist(e: CrunchyEpisode, s: Array<string>, d: Array<string>, dir: string) {
export async function addEpisodeToPlaylist(e: CrunchyEpisode, s: Array<string>, d: Array<string>, dir: string, hardsub: boolean) {
const episode = await Playlist.create({
media: e,
sub: s,
dub: d,
dir: dir
dir: dir,
hardsub: hardsub
})
return episode.get()
@ -154,8 +157,22 @@ export async function updatePlaylistToDownloadPartsByID(id: number, parts: numbe
await Playlist.update({ partsleft: parts }, { where: { id: id } })
}
export async function updatePlaylistToDownloadedPartsByID(id: number, parts: number) {
let updateTimeout: NodeJS.Timeout | null = null
let cooldown = false
export async function updatePlaylistToDownloadedPartsByID(id: number, parts: number, lenght: number) {
if (cooldown && parts !== lenght) {
return
}
cooldown = true
await Playlist.update({ partsdownloaded: parts }, { where: { id: id } })
updateTimeout = setTimeout(function () {
cooldown = false
updateTimeout = null
}, 2000)
}
async function checkPlaylists() {
@ -163,11 +180,17 @@ async function checkPlaylists() {
for (const e of episodes) {
await updatePlaylistByID(e.dataValues.id, 'preparing')
await downloadPlaylist(e.dataValues.media.id, (e as any).dataValues.dub.map((s: { locale: any })=> s.locale), (e as any).dataValues.sub.map((s: { locale: any })=> s.locale), e.dataValues.hardsub, e.dataValues.id)
await downloadPlaylist(
e.dataValues.media.id,
(e as any).dataValues.dub.map((s: { locale: any }) => s.locale),
(e as any).dataValues.sub.map((s: { locale: any }) => s.locale),
e.dataValues.hardsub,
e.dataValues.id
)
}
}
cron.schedule('* * * * * *', () => {
cron.schedule('*/2 * * * * *', () => {
checkPlaylists()
})
@ -268,7 +291,10 @@ export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Arr
console.log(dubs)
console.log(subs)
if (!playlist) return
if (!playlist) {
console.log('Playlist not found')
return
}
if (playlist.audioLocale !== subs[0]) {
const found = playlist.versions.find((v) => v.audio_locale === 'ja-JP')
@ -277,7 +303,10 @@ export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Arr
}
}
if (!playlist) return
if (!playlist) {
console.log('Exact Playlist not found')
return
}
const subFolder = await createFolder()
@ -297,12 +326,29 @@ export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Arr
format: string
language: string
url: string
isDub: boolean
}> = []
for (const s of subs) {
const found = playlist.subtitles.find((sub) => sub.language === s)
var subPlaylist
if (playlist.audioLocale !== 'ja-JP') {
const foundStream = playlist.versions.find((v) => v.audio_locale === 'ja-JP')
if (foundStream) {
subPlaylist = await crunchyGetPlaylist(foundStream.guid)
}
} else {
subPlaylist = playlist
}
if (!subPlaylist) {
console.log('Subtitle Playlist not found')
return
}
const found = subPlaylist.subtitles.find((sub) => sub.language === s)
if (found) {
subDownloadList.push(found)
subDownloadList.push({ ...found, isDub: false })
console.log(`Subtitle ${s}.ass found, adding to download`)
} else {
console.warn(`Subtitle ${s}.ass not found, skipping`)
@ -312,6 +358,15 @@ export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Arr
for (const d of dubs) {
const found = playlist.versions.find((p) => p.audio_locale === d)
if (found) {
const list = await crunchyGetPlaylist(found.guid)
if (list) {
const foundSub = list.subtitles.find((sub) => sub.language === d)
if (foundSub) {
subDownloadList.push({ ...foundSub, isDub: true })
} else {
console.log(`No Dub Sub Found for ${d}`)
}
}
dubDownloadList.push(found)
console.log(`Audio ${d}.aac found, adding to download`)
} else {
@ -388,21 +443,19 @@ export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Arr
var mdp = await crunchyGetPlaylistMPD(play.url)
if (hardsub) {
const findjpplaylist = playlist.versions.find((p) => p.audio_locale === 'ja-JP')?.guid
// if (hardsub) {
// const hardsuburl = play.hardSubs.find(h=> h.hlang === subs[0])?.url
if (!findjpplaylist) return
// if (!hardsuburl) {
// console.error('No Hardsub stream found')
// return
// }
const hsplaylist = await crunchyGetPlaylist(findjpplaylist)
if (!hsplaylist) return
const hsurl = hsplaylist.hardSubs.find((h) => h.hlang === subs[0])
if (hsurl) {
mdp = await crunchyGetPlaylistMPD(hsurl.url)
}
}
// mdp = await crunchyGetPlaylistMPD(hardsuburl)
// console.error('Hardsub stream found')
// } else {
// console.error('Hardsub is false')
// }
if (!mdp) return
@ -424,7 +477,7 @@ export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Arr
})
}
await updatePlaylistByID(downloadID, "downloading")
await updatePlaylistByID(downloadID, 'downloading')
await updatePlaylistToDownloadPartsByID(downloadID, p.length)
@ -437,14 +490,14 @@ export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Arr
if (!audios) return
await updatePlaylistByID(downloadID, "merging")
await updatePlaylistByID(downloadID, 'merging')
await mergeFile(file as string, audios, subss, String(playlist.assetId))
await deleteFolder(subFolder)
await deleteFolder(audioFolder)
await updatePlaylistByID(downloadID, "completed")
await updatePlaylistByID(downloadID, 'completed')
return playlist
}
@ -486,7 +539,7 @@ async function downloadParts(parts: { filename: string; url: string }[], downloa
const path = await createFolder()
for (const [index, part] of parts.entries()) {
let success = false;
let success = false
while (!success) {
try {
const stream = fs.createWriteStream(`${path}/${part.filename}`)
@ -494,12 +547,12 @@ async function downloadParts(parts: { filename: string; url: string }[], downloa
await finished(Readable.fromWeb(body as any).pipe(stream))
console.log(`Fragment ${index + 1} downloaded`)
partsdownloaded++
updatePlaylistToDownloadedPartsByID(downloadID, partsdownloaded)
success = true;
updatePlaylistToDownloadedPartsByID(downloadID, partsdownloaded, parts.length)
success = true
} catch (error) {
console.error(`Error occurred during download of fragment ${index + 1}:`, error);
console.log(`Retrying download of fragment ${index + 1}...`);
await new Promise(resolve => setTimeout(resolve, 5000));
console.error(`Error occurred during download of fragment ${index + 1}:`, error)
console.log(`Retrying download of fragment ${index + 1}...`)
await new Promise((resolve) => setTimeout(resolve, 5000))
}
}
}
@ -512,10 +565,11 @@ async function downloadSub(
format: string
language: string
url: string
isDub: boolean
},
dir: string
) {
const path = `${dir}/${sub.language}.${sub.format}`
const path = `${dir}/${sub.language}${sub.isDub ? `-FORCED` : ''}.${sub.format}`
const stream = fs.createWriteStream(path)
const response = await fetch(sub.url)
@ -666,7 +720,7 @@ async function mergeFile(video: string, audios: Array<string>, subs: Array<strin
var output = Ffmpeg()
var ffindex = 1
output.addInput(video)
var options = ['-c copy', '-map 0']
var options = ['-map_metadata -1', '-c copy', '-metadata:s:v:0 VARIANT_BITRATE=0', '-map 0']
for (const [index, a] of audios.entries()) {
output.addInput(a)
@ -681,33 +735,63 @@ async function mergeFile(video: string, audios: Array<string>, subs: Array<strin
ffindex++
// Somehow not working
// options.push(
// `-metadata:s:a:${index} language="${
// locales.find(
// (l) => l.locale === a.split("/")[1].split(".aac")[0]
// ) ? locales.find(
// (l) => l.locale === a.split("/")[1].split(".aac")[0]
// )?.title : a.split("/")[1].split(".aac")[0]
// }"`
// );
options.push(
`-metadata:s:a:${index} title=${
locales.find((l) => l.locale === a.split('/')[1].split('.aac')[0])
? locales.find((l) => l.locale === a.split('/')[1].split('.aac')[0])?.title
: a.split('/')[1].split('.aac')[0]
}`
)
options.push(`-metadata:s:a:${index} VARIANT_BITRATE=0`)
}
options.push(`-disposition:a:0 default`)
if (subs) {
for (const [index, s] of subs.entries()) {
output.addInput(s)
options.push(`-map ${ffindex}:s`)
options.push(
`-metadata:s:s:${index} language=${
locales.find((l) => l.locale === s.split('/')[1].split('.ass')[0])
? locales.find((l) => l.locale === s.split('/')[1].split('.ass')[0])?.iso
: s.split('/')[1].split('.ass')[0]
}`
)
if (s.includes('-FORCED')) {
options.push(
`-metadata:s:s:${index} language=${
locales.find((l) => l.locale === s.split('/')[1].split('-FORCED.ass')[0])
? locales.find((l) => l.locale === s.split('/')[1].split('-FORCED.ass')[0])?.iso
: s.split('/')[1].split('-FORCED.ass')[0]
}`
)
} else {
options.push(
`-metadata:s:s:${index} language=${
locales.find((l) => l.locale === s.split('/')[1].split('.ass')[0])
? locales.find((l) => l.locale === s.split('/')[1].split('.ass')[0])?.iso
: s.split('/')[1].split('.ass')[0]
}`
)
}
if (s.includes('-FORCED')) {
options.push(
`-metadata:s:s:${index} title=${
locales.find((l) => l.locale === s.split('/')[1].split('-FORCED.ass')[0])
? locales.find((l) => l.locale === s.split('/')[1].split('-FORCED.ass')[0])?.title
: s.split('/')[1].split('-FORCED.ass')[0]
}[FORCED]`
)
} else {
options.push(
`-metadata:s:s:${index} title=${
locales.find((l) => l.locale === s.split('/')[1].split('.ass')[0])
? locales.find((l) => l.locale === s.split('/')[1].split('.ass')[0])?.title
: s.split('/')[1].split('.ass')[0]
}`
)
}
ffindex++
}
options.push(`-disposition:s:0 default`)
}
output