added better dir and file creation and bugfixes
This commit is contained in:
parent
e74c822f49
commit
e59f808ae4
@ -5,6 +5,12 @@ A simple tool for downloading videos from Crunchyroll and ADN.
|
|||||||
- Linux
|
- Linux
|
||||||
## Credits
|
## Credits
|
||||||
- This is literally just an improved version of [hama3254's](https://github.com/hama3254/Crunchyroll-Downloader-v3.0) [Crunchyroll-Downloader-v3.0](https://github.com/hama3254/Crunchyroll-Downloader-v3.0)
|
- This is literally just an improved version of [hama3254's](https://github.com/hama3254/Crunchyroll-Downloader-v3.0) [Crunchyroll-Downloader-v3.0](https://github.com/hama3254/Crunchyroll-Downloader-v3.0)
|
||||||
|
## To-Do
|
||||||
|
- ADN Downloader
|
||||||
|
- Download Pause/Delete
|
||||||
|
- Download Speed Display
|
||||||
|
- Settings
|
||||||
|
- Open Download Path
|
||||||
## User Interface
|
## User Interface
|
||||||
#### Home
|
#### Home
|
||||||
![Screenshot 2024-04-06 180439](https://github.com/Junon401/CR-Downloader/assets/166554835/b45c5799-3716-49c9-8abf-5fc7c9fe08c9)
|
![Screenshot 2024-04-06 180439](https://github.com/Junon401/CR-Downloader/assets/166554835/b45c5799-3716-49c9-8abf-5fc7c9fe08c9)
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<div v-if="tab === 1" class="flex flex-col mt-5 gap-3.5 h-full" style="-webkit-app-region: no-drag">
|
<div v-if="tab === 1" class="flex flex-col mt-5 gap-3.5 h-full" style="-webkit-app-region: no-drag">
|
||||||
<div class="relative flex flex-col">
|
<div class="relative flex flex-col">
|
||||||
<select v-model="service" name="service" class="bg-[#5c5b5b] focus:outline-none px-3 py-2 rounded-xl text-sm text-center cursor-pointer">
|
<select v-model="service" name="service" class="bg-[#5c5b5b] focus:outline-none px-3 py-2 rounded-xl text-sm text-center cursor-pointer">
|
||||||
<option value="adn">ADN</option>
|
<!-- <option value="adn">ADN</option> -->
|
||||||
<option value="crunchyroll">Crunchyroll</option>
|
<option value="crunchyroll">Crunchyroll</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -64,7 +64,7 @@
|
|||||||
<div class="relative flex flex-col">
|
<div class="relative flex flex-col">
|
||||||
<input v-model="url" type="text" name="text" placeholder="URL" class="bg-[#5c5b5b] focus:outline-none px-3 py-2 rounded-xl text-sm text-center" />
|
<input v-model="url" type="text" name="text" placeholder="URL" class="bg-[#5c5b5b] focus:outline-none px-3 py-2 rounded-xl text-sm text-center" />
|
||||||
</div>
|
</div>
|
||||||
<div class="relative flex flex-col">
|
<!-- <div class="relative flex flex-col">
|
||||||
<input
|
<input
|
||||||
@click="getFolderPath()"
|
@click="getFolderPath()"
|
||||||
v-model="path"
|
v-model="path"
|
||||||
@ -74,7 +74,7 @@
|
|||||||
class="bg-[#5c5b5b] focus:outline-none px-3 py-2 rounded-xl text-sm text-center cursor-pointer"
|
class="bg-[#5c5b5b] focus:outline-none px-3 py-2 rounded-xl text-sm text-center cursor-pointer"
|
||||||
readonly
|
readonly
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> -->
|
||||||
<div class="relative flex flex-col mt-auto">
|
<div class="relative flex flex-col mt-auto">
|
||||||
<button @click="switchToSeason" class="relative py-3 border-2 rounded-xl flex flex-row items-center justify-center">
|
<button @click="switchToSeason" 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="flex flex-row items-center justify-center transition-all" :class="isFetchingSeasons ? 'opacity-0' : 'opacity-100'">
|
||||||
@ -421,8 +421,20 @@ const toggleSub = (lang: { name: string | undefined; locale: string }) => {
|
|||||||
|
|
||||||
const addToPlaylist = async () => {
|
const addToPlaylist = async () => {
|
||||||
|
|
||||||
|
if (!episodes.value) return
|
||||||
|
|
||||||
|
const startEpisodeIndex = episodes.value.findIndex(episode => episode === selectedStartEpisode.value);
|
||||||
|
const endEpisodeIndex = episodes.value.findIndex(episode => episode === selectedEndEpisode.value);
|
||||||
|
|
||||||
|
if (startEpisodeIndex === -1 || endEpisodeIndex === -1) {
|
||||||
|
console.error('Indexes not found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedEpisodes = episodes.value.slice(startEpisodeIndex, endEpisodeIndex + 1);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
episodes: [selectedStartEpisode.value],
|
episodes: selectedEpisodes,
|
||||||
dubs: selectedDubs.value,
|
dubs: selectedDubs.value,
|
||||||
subs: selectedSubs.value,
|
subs: selectedSubs.value,
|
||||||
dir: path.value,
|
dir: path.value,
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<MainHeader />
|
<MainHeader />
|
||||||
<div class="flex flex-col text-white">
|
<div class="flex flex-col text-white pt-16">
|
||||||
|
<button @click="deletePlaylist">
|
||||||
|
Delete Playlist
|
||||||
|
</button>
|
||||||
<div v-for="p in playlist" class="flex flex-row gap-4 h-40 p-5 bg-[#636363]">
|
<div v-for="p in playlist" class="flex flex-row gap-4 h-40 p-5 bg-[#636363]">
|
||||||
<div class="flex min-w-52 w-52">
|
<div class="flex min-w-52 w-52">
|
||||||
<img :src="p.media.images.thumbnail[0].find((p) => p.height === 1080)?.source" alt="Image" class="object-cover rounded-xl" />
|
<img :src="p.media.images.thumbnail[0].find((p) => p.height === 1080)?.source" alt="Image" class="object-cover rounded-xl" />
|
||||||
@ -12,7 +15,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-base capitalize"> {{ p.media.series_title }} Season {{ p.media.season_number }} Episode {{ p.media.episode_number }} </div>
|
<div class="text-base capitalize"> {{ p.media.series_title }} Season {{ p.media.season_number }} Episode {{ p.media.episode_number }} </div>
|
||||||
<div class="relative w-full min-h-5 bg-[#bdbbbb] mt-1 rounded">
|
<div class="relative w-full min-h-5 bg-[#bdbbbb] mt-1 rounded">
|
||||||
<div v-if="p.partsleft && p.status === 'downloading'" class="w-full h-full rounded bg-[#4e422d] transition-all duration-300" :style="`width: calc((${p.partsdownloaded} / ${p.partsleft}) * 100%);`"></div>
|
<div
|
||||||
|
v-if="p.partsleft && p.status === 'downloading'"
|
||||||
|
class="w-full h-full rounded bg-[#4e422d] transition-all duration-300"
|
||||||
|
:style="`width: calc((${p.partsdownloaded} / ${p.partsleft}) * 100%);`"
|
||||||
|
></div>
|
||||||
<div v-if="p.status === 'completed'" class="w-full h-full rounded bg-[#79ff77] transition-all duration-300"></div>
|
<div v-if="p.status === 'completed'" class="w-full h-full rounded bg-[#79ff77] transition-all duration-300"></div>
|
||||||
<div v-if="p.status === 'merging'" class="absolute top-0 w-20 h-full rounded bg-[#293129] transition-all duration-300 loading-a"></div>
|
<div v-if="p.status === 'merging'" class="absolute top-0 w-20 h-full rounded bg-[#293129] transition-all duration-300 loading-a"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -22,6 +29,7 @@
|
|||||||
<div class="text-sm">Dubs: {{ p.dub.map((t) => t.name).join(', ') }}</div>
|
<div class="text-sm">Dubs: {{ p.dub.map((t) => t.name).join(', ') }}</div>
|
||||||
<div class="text-sm">Subs: {{ p.sub.map((t) => t.name).join(', ') }}</div>
|
<div class="text-sm">Subs: {{ p.sub.map((t) => t.name).join(', ') }}</div>
|
||||||
<div v-if="p.partsleft && p.status === 'downloading'" class="text-sm">{{ p.partsdownloaded }}/{{ p.partsleft }}</div>
|
<div v-if="p.partsleft && p.status === 'downloading'" class="text-sm">{{ p.partsdownloaded }}/{{ p.partsleft }}</div>
|
||||||
|
<div v-if="p.downloadspeed && p.status === 'downloading'" class="text-sm">{{ p.downloadspeed }} MB/s</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> </div
|
</div> </div
|
||||||
@ -39,9 +47,10 @@ const playlist = ref<
|
|||||||
media: CrunchyEpisode
|
media: CrunchyEpisode
|
||||||
dub: Array<{ locale: string; name: string }>
|
dub: Array<{ locale: string; name: string }>
|
||||||
sub: Array<{ locale: string; name: string }>
|
sub: Array<{ locale: string; name: string }>
|
||||||
dir: string,
|
dir: string
|
||||||
partsleft: number,
|
partsleft: number
|
||||||
partsdownloaded: number
|
partsdownloaded: number
|
||||||
|
downloadspeed: number
|
||||||
}>
|
}>
|
||||||
>()
|
>()
|
||||||
|
|
||||||
@ -53,9 +62,10 @@ const getPlaylist = async () => {
|
|||||||
media: CrunchyEpisode
|
media: CrunchyEpisode
|
||||||
dub: Array<{ locale: string; name: string }>
|
dub: Array<{ locale: string; name: string }>
|
||||||
sub: Array<{ locale: string; name: string }>
|
sub: Array<{ locale: string; name: string }>
|
||||||
dir: string,
|
dir: string
|
||||||
partsleft: number,
|
partsleft: number
|
||||||
partsdownloaded: number
|
partsdownloaded: number
|
||||||
|
downloadspeed: number
|
||||||
}>
|
}>
|
||||||
>('http://localhost:8080/api/crunchyroll/playlist')
|
>('http://localhost:8080/api/crunchyroll/playlist')
|
||||||
|
|
||||||
@ -71,6 +81,21 @@ const getPlaylist = async () => {
|
|||||||
playlist.value = data.value
|
playlist.value = data.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deletePlaylist = async () => {
|
||||||
|
const { data, error } = await useFetch('http://localhost:8080/api/crunchyroll/playlist', {
|
||||||
|
method: "delete"
|
||||||
|
})
|
||||||
|
|
||||||
|
if (error.value) {
|
||||||
|
alert(error.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getPlaylist()
|
getPlaylist()
|
||||||
|
|
||||||
@ -79,7 +104,6 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
.loading-a {
|
.loading-a {
|
||||||
animation: animation infinite 3s;
|
animation: animation infinite 3s;
|
||||||
}
|
}
|
||||||
@ -97,5 +121,4 @@ onMounted(() => {
|
|||||||
left: 0%;
|
left: 0%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -34,8 +34,6 @@ interface PlaylistAttributes {
|
|||||||
sub: Array<string>
|
sub: Array<string>
|
||||||
hardsub: boolean,
|
hardsub: boolean,
|
||||||
dir: string,
|
dir: string,
|
||||||
partsdownloaded: number,
|
|
||||||
partsleft: number,
|
|
||||||
failedreason: string
|
failedreason: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +43,7 @@ interface PlaylistCreateAttributes {
|
|||||||
sub: Array<string>
|
sub: Array<string>
|
||||||
dir: string,
|
dir: string,
|
||||||
hardsub: boolean,
|
hardsub: boolean,
|
||||||
|
status: 'waiting' | 'preparing' | 'downloading' | 'merging' | 'completed' | 'failed'
|
||||||
}
|
}
|
||||||
|
|
||||||
const Account: ModelDefined<AccountAttributes, AccountCreateAttributes> = sequelize.define('Accounts', {
|
const Account: ModelDefined<AccountAttributes, AccountCreateAttributes> = sequelize.define('Accounts', {
|
||||||
@ -86,7 +85,6 @@ const Playlist: ModelDefined<PlaylistAttributes, PlaylistCreateAttributes> = seq
|
|||||||
status: {
|
status: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
defaultValue: 'waiting'
|
|
||||||
},
|
},
|
||||||
media: {
|
media: {
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
@ -108,16 +106,6 @@ const Playlist: ModelDefined<PlaylistAttributes, PlaylistCreateAttributes> = seq
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
type: DataTypes.BOOLEAN
|
type: DataTypes.BOOLEAN
|
||||||
},
|
},
|
||||||
partsdownloaded: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.BOOLEAN,
|
|
||||||
defaultValue: 0
|
|
||||||
},
|
|
||||||
partsleft: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.BOOLEAN,
|
|
||||||
defaultValue: 0
|
|
||||||
},
|
|
||||||
failedreason: {
|
failedreason: {
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { FastifyReply, FastifyRequest } from 'fastify'
|
import type { FastifyReply, FastifyRequest } from 'fastify'
|
||||||
import { crunchyLogin, checkIfLoggedInCR, safeLoginData, addEpisodeToPlaylist, getPlaylist } from './crunchyroll.service'
|
import { crunchyLogin, checkIfLoggedInCR, safeLoginData, addEpisodeToPlaylist, getPlaylist, getDownloading, deletePlaylist } from './crunchyroll.service'
|
||||||
import { dialog } from 'electron'
|
import { dialog } from 'electron'
|
||||||
import { messageBox } from '../../../electron/background'
|
import { messageBox } from '../../../electron/background'
|
||||||
import { CrunchyEpisodes, CrunchySeason } from '../../types/crunchyroll'
|
import { CrunchyEpisodes, CrunchySeason } from '../../types/crunchyroll'
|
||||||
@ -76,12 +76,22 @@ export async function addPlaylistController(
|
|||||||
const body = request.body;
|
const body = request.body;
|
||||||
|
|
||||||
for (const e of body.episodes) {
|
for (const e of body.episodes) {
|
||||||
await addEpisodeToPlaylist(e, body.subs, body.dubs, body.dir, body.hardsub)
|
await addEpisodeToPlaylist(e, body.subs, body.dubs, body.dir, body.hardsub, "waiting")
|
||||||
}
|
}
|
||||||
|
|
||||||
return reply.code(201).send()
|
return reply.code(201).send()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteCompletePlaylistController(
|
||||||
|
request: FastifyRequest,
|
||||||
|
reply: FastifyReply
|
||||||
|
) {
|
||||||
|
|
||||||
|
await deletePlaylist()
|
||||||
|
|
||||||
|
return reply.code(200).send()
|
||||||
|
}
|
||||||
|
|
||||||
export async function getPlaylistController(
|
export async function getPlaylistController(
|
||||||
request: FastifyRequest,
|
request: FastifyRequest,
|
||||||
reply: FastifyReply
|
reply: FastifyReply
|
||||||
@ -89,6 +99,20 @@ export async function getPlaylistController(
|
|||||||
|
|
||||||
const playlist = await getPlaylist()
|
const playlist = await getPlaylist()
|
||||||
|
|
||||||
|
for (const v of playlist) {
|
||||||
|
if (v.dataValues.status === 'downloading') {
|
||||||
|
const found = await getDownloading(v.dataValues.id)
|
||||||
|
if (found) {
|
||||||
|
(v as any).dataValues = {
|
||||||
|
...v.dataValues,
|
||||||
|
partsleft: found.partsToDownload,
|
||||||
|
partsdownloaded: found.downloadedParts,
|
||||||
|
downloadspeed: found.downloadSpeed.toFixed(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return reply.code(200).send(playlist.reverse())
|
return reply.code(200).send(playlist.reverse())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { FastifyInstance } from 'fastify'
|
import type { FastifyInstance } from 'fastify'
|
||||||
import { addPlaylistController, checkLoginController, getPlaylistController, loginController, loginLoginController } from './crunchyroll.controller'
|
import { addPlaylistController, checkLoginController, deleteCompletePlaylistController, getPlaylistController, loginController, loginLoginController } from './crunchyroll.controller'
|
||||||
|
|
||||||
async function crunchyrollRoutes(server: FastifyInstance) {
|
async function crunchyrollRoutes(server: FastifyInstance) {
|
||||||
server.post(
|
server.post(
|
||||||
@ -72,6 +72,21 @@ async function crunchyrollRoutes(server: FastifyInstance) {
|
|||||||
},
|
},
|
||||||
getPlaylistController
|
getPlaylistController
|
||||||
)
|
)
|
||||||
|
|
||||||
|
server.delete(
|
||||||
|
'/playlist',
|
||||||
|
{
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
'4xx': {
|
||||||
|
error: { type: 'string' },
|
||||||
|
message: { type: 'string' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteCompletePlaylistController
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default crunchyrollRoutes
|
export default crunchyrollRoutes
|
||||||
|
@ -131,13 +131,21 @@ export async function safeLoginData(user: string, password: string, service: str
|
|||||||
return login?.get()
|
return login?.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addEpisodeToPlaylist(e: CrunchyEpisode, s: Array<string>, d: Array<string>, dir: string, hardsub: boolean) {
|
export async function addEpisodeToPlaylist(
|
||||||
|
e: CrunchyEpisode,
|
||||||
|
s: Array<string>,
|
||||||
|
d: Array<string>,
|
||||||
|
dir: string,
|
||||||
|
hardsub: boolean,
|
||||||
|
status: 'waiting' | 'preparing' | 'downloading' | 'merging' | 'completed' | 'failed'
|
||||||
|
) {
|
||||||
const episode = await Playlist.create({
|
const episode = await Playlist.create({
|
||||||
media: e,
|
media: e,
|
||||||
sub: s,
|
sub: s,
|
||||||
dub: d,
|
dub: d,
|
||||||
dir: dir,
|
dir: dir,
|
||||||
hardsub: hardsub
|
hardsub: hardsub,
|
||||||
|
status
|
||||||
})
|
})
|
||||||
|
|
||||||
return episode.get()
|
return episode.get()
|
||||||
@ -149,46 +157,46 @@ export async function getPlaylist() {
|
|||||||
return episodes
|
return episodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deletePlaylist() {
|
||||||
|
await Playlist.truncate()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDownloading(id: number) {
|
||||||
|
const found = downloading.find((i) => i.id === id)
|
||||||
|
|
||||||
|
if (found) return found
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export async function updatePlaylistByID(id: number, status: 'waiting' | 'preparing' | 'downloading' | 'completed' | 'merging' | 'failed') {
|
export async function updatePlaylistByID(id: number, status: 'waiting' | 'preparing' | 'downloading' | 'completed' | 'merging' | 'failed') {
|
||||||
await Playlist.update({ status: status }, { where: { id: id } })
|
await Playlist.update({ status: status }, { where: { id: id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updatePlaylistToDownloadPartsByID(id: number, parts: number) {
|
var isDownloading: number = 0
|
||||||
await Playlist.update({ partsleft: parts }, { where: { id: id } })
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
async function checkPlaylists() {
|
||||||
const episodes = await Playlist.findAll({ where: { status: 'waiting' } })
|
const eps = await Playlist.findAll({ where: { status: 'waiting' } })
|
||||||
|
|
||||||
for (const e of episodes) {
|
for (const e of eps) {
|
||||||
await updatePlaylistByID(e.dataValues.id, 'preparing')
|
if (isDownloading < 2 && e.dataValues.status === 'waiting') {
|
||||||
await downloadPlaylist(
|
updatePlaylistByID(e.dataValues.id, 'preparing')
|
||||||
|
isDownloading++
|
||||||
|
downloadPlaylist(
|
||||||
e.dataValues.media.id,
|
e.dataValues.media.id,
|
||||||
(e as any).dataValues.dub.map((s: { locale: any }) => s.locale),
|
(e as any).dataValues.dub.map((s: { locale: any }) => s.locale),
|
||||||
(e as any).dataValues.sub.map((s: { locale: any }) => s.locale),
|
(e as any).dataValues.sub.map((s: { locale: any }) => s.locale),
|
||||||
e.dataValues.hardsub,
|
e.dataValues.hardsub,
|
||||||
e.dataValues.id
|
e.dataValues.id,
|
||||||
|
e.dataValues.media.series_title,
|
||||||
|
e.dataValues.media.season_number,
|
||||||
|
e.dataValues.media.episode_number
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cron.schedule('*/2 * * * * *', () => {
|
cron.schedule('*/2 * * * * *', () => {
|
||||||
checkPlaylists()
|
checkPlaylists()
|
||||||
@ -249,6 +257,23 @@ async function createFolder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createFolderName(name: string) {
|
||||||
|
const folderPath = path.join(app.getPath('documents'), name)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.promises.access(folderPath)
|
||||||
|
return folderPath
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
await fs.promises.mkdir(folderPath, { recursive: true })
|
||||||
|
return folderPath
|
||||||
|
} catch (mkdirError) {
|
||||||
|
console.error('Error creating season folder:', mkdirError)
|
||||||
|
throw mkdirError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteFolder(folderPath: string) {
|
async function deleteFolder(folderPath: string) {
|
||||||
fs.rmSync(folderPath, { recursive: true, force: true })
|
fs.rmSync(folderPath, { recursive: true, force: true })
|
||||||
}
|
}
|
||||||
@ -285,11 +310,24 @@ export async function crunchyGetPlaylistMPD(q: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Array<string>, hardsub: boolean, downloadID: number) {
|
var downloading: Array<{
|
||||||
var playlist = await crunchyGetPlaylist(e)
|
id: number
|
||||||
|
downloadedParts: number
|
||||||
|
partsToDownload: number
|
||||||
|
downloadSpeed: number
|
||||||
|
}> = []
|
||||||
|
|
||||||
console.log(dubs)
|
export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Array<string>, hardsub: boolean, downloadID: number, name: string, season: number, episode: number) {
|
||||||
console.log(subs)
|
downloading.push({
|
||||||
|
id: downloadID,
|
||||||
|
downloadedParts: 0,
|
||||||
|
partsToDownload: 0,
|
||||||
|
downloadSpeed: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
await updatePlaylistByID(downloadID, 'downloading')
|
||||||
|
|
||||||
|
var playlist = await crunchyGetPlaylist(e)
|
||||||
|
|
||||||
if (!playlist) {
|
if (!playlist) {
|
||||||
console.log('Playlist not found')
|
console.log('Playlist not found')
|
||||||
@ -312,6 +350,10 @@ export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Arr
|
|||||||
|
|
||||||
const audioFolder = await createFolder()
|
const audioFolder = await createFolder()
|
||||||
|
|
||||||
|
const videoFolder = await createFolder()
|
||||||
|
|
||||||
|
const seasonFolder = await createFolderName(`${name.replace(/[/\\?%*:|"<>]/g, '')} Season ${season}`)
|
||||||
|
|
||||||
const dubDownloadList: Array<{
|
const dubDownloadList: Array<{
|
||||||
audio_locale: string
|
audio_locale: string
|
||||||
guid: string
|
guid: string
|
||||||
@ -443,20 +485,6 @@ export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Arr
|
|||||||
|
|
||||||
var mdp = await crunchyGetPlaylistMPD(play.url)
|
var mdp = await crunchyGetPlaylistMPD(play.url)
|
||||||
|
|
||||||
// if (hardsub) {
|
|
||||||
// const hardsuburl = play.hardSubs.find(h=> h.hlang === subs[0])?.url
|
|
||||||
|
|
||||||
// if (!hardsuburl) {
|
|
||||||
// console.error('No Hardsub stream found')
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// mdp = await crunchyGetPlaylistMPD(hardsuburl)
|
|
||||||
// console.error('Hardsub stream found')
|
|
||||||
// } else {
|
|
||||||
// console.error('Hardsub is false')
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!mdp) return
|
if (!mdp) return
|
||||||
|
|
||||||
var hq = mdp.playlists.find((i) => i.attributes.RESOLUTION?.width === 1920)
|
var hq = mdp.playlists.find((i) => i.attributes.RESOLUTION?.width === 1920)
|
||||||
@ -477,11 +505,15 @@ export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Arr
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
await updatePlaylistByID(downloadID, 'downloading')
|
// await updatePlaylistToDownloadPartsByID(downloadID, p.length)
|
||||||
|
|
||||||
await updatePlaylistToDownloadPartsByID(downloadID, p.length)
|
const dn = downloading.find((i) => i.id === downloadID)
|
||||||
|
|
||||||
const file = await downloadParts(p, downloadID)
|
if (dn) {
|
||||||
|
dn.partsToDownload = p.length
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = await downloadParts(p, downloadID, videoFolder)
|
||||||
|
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
@ -490,14 +522,13 @@ export async function downloadPlaylist(e: string, dubs: Array<string>, subs: Arr
|
|||||||
|
|
||||||
if (!audios) return
|
if (!audios) return
|
||||||
|
|
||||||
await updatePlaylistByID(downloadID, 'merging')
|
await mergeFile(file as string, audios, subss, String(playlist.assetId), seasonFolder, `${name.replace(/[/\\?%*:|"<>]/g, '')} Season ${season} Episode ${episode}`)
|
||||||
|
|
||||||
await mergeFile(file as string, audios, subss, String(playlist.assetId))
|
await updatePlaylistByID(downloadID, 'completed')
|
||||||
|
|
||||||
await deleteFolder(subFolder)
|
await deleteFolder(subFolder)
|
||||||
await deleteFolder(audioFolder)
|
await deleteFolder(audioFolder)
|
||||||
|
await deleteFolder(videoFolder)
|
||||||
await updatePlaylistByID(downloadID, 'completed')
|
|
||||||
|
|
||||||
return playlist
|
return playlist
|
||||||
}
|
}
|
||||||
@ -534,9 +565,12 @@ async function fetchAndPipe(url: string, stream: fs.WriteStream, index: number)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadParts(parts: { filename: string; url: string }[], downloadID: number) {
|
async function downloadParts(parts: { filename: string; url: string }[], downloadID: number, dir: string) {
|
||||||
var partsdownloaded = 0
|
|
||||||
const path = await createFolder()
|
const path = await createFolder()
|
||||||
|
const dn = downloading.find((i) => i.id === downloadID)
|
||||||
|
|
||||||
|
let totalDownloadedBytes = 0
|
||||||
|
let startTime = Date.now()
|
||||||
|
|
||||||
for (const [index, part] of parts.entries()) {
|
for (const [index, part] of parts.entries()) {
|
||||||
let success = false
|
let success = false
|
||||||
@ -544,10 +578,25 @@ async function downloadParts(parts: { filename: string; url: string }[], downloa
|
|||||||
try {
|
try {
|
||||||
const stream = fs.createWriteStream(`${path}/${part.filename}`)
|
const stream = fs.createWriteStream(`${path}/${part.filename}`)
|
||||||
const { body } = await fetch(part.url)
|
const { body } = await fetch(part.url)
|
||||||
await finished(Readable.fromWeb(body as any).pipe(stream))
|
|
||||||
|
const readableStream = Readable.from(body as any)
|
||||||
|
let partDownloadedBytes = 0
|
||||||
|
readableStream.on('data', (chunk) => {
|
||||||
|
partDownloadedBytes += chunk.length
|
||||||
|
totalDownloadedBytes += chunk.length
|
||||||
|
})
|
||||||
|
|
||||||
|
await finished(readableStream.pipe(stream))
|
||||||
|
|
||||||
console.log(`Fragment ${index + 1} downloaded`)
|
console.log(`Fragment ${index + 1} downloaded`)
|
||||||
partsdownloaded++
|
|
||||||
updatePlaylistToDownloadedPartsByID(downloadID, partsdownloaded, parts.length)
|
if (dn) {
|
||||||
|
dn.downloadedParts++
|
||||||
|
const endTime = Date.now()
|
||||||
|
const durationInSeconds = (endTime - startTime) / 1000
|
||||||
|
dn.downloadSpeed = totalDownloadedBytes / 1024 / 1024 / durationInSeconds
|
||||||
|
}
|
||||||
|
|
||||||
success = true
|
success = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error occurred during download of fragment ${index + 1}:`, error)
|
console.error(`Error occurred during download of fragment ${index + 1}:`, error)
|
||||||
@ -557,7 +606,7 @@ async function downloadParts(parts: { filename: string; url: string }[], downloa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await mergeParts(parts, path)
|
return await mergeParts(parts, downloadID, path, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadSub(
|
async function downloadSub(
|
||||||
@ -631,12 +680,15 @@ async function concatenateTSFiles(inputFiles: Array<string>, outputFile: string)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mergeParts(parts: { filename: string; url: string }[], tmp: string) {
|
async function mergeParts(parts: { filename: string; url: string }[], downloadID: number, tmp: string, dir: string) {
|
||||||
const tempname = (Math.random() + 1).toString(36).substring(2)
|
const tempname = (Math.random() + 1).toString(36).substring(2)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const list: Array<string> = []
|
const list: Array<string> = []
|
||||||
|
|
||||||
|
await updatePlaylistByID(downloadID, 'merging')
|
||||||
|
isDownloading--
|
||||||
|
|
||||||
for (const [index, part] of parts.entries()) {
|
for (const [index, part] of parts.entries()) {
|
||||||
list.push(`${tmp}/${part.filename}`)
|
list.push(`${tmp}/${part.filename}`)
|
||||||
}
|
}
|
||||||
@ -648,11 +700,11 @@ async function mergeParts(parts: { filename: string; url: string }[], tmp: strin
|
|||||||
Ffmpeg()
|
Ffmpeg()
|
||||||
.input(concatenatedFile)
|
.input(concatenatedFile)
|
||||||
.outputOptions('-c copy')
|
.outputOptions('-c copy')
|
||||||
.save(app.getPath('documents') + `/${tempname}.mp4`)
|
.save(dir + `/${tempname}.mp4`)
|
||||||
.on('end', async () => {
|
.on('end', async () => {
|
||||||
console.log('Merging finished')
|
console.log('Merging finished')
|
||||||
await deleteFolder(tmp)
|
await deleteFolder(tmp)
|
||||||
return resolve(app.getPath('documents') + `/${tempname}.mp4`)
|
return resolve(dir + `/${tempname}.mp4`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -688,7 +740,7 @@ async function mergePartsAudio(parts: { filename: string; url: string }[], tmp:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mergeFile(video: string, audios: Array<string>, subs: Array<string>, name: string) {
|
async function mergeFile(video: string, audios: Array<string>, subs: Array<string>, name: string, path: string, filename: string) {
|
||||||
const locales: Array<{
|
const locales: Array<{
|
||||||
locale: string
|
locale: string
|
||||||
name: string
|
name: string
|
||||||
@ -796,7 +848,7 @@ async function mergeFile(video: string, audios: Array<string>, subs: Array<strin
|
|||||||
|
|
||||||
output
|
output
|
||||||
.addOptions(options)
|
.addOptions(options)
|
||||||
.saveToFile(app.getPath('documents') + `/${name}.mkv`)
|
.saveToFile(path + `/${filename}.mkv`)
|
||||||
.on('error', (error) => {
|
.on('error', (error) => {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
reject(error)
|
reject(error)
|
||||||
|
@ -65,7 +65,6 @@ function createWindow() {
|
|||||||
if (singleInstance(app, mainWindow)) return
|
if (singleInstance(app, mainWindow)) return
|
||||||
|
|
||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
!isProduction &&
|
|
||||||
mainWindow.webContents.openDevTools({
|
mainWindow.webContents.openDevTools({
|
||||||
mode: 'bottom'
|
mode: 'bottom'
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user