This commit is contained in:
Daniel Haller 2024-05-02 03:15:04 +02:00
parent 03bc1d5270
commit 755b69daf1
7 changed files with 96 additions and 62 deletions

7
.gitignore vendored
View File

@ -10,18 +10,19 @@ dist
.DS_Store .DS_Store
# Electron # Electron
crunchyroll-downloader-advanced-output crunchyroll-downloader-output
debug.log debug.log
debug.log debug.log
*.log *.log
debug.json debug.json
errors.json errors.json
build/ build/
crunchyroll-downloader-advanced-output-*/ crunchyroll-downloader-output-*/
updater/
# FFMPEG # FFMPEG
ffmpeg/ ffmpeg/
# MP4DECRYPT # MP4DECRYPT
mp4decrypt/ mp4decrypt/
keys/ src/api/services/widevine.ts

View File

@ -6,8 +6,8 @@ const Platform = builder.Platform
* @type {import('electron-builder').Configuration} * @type {import('electron-builder').Configuration}
*/ */
const options = { const options = {
appId: 'com.stratum.crunchyrolldownloaderadvanced', appId: 'com.stratum.crunchyrolldownloader',
productName: 'Crunchyroll Downloader Advanced', productName: 'Crunchyroll Downloader',
compression: 'maximum', compression: 'maximum',
removePackageScripts: true, removePackageScripts: true,
@ -15,12 +15,17 @@ const options = {
nodeGypRebuild: true, nodeGypRebuild: true,
buildDependenciesFromSource: true, buildDependenciesFromSource: true,
publish: {
provider: 'github',
releaseType: 'release'
},
directories: { directories: {
output: 'crunchyroll-downloader-advanced-output-${version}' output: 'crunchyroll-downloader-output-${version}'
}, },
win: { win: {
artifactName: 'crunchyroll-downloader-advanced-${version}-windows-installer.${ext}', artifactName: 'crunchyroll-downloader-${version}-windows-installer.${ext}',
icon: 'public/favicon.ico', icon: 'public/favicon.ico',
target: [ target: [
{ {

View File

@ -14,8 +14,7 @@
<img src="/logo.png" class="h-8" /> <img src="/logo.png" class="h-8" />
<div class="text-[10px] leading-[10px] text-opacity-90 text-white select-none" <div class="text-[10px] leading-[10px] text-opacity-90 text-white select-none"
>Crunchyroll <br /> >Crunchyroll <br />
Downloader <br /> Downloader</div
Advanced</div
> >
</div> </div>
<div class="w-full flex gap-2 flex-row items-center justify-center"> <div class="w-full flex gap-2 flex-row items-center justify-center">

58
components/Updater.vue Normal file
View File

@ -0,0 +1,58 @@
<template>
<div
class="fixed bottom-3 right-5 p-3 flex flex-col bg-[#111111dc] w-80 min-h-24 rounded-xl font-dm text-white transition-all duration-300"
:class="
(data?.status === 'update-available' && !ignoreUpdate) || data?.status === 'downloading' || data?.status === 'update-downloaded'
? 'opacity-100'
: 'opacity-0 pointer-events-none'
"
>
<button @click="ignoreUpdate = true" class="absolute right-3 top-2">
<Icon name="akar-icons:cross" class="h-4 w-4 text-white" />
</button>
<div class="text-base text-center"> Update available </div>
<div class="text-sm text-center"> A new update is available </div>
<div v-if="data && data.info && data.info.version" class="text-sm text-center"> v{{ data.info.version }} </div>
<button @click="startDownload" v-if="data && data.status === 'update-available'" class="text-sm py-3 bg-[#363434] mt-5 rounded-xl" :disabled="downloading">
Download Update
</button>
<button v-if="data && data.status === 'downloading'" class="relative text-sm py-3 bg-[#363434] mt-5 rounded-xl overflow-hidden">
<div class="absolute top-0 left-0 w-full h-full bg-[#a1a1a141] transition-all duration-300" :style="`width: calc((${data.info.percent} / 100) * 100%);`"></div>
Downloading...
</button>
<button @click="startInstall" v-if="data && data.status === 'update-downloaded'" class="text-sm py-3 bg-[#363434] mt-5 rounded-xl" :disabled="installing">
Install Update
</button>
</div>
</template>
<script lang="ts" setup>
const data = ref<{ status: string; info: any }>()
const downloading = ref<boolean>(false)
const installing = ref<boolean>(false)
const ignoreUpdate = ref<boolean>(false)
const checkUpdate = () => {
;(window as any).myAPI.getUpdateStatus().then((result: any) => {
data.value = result
})
}
const startDownload = () => {
downloading.value = true
;(window as any).myAPI.startUpdateDownload()
}
const startInstall = () => {
installing.value = true
;(window as any).myAPI.startUpdateInstall()
}
onMounted(() => {
checkUpdate()
setInterval(checkUpdate, 2000)
})
</script>
<style></style>

View File

@ -1,10 +1,11 @@
{ {
"name": "crunchyroll-downloader-advanced", "name": "crunchyroll-downloader",
"author": "Stratum", "author": "Stratum",
"description": "Crunchyroll Downloader Advanced", "description": "Crunchyroll Downloader",
"version": "1.0.0", "version": "1.1.0",
"private": true, "private": true,
"main": ".output/src/electron/background.js", "main": ".output/src/electron/background.js",
"repository": "https://github.com/stratuma/Crunchyroll-Downloader-v4.0",
"scripts": { "scripts": {
"dev": "nuxt dev -o", "dev": "nuxt dev -o",
"build": "nuxt generate", "build": "nuxt generate",
@ -53,11 +54,9 @@
"fastify": "^4.26.2", "fastify": "^4.26.2",
"fluent-ffmpeg": "^2.1.2", "fluent-ffmpeg": "^2.1.2",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"long": "^5.2.3",
"mpd-parser": "^1.3.0", "mpd-parser": "^1.3.0",
"node-cache": "^5.1.2", "node-cache": "^5.1.2",
"node-cron": "^3.0.3", "node-cron": "^3.0.3",
"protobufjs": "^7.2.6",
"sequelize": "^6.37.3", "sequelize": "^6.37.3",
"sqlite3": "5.1.6" "sqlite3": "5.1.6"
}, },
@ -65,7 +64,7 @@
"extraResources": [ "extraResources": [
"./ffmpeg/**", "./ffmpeg/**",
"./mp4decrypt/**", "./mp4decrypt/**",
"./keys/**" "./updater/**"
] ]
} }
} }

View File

@ -6,6 +6,9 @@ import { parse as mpdParse } from 'mpd-parser'
import { loggedInCheck } from '../service/service.service' import { loggedInCheck } from '../service/service.service'
import { app } from 'electron' import { app } from 'electron'
// Disable when Crunchyroll turns off switch endpoint
const enableDRMBypass = false
const crErrors = [ const crErrors = [
{ {
error: 'invalid_grant', error: 'invalid_grant',
@ -118,46 +121,15 @@ export async function crunchyGetPlaylist(q: string) {
} }
try { try {
const response = await fetch(`https://cr-play-service.prd.crunchyrollsvc.com/v1/${q}/console/switch/play`, { const response = await fetch(
method: 'GET', enableDRMBypass
headers: headers ? `https://cr-play-service.prd.crunchyrollsvc.com/v1/${q}/tv/samsung/play`
}) : `https://cr-play-service.prd.crunchyrollsvc.com/v1/${q}/console/switch/play`,
{
if (response.ok) { method: 'GET',
const data: VideoPlaylist = JSON.parse(await response.text()) headers: headers
}
data.hardSubs = Object.values((data as any).hardSubs) )
data.subtitles = Object.values((data as any).subtitles)
return { data: data, account_id: login.account_id }
} else {
throw new Error(await response.text())
}
} catch (e) {
throw new Error(e as string)
}
}
export async function crunchyGetPlaylistDRM(q: string) {
const account = await loggedInCheck('CR')
if (!account) return
const { data: login, error } = await crunchyLogin(account.username, account.password)
if (!login) return
const headers = {
Authorization: `Bearer ${login.access_token}`,
'x-cr-stream-limits': 'false'
}
try {
const response = await fetch(`https://cr-play-service.prd.crunchyrollsvc.com/v1/${q}/tv/samsung/play`, {
method: 'GET',
headers: headers
})
if (response.ok) { if (response.ok) {
const data: VideoPlaylist = JSON.parse(await response.text()) const data: VideoPlaylist = JSON.parse(await response.text())

View File

@ -4,7 +4,7 @@ import { concatenateTSFiles } from '../../services/concatenate'
import { createFolder, createFolderName, deleteFolder, deleteTemporaryFolders } from '../../services/folder' import { createFolder, createFolderName, deleteFolder, deleteTemporaryFolders } from '../../services/folder'
import { downloadADNSub, downloadCRSub } from '../../services/subs' import { downloadADNSub, downloadCRSub } from '../../services/subs'
import { CrunchyEpisode } from '../../types/crunchyroll' import { CrunchyEpisode } from '../../types/crunchyroll'
import { crunchyGetPlaylist, crunchyGetPlaylistDRM, crunchyGetPlaylistMPD, deleteVideoToken } from '../crunchyroll/crunchyroll.service' import { crunchyGetPlaylist, crunchyGetPlaylistMPD, deleteVideoToken } from '../crunchyroll/crunchyroll.service'
import fs from 'fs' import fs from 'fs'
var cron = require('node-cron') var cron = require('node-cron')
import { Readable } from 'stream' import { Readable } from 'stream'
@ -327,7 +327,7 @@ export async function downloadCrunchyrollPlaylist(
await updatePlaylistByID(downloadID, 'downloading') await updatePlaylistByID(downloadID, 'downloading')
var playlist = await crunchyGetPlaylistDRM(e) var playlist = await crunchyGetPlaylist(e)
if (!playlist) { if (!playlist) {
await updatePlaylistByID(downloadID, 'failed') await updatePlaylistByID(downloadID, 'failed')
@ -340,7 +340,7 @@ export async function downloadCrunchyrollPlaylist(
const found = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP') const found = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP')
if (found) { if (found) {
await deleteVideoToken(episodeID, playlist.data.token) await deleteVideoToken(episodeID, playlist.data.token)
playlist = await crunchyGetPlaylistDRM(found.guid) playlist = await crunchyGetPlaylist(found.guid)
} else { } else {
console.log('Exact Playlist not found, taking what crunchy gives.'), console.log('Exact Playlist not found, taking what crunchy gives.'),
messageBox( messageBox(
@ -394,7 +394,7 @@ export async function downloadCrunchyrollPlaylist(
if (playlist.data.audioLocale !== 'ja-JP') { if (playlist.data.audioLocale !== 'ja-JP') {
const foundStream = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP') const foundStream = playlist.data.versions.find((v) => v.audio_locale === 'ja-JP')
if (foundStream) { if (foundStream) {
subPlaylist = await crunchyGetPlaylistDRM(foundStream.guid) subPlaylist = await crunchyGetPlaylist(foundStream.guid)
} }
} else { } else {
subPlaylist = playlist subPlaylist = playlist
@ -424,7 +424,7 @@ export async function downloadCrunchyrollPlaylist(
} }
if (found) { if (found) {
const list = await crunchyGetPlaylistDRM(found.guid) const list = await crunchyGetPlaylist(found.guid)
if (list) { if (list) {
const foundSub = list.data.subtitles.find((sub) => sub.language === d) const foundSub = list.data.subtitles.find((sub) => sub.language === d)
if (foundSub) { if (foundSub) {
@ -479,7 +479,7 @@ export async function downloadCrunchyrollPlaylist(
const audioDownload = async () => { const audioDownload = async () => {
const audios: Array<string> = [] const audios: Array<string> = []
for (const v of dubDownloadList) { for (const v of dubDownloadList) {
const list = await crunchyGetPlaylistDRM(v.guid) const list = await crunchyGetPlaylist(v.guid)
if (!list) return if (!list) return
@ -554,7 +554,7 @@ export async function downloadCrunchyrollPlaylist(
return return
} }
const play = await crunchyGetPlaylistDRM(code) const play = await crunchyGetPlaylist(code)
if (!play) { if (!play) {
await updatePlaylistByID(downloadID, 'failed') await updatePlaylistByID(downloadID, 'failed')