multiple fixes and additions

This commit is contained in:
stratuma 2024-05-23 23:04:58 +02:00
parent e97bb2a525
commit 7e255b6fcc
9 changed files with 174 additions and 146 deletions

17
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,17 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args" : ["."],
"outputCapture": "std"
}
]
}

View File

@ -9,19 +9,13 @@
placeholder="Episode Naming"
class="bg-[#5c5b5b] w-full focus:outline-none px-3 py-2 rounded-xl text-sm text-center"
/>
<div class="text-sm mt-2">
Example:
</div>
<div class="text-sm mt-2"> Example: </div>
<div class="text-sm">
{{ `${episodeNaming}` }}
</div>
<div class="text-sm mt-2">
Variables:
</div>
<div class="text-sm text-center">
{seriesName}, {seasonNumber}, {seasonNumberDD}, {episodeNumber}, {episodeNumberDD}, {quality}
</div>
<div class="text-sm mt-2"> Variables: </div>
<div class="text-sm text-center"> {seriesName}, {seasonNumber}, {seasonNumberDD}, {episodeNumber}, {episodeNumberDD}, {quality} </div>
</div>
<div class="flex flex-col items-center p-3 bg-[#11111189] rounded-xl select-none">
<div class="text-sm mb-2">Season Folder Naming</div>
@ -32,30 +26,24 @@
placeholder="Episode Naming"
class="bg-[#5c5b5b] w-full focus:outline-none px-3 py-2 rounded-xl text-sm text-center"
/>
<div class="text-sm mt-2">
Example:
</div>
<div class="text-sm mt-2"> Example: </div>
<div class="text-sm">
{{ `${seasonNaming}` }}
</div>
<div class="text-sm mt-2">
Variables:
</div>
<div class="text-sm text-center">
{seriesName}, {seasonNumber}, {seasonNumberDD}, {quality}
</div>
<div class="text-sm mt-2"> Variables: </div>
<div class="text-sm text-center"> {seriesName}, {seasonNumber}, {seasonNumberDD}, {quality} </div>
</div>
</div>
</template>
<script lang="ts" setup>
const episodeNumber = ref<number>(1);
const seasonNumber = ref<number>(1);
const quality = ref<number>(1080);
const seriesName = ref<string>('Frieren');
const episodeNamingTemplate = ref<string>();
const seasonNamingTemplate = ref<string>();
const episodeNumber = ref<number>(1)
const seasonNumber = ref<number>(1)
const quality = ref<number>(1080)
const seriesName = ref<string>('Frieren')
const episodeNamingTemplate = ref<string>()
const seasonNamingTemplate = ref<string>()
const episodeNaming = computed(() => {
if (!episodeNamingTemplate.value) return
@ -65,8 +53,8 @@ const episodeNaming = computed(() => {
.replace('{seasonNumberDD}', seasonNumber.value.toString().padStart(2, '0'))
.replace('{episodeNumber}', episodeNumber.value.toString())
.replace('{episodeNumberDD}', episodeNumber.value.toString().padStart(2, '0'))
.replace('{quality}', quality.value.toString() +'p');
});
.replace('{quality}', quality.value.toString() + 'p')
})
const seasonNaming = computed(() => {
if (!seasonNamingTemplate.value) return
@ -74,8 +62,8 @@ const seasonNaming = computed(() => {
.replace('{seriesName}', seriesName.value)
.replace('{seasonNumber}', seasonNumber.value.toString())
.replace('{seasonNumberDD}', seasonNumber.value.toString().padStart(2, '0'))
.replace('{quality}', quality.value.toString() +'p');
});
.replace('{quality}', quality.value.toString() + 'p')
})
onMounted(() => {
;(window as any).myAPI.getSeasonTemplate().then((result: string) => {
@ -111,7 +99,6 @@ const setSeasonTemplate = (name: string) => {
})
}
}
</script>
<style></style>

View File

@ -56,19 +56,35 @@
<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">
<div v-if="p.status === 'downloading video'" 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 === 'decrypting video'" 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" />
{{ 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">
<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 === 'merging video & audio'" 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 === '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>
@ -77,10 +93,13 @@
{{ 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
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>
@ -141,9 +160,9 @@ const playlist = ref<
service: string
format: string
audiosdownloading: {
status: string,
audio: string
}[]
status: string
audio: string
}[]
}>
>()
@ -165,7 +184,7 @@ const getPlaylist = async () => {
service: string
format: string
audiosdownloading: {
status: string,
status: string
audio: string
}[]
}>

View File

@ -29,7 +29,20 @@ interface AccountCreateAttributes {
interface PlaylistAttributes {
id: number
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'
status:
| 'waiting'
| 'preparing'
| 'waiting for playlist'
| 'waiting for sub playlist'
| 'waiting for dub playlist'
| 'downloading'
| 'downloading video'
| 'merging video'
| 'decrypting video'
| 'awaiting all dubs downloaded'
| 'merging video & audio'
| 'completed'
| 'failed'
media: CrunchyEpisode | ADNEpisode
dub: Array<string>
sub: Array<string>
@ -49,7 +62,20 @@ interface PlaylistCreateAttributes {
dir: string
quality: 1080 | 720 | 480 | 360 | 240
hardsub: boolean
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'
status:
| 'waiting'
| 'preparing'
| 'waiting for playlist'
| 'waiting for sub playlist'
| 'waiting for dub playlist'
| 'downloading'
| 'downloading video'
| 'merging video'
| 'decrypting video'
| 'awaiting all dubs downloaded'
| 'merging video & audio'
| 'completed'
| 'failed'
service: 'CR' | 'ADN'
format: 'mp4' | 'mkv'
}

View File

@ -17,10 +17,14 @@ const crErrors = [
// Crunchyroll Login Handler
export async function crunchyLogin(user: string, passw: string, geo: string) {
var endpoint = await settings.get('CREndpoint')
const drmL3blob = await settings.get('l3blob')
const drmL3key = await settings.get('l3key')
const drmL3blob = await settings.has('l3blob')
const drmL3key = await settings.has('l3key')
if (!drmL3blob || !drmL3key) {
endpoint = 1
}
if (!endpoint) {
await settings.set('CREndpoint', 1)
endpoint = 1
}
@ -71,8 +75,8 @@ async function crunchyLoginFetchProxy(user: string, passw: string, geo: string)
var body
var endpoint = await settings.get('CREndpoint')
const drmL3blob = await settings.get('l3blob')
const drmL3key = await settings.get('l3key')
const drmL3blob = await settings.has('l3blob')
const drmL3key = await settings.has('l3key')
var proxy:
| {
name: string
@ -189,10 +193,14 @@ async function crunchyLoginFetch(user: string, passw: string) {
var body
var endpoint = await settings.get('CREndpoint')
const drmL3blob = await settings.get('l3blob')
const drmL3key = await settings.get('l3key')
const drmL3blob = await settings.has('l3blob')
const drmL3key = await settings.has('l3key')
if (!drmL3blob || !drmL3key) {
endpoint = 1
}
if (!endpoint) {
await settings.set('CREndpoint', 1)
endpoint = 1
}
@ -274,6 +282,27 @@ async function crunchyLoginFetch(user: string, passw: string) {
}
}
let counter = 0
var maxLimit = 0
async function incrementPlaylistCounter() {
return new Promise<void>((resolve) => {
const interval = setInterval(() => {
if (counter < maxLimit) {
counter++
clearInterval(interval)
resolve()
}
}, 100)
})
}
function decrementPlaylistCounter() {
if (counter > 0) {
counter--
}
}
// Crunchyroll Playlist Fetch
export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
const isProxyActive = await settings.get('proxyActive')
@ -290,10 +319,14 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
}
var endpoint = await settings.get('CREndpoint')
const drmL3blob = await settings.get('l3blob')
const drmL3key = await settings.get('l3key')
const drmL3blob = await settings.has('l3blob')
const drmL3key = await settings.has('l3key')
if (!drmL3blob || !drmL3key) {
endpoint = 1
}
if (!endpoint) {
await settings.set('CREndpoint', 1)
endpoint = 1
}
@ -383,6 +416,12 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
var playlist: VideoPlaylist
if (maxLimit === 0) {
maxLimit = await checkAccountMaxStreams()
}
await incrementPlaylistCounter()
try {
const response = await fetch(
`https://cr-play-service.prd.crunchyrollsvc.com/v1/${q}${
@ -406,44 +445,12 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
playlist = data
} else {
const error = await response.text()
const errorJSON: {
activeStreams: {
accountId: string
active: boolean
assetId: string
clientId: string
contentId: string
country: string
createdTimestamp: string
deviceSubtype: string
deviceType: string
episodeIdentity: string
id: string
token: string
}[]
} = await JSON.parse(error)
if (errorJSON && errorJSON.activeStreams && errorJSON.activeStreams.length !== 0) {
for (const e of errorJSON.activeStreams) {
await deleteVideoToken(e.contentId, e.token)
}
server.logger.log({
level: 'error',
message: 'Refetching Crunchyroll Video Playlist & Deleting all Video Token because too many streams',
error: errorJSON,
timestamp: new Date().toISOString(),
section: 'playlistCrunchyrollFetch'
})
return await crunchyGetPlaylist(q, geo)
}
messageBox('error', ['Cancel'], 2, 'Failed to get Crunchyroll Video Playlist', 'Failed to get Crunchyroll Video Playlist', error)
server.logger.log({
level: 'error',
message: 'Failed to get Crunchyroll Video Playlist',
error: errorJSON,
error: error,
timestamp: new Date().toISOString(),
section: 'playlistCrunchyrollFetch'
})
@ -466,6 +473,8 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
'User-Agent': 'Crunchyroll/1.8.0 Nintendo Switch/12.3.12.0 UE4/4.27'
}
await incrementPlaylistCounter()
const responseProx = await fetch(
`https://cr-play-service.prd.crunchyrollsvc.com/v1/${q}${
endpoints.find((e) => e.id === endpoint) ? endpoints.find((e) => e.id === endpoint)?.url : '/console/switch/play'
@ -502,6 +511,8 @@ export async function crunchyGetPlaylist(q: string, geo: string | undefined) {
}
await deleteVideoToken(q, dataProx.token)
} else {
decrementPlaylistCounter();
}
}
}
@ -536,6 +547,9 @@ export async function deleteVideoToken(content: string, token: string) {
timestamp: new Date().toISOString(),
section: 'tokenDeletionCrunchyrollFetch'
})
decrementPlaylistCounter()
return 'ok'
} else {
const error = await response.text()
@ -605,7 +619,7 @@ export async function getAccountInfo() {
if (!login) return
const headers = {
Authorization: `Bearer ${login.access_token}`,
Authorization: `Bearer ${login.access_token}`
}
try {
@ -616,8 +630,8 @@ export async function getAccountInfo() {
if (response.ok) {
const data: {
account_id: string,
external_id: string,
account_id: string
external_id: string
} = await JSON.parse(await response.text())
return data
@ -640,7 +654,6 @@ export async function getAccountInfo() {
// Check Max account streams because of crunchyroll activestream limit
export async function checkAccountMaxStreams() {
const accountinfo = await getAccountInfo()
if (!accountinfo) return 1
@ -654,7 +667,7 @@ export async function checkAccountMaxStreams() {
if (!login) return 1
const headers = {
Authorization: `Bearer ${login.access_token}`,
Authorization: `Bearer ${login.access_token}`
}
try {
@ -666,22 +679,22 @@ export async function checkAccountMaxStreams() {
if (response.ok) {
const data: {
items: {
__class__: string,
__href__: string,
__links__: string,
__actions__: string,
benefit: string,
__class__: string
__href__: string
__links__: string
__actions__: string
benefit: string
source: string
}[]
} = await JSON.parse(await response.text())
if (!data.items || data.items.length === 0) return 1
if (data.items.find(i => i.benefit === 'concurrent_streams.4')) return 2
if (data.items.find((i) => i.benefit === 'concurrent_streams.4')) return 2
if (data.items.find(i => i.benefit === 'concurrent_streams.1')) return 1
if (data.items.find((i) => i.benefit === 'concurrent_streams.1')) return 1
if (data.items.find(i => i.benefit === 'concurrent_streams.6')) return 3
if (data.items.find((i) => i.benefit === 'concurrent_streams.6')) return 3
return 1
} else {
@ -699,4 +712,4 @@ export async function checkAccountMaxStreams() {
} catch (e) {
throw new Error(e as string)
}
}
}

View File

@ -171,6 +171,7 @@ export async function updatePlaylistByID(
| 'waiting for sub playlist'
| 'waiting for dub playlist'
| 'downloading'
| 'downloading video'
| 'merging video'
| 'decrypting video'
| 'awaiting all dubs downloaded'
@ -193,7 +194,7 @@ export async function updatePlaylistByID(
section: 'playlistItemUpdateDatabase'
})
} catch (e) {
messageBox('error', ['Cancel'], 2, 'Database Error', 'Failed to update playlist item', JSON.stringify(e))
messageBox('error', ['Cancel'], 2, 'Database Error', 'Failed to update playlist item', 'Failed to update playlist item')
server.logger.log({
level: 'error',
message: 'Failed to update playlist item',
@ -218,6 +219,7 @@ export async function addEpisodeToPlaylist(
| 'waiting for sub playlist'
| 'waiting for dub playlist'
| 'downloading'
| 'downloading video'
| 'merging video'
| 'decrypting video'
| 'awaiting all dubs downloaded'
@ -451,27 +453,6 @@ export async function downloadADNPlaylist(
await deleteFolder(videoFolder)
}
var counter = 0
var maxLimit = 1
async function incrementPlaylistCounter() {
return new Promise<void>((resolve) => {
const interval = setInterval(() => {
if (counter < maxLimit) {
counter++
clearInterval(interval)
resolve()
}
}, 100)
})
}
function decrementPlaylistCounter() {
if (counter > 0) {
counter--
}
}
// Download Crunchyroll Playlist
export async function downloadCrunchyrollPlaylist(
e: string,
@ -488,15 +469,8 @@ export async function downloadCrunchyrollPlaylist(
format: 'mp4' | 'mkv',
geo: string | undefined
) {
const accmaxstream = await checkAccountMaxStreams()
if (accmaxstream) {
maxLimit = accmaxstream
}
await updatePlaylistByID(downloadID, 'waiting for playlist')
await incrementPlaylistCounter()
var playlist = await crunchyGetPlaylist(e, geo)
if (!playlist) {
@ -517,8 +491,6 @@ export async function downloadCrunchyrollPlaylist(
const found = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP')
if (found) {
await deleteVideoToken(episodeID, playlist.data.token)
decrementPlaylistCounter()
await incrementPlaylistCounter()
playlist = await crunchyGetPlaylist(found.guid, found.geo)
} else {
console.log('Exact Playlist not found, taking what crunchy gives.')
@ -547,7 +519,6 @@ export async function downloadCrunchyrollPlaylist(
}
await deleteVideoToken(episodeID, playlist.data.token)
decrementPlaylistCounter()
const subFolder = await createFolder()
@ -600,7 +571,6 @@ export async function downloadCrunchyrollPlaylist(
if (playlist.data.audioLocale !== 'ja-JP') {
const foundStream = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP')
if (foundStream) {
await incrementPlaylistCounter()
subPlaylist = await crunchyGetPlaylist(foundStream.guid, foundStream.geo)
}
} else {
@ -641,7 +611,6 @@ export async function downloadCrunchyrollPlaylist(
}
await deleteVideoToken(episodeID, subPlaylist.data.token)
decrementPlaylistCounter()
}
await updatePlaylistByID(downloadID, 'waiting for dub playlist')
@ -653,11 +622,9 @@ export async function downloadCrunchyrollPlaylist(
}
if (found) {
await incrementPlaylistCounter()
const list = await crunchyGetPlaylist(found.guid, found.geo)
if (list) {
await deleteVideoToken(episodeID, list.data.token)
decrementPlaylistCounter()
const foundSub = list.data.subtitles.find((sub) => sub.language === d)
if (foundSub) {
@ -699,7 +666,7 @@ export async function downloadCrunchyrollPlaylist(
}
}
await updatePlaylistByID(downloadID, 'downloading')
await updatePlaylistByID(downloadID, 'downloading video')
const subDownload = async () => {
const sbs: Array<string> = []
@ -713,7 +680,6 @@ export async function downloadCrunchyrollPlaylist(
const audioDownload = async () => {
const audios: Array<string> = []
for (const v of dubDownloadList) {
await incrementPlaylistCounter()
const list = await crunchyGetPlaylist(v.guid, v.geo)
if (!list) return
@ -723,7 +689,6 @@ export async function downloadCrunchyrollPlaylist(
if (!playlist) return
await deleteVideoToken(episodeID, list.data.token)
decrementPlaylistCounter()
const assetId = playlist.mediaGroups.AUDIO.audio.main.playlists[0].segments[0].resolvedUri.match(/\/assets\/(?:p\/)?([^_,]+)/)
@ -837,7 +802,6 @@ export async function downloadCrunchyrollPlaylist(
return
}
await incrementPlaylistCounter()
const play = await crunchyGetPlaylist(code, geo)
if (!play) {
@ -882,7 +846,6 @@ export async function downloadCrunchyrollPlaylist(
if (!mdp) return
await deleteVideoToken(episodeID, play.data.token)
decrementPlaylistCounter()
var hq = mdp.playlists.find((i) => i.attributes.RESOLUTION?.height === quality)

View File

@ -4,8 +4,7 @@ import fs from 'fs'
import settings from 'electron-settings'
export async function createFolder() {
var tempPath = await settings.get('tempPath') as string
var tempPath = (await settings.get('tempPath')) as string
if (!tempPath) {
tempPath = app.getPath('temp')
@ -33,8 +32,7 @@ export async function checkDirectoryExistence(dir: string) {
}
export async function createFolderName(name: string, dir: string) {
var tempPath = await settings.get('tempPath') as string
var tempPath = (await settings.get('tempPath')) as string
if (!tempPath) {
tempPath = app.getPath('temp')
@ -69,8 +67,7 @@ export async function deleteFolder(folderPath: string) {
}
export async function deleteTemporaryFolders() {
var tempPath = await settings.get('tempPath') as string
var tempPath = (await settings.get('tempPath')) as string
if (!tempPath) {
tempPath = app.getPath('temp')

View File

@ -18,6 +18,7 @@ var mainWindow: BrowserWindow
function createWindow() {
console.log('System info', { isProduction, platform, architucture })
mainWindow = new BrowserWindow({
title: 'Crunchyroll Downloader',
icon: __dirname + '/icon/favicon.ico',
@ -73,6 +74,11 @@ function createWindow() {
// App events
// ==========
app.whenReady().then(async () => {
settings.configure({
dir: app.getPath('documents') + '/Crunchyroll Downloader/settings/',
atomicSave: process.platform !== 'win32'
})
startAPI()
const mainWindow = createWindow()

View File

@ -22,5 +22,5 @@ contextBridge.exposeInMainWorld('myAPI', {
setEpisodeTemplate: (name: string) => ipcRenderer.invoke('dialog:setEpisodeTemplate', name),
getEpisodeTemplate: () => ipcRenderer.invoke('dialog:getEpisodeTemplate'),
setSeasonTemplate: (name: string) => ipcRenderer.invoke('dialog:setSeasonTemplate', name),
getSeasonTemplate: () => ipcRenderer.invoke('dialog:getSeasonTemplate'),
getSeasonTemplate: () => ipcRenderer.invoke('dialog:getSeasonTemplate')
})