Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom script and more #30

Merged
merged 17 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
/.idea/inspectionProfiles
/.idea/sonarlint/
/.idea/
/gui/node_modules/
/gui/src-tauri/target/
/gui/dist
/gui/.vite
node_modules
vendor
2 changes: 1 addition & 1 deletion gen_pack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export async function generatePack(opt: ModOptions) {
folder = await fsToFolder(storyPath, true);

const metadata: Metadata = await getMetadata(storyPath, opt);
const pack = folderToPack(folder, metadata);
const pack = await folderToPack(folder, metadata);
const nightModeAudioItemName = getNightModeAudioItem(folder);
const serializedPack = await serializePack(pack, opt, {
autoNextStoryTransition: opt.autoNextStoryTransition,
Expand Down
22 changes: 10 additions & 12 deletions generate/gen_image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,22 @@ import { getConvertCommand } from "../utils/external_commands.ts";
export async function generateImage(
title: string,
outputPath: string,
fontName: string,
fontName: string,
extraArgs = {},
) {
console.log(green(`Generate image to ${outputPath}`));

const args: Record<string, string> = Object.assign({
background:'black',
fill:'white',
gravity:'center',
size:'320x240',
font:fontName,
}, extraArgs)
const convertCommand = await getConvertCommand();
const cmd = [
convertCommand[0],
...(convertCommand.splice(1)),
"-background",
"black",
"-fill",
"white",
"-gravity",
"center",
"-size",
"320x240",
"-font",
fontName,
...Object.keys(args).map(k=>([`-${k}`, args[k] ])).flat(),
`caption:${title}`,
outputPath,
];
Expand Down
19 changes: 15 additions & 4 deletions generate/gen_missing_items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { generateImage } from "./gen_image.ts";
import { generateAudio } from "./gen_audio.ts";
import i18next from "https://deno.land/x/[email protected]/index.js";
import { join } from "@std/path";
import { exists } from "@std/fs";

import type { ModOptions } from "../types.ts";

Expand All @@ -35,7 +36,7 @@ export async function genMissingItems(
if (!opt.skipImageItemGen || !opt.skipAudioItemGen) {
await checkRunPermission();
if (!opt.skipImageItemGen && !getFolderImageItem(folder)) {
if (isRoot && opt.useThumbnailAsRootImage) {
if (isRoot && opt.useThumbnailAsRootImage && await exists(join(rootpath, "thumbnail.png"))) {
await Deno.copyFile(
join(rootpath, "thumbnail.png"),
`${rootpath}/0-item.png`,
Expand All @@ -58,7 +59,7 @@ export async function genMissingItems(
}
if (!opt.skipAudioItemGen && isRoot && !getNightModeAudioItem(folder)) {
await generateAudio(
i18next.t("NightModeTransition"),
opt.i18n?.['NightModeTransition'] || i18next.t("NightModeTransition"),
`${rootpath}/0-night-mode.wav`,
lang,
opt,
Expand All @@ -75,16 +76,26 @@ export async function genMissingItems(
opt,
);
} else if (isStory(file)) {
let title = getTitle(getNameWithoutExt(file.name));
const metadataPath =join(rootpath, getNameWithoutExt(file.name)+ "-metadata.json");
if (await exists(metadataPath)) {
try {
const metadata =JSON.parse(await Deno.readTextFile(metadataPath))
title = metadata?.title ?? title
} catch (error) {
console.error(`error reading json metadata: ${metadataPath}`, error);
}
}
if (!opt.skipImageItemGen && !getFileImageItem(file, folder)) {
await generateImage(
getTitle(getNameWithoutExt(file.name)),
title,
`${rootpath}/${getNameWithoutExt(file.name)}-generated.item.png`,
opt.imageItemGenFont,
);
}
if (!opt.skipAudioItemGen && !getFileAudioItem(file, folder)) {
await generateAudio(
getTitle(getNameWithoutExt(file.name)),
title,
`${rootpath}/${getNameWithoutExt(file.name)}-generated.item.wav`,
lang,
opt,
Expand Down
111 changes: 68 additions & 43 deletions generate/rss_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { blue, green } from "@std/fmt/colors";
import { exists } from "@std/fs";
import { parse } from "@libs/xml";
import i18next from "https://deno.land/x/[email protected]/index.js";
import { sprintf } from "@std/fmt/printf";
import type { File, Metadata } from "../serialize/serialize-types.ts";
import type { ModOptions } from "../types.ts";
import { convertImage } from "./gen_image.ts";
Expand All @@ -28,6 +29,9 @@ export type Rss = {
"@href"?: string;
};
};
"itunes:image"?: {
"@href": string;
};
item: RssItem[];
};
export type RssItem = {
Expand All @@ -39,26 +43,29 @@ export type RssItem = {
"podcast:season"?: number;
"podcast:episode"?: number;
"itunes:season"?: number;
"itunes:subtitle"?: string;
"itunes:episode"?: number;
"itunes:duration"?: string;
"itunes:image"?: {
"@href": string;
};
};
export type FolderWithUrl = {
export type FolderWithUrlOrData = {
name: string;
files: (FolderWithUrl | FileWithUrl)[];
files: (FolderWithUrlOrData | FileWithUrlOrData)[];
metadata?: Metadata;
thumbnailUrl?: string;
};
export type FileWithUrl = File & {
url: string;
export type FileWithUrlOrData = File & {
url?: string;
} & {
data?: string | Uint8Array | object;
};

async function getFolderWithUrlFromRssUrl(
url: string,
opt: ModOptions,
): Promise<FolderWithUrl[]> {
): Promise<FolderWithUrlOrData[]> {
console.log(green(`→ url = ${url}`));

const resp = await fetch(url);
Expand Down Expand Up @@ -101,13 +108,13 @@ async function getFolderWithUrlFromRssUrl(
rssItems = sorted.map((i) => i[1]);
}
const rssName = convertToValidFilename(rss.title);
const imgUrl = rss.image?.url || rss.itunes?.image?.["@href"] || "";
const fss: FolderWithUrl[] = rssItems.map((items, index) => {
const imgUrl = rss.image?.url || rss.itunes?.image?.["@href"] || rss["itunes:image"]?.["@href"] || "";
const fss: FolderWithUrlOrData[] = rssItems.map((items, index) => {
const name = rssItems.length > 1
? `${rssName} ${
seasonIds[index] === "0"
? i18next.t("special")
: i18next.t("season") + " " + seasonIds[index]
? opt.i18n?.['special'] || i18next.t("special")
: sprintf(opt.i18n?.['season'] || i18next.t("season"), seasonIds[index])
}`
: rssName;
return {
Expand All @@ -116,15 +123,15 @@ async function getFolderWithUrlFromRssUrl(
thumbnailUrl: opt.rssUseImageAsThumbnail
? items.find((item) => item["itunes:image"]?.["@href"])
?.["itunes:image"]?.["@href"]
: undefined,
: imgUrl,
metadata: { ...metadata, title: name },
};
});
for (let index = 0; index < fss.length; index++) {
const fs = fss[index];
if (imgUrl) {
fs.files.push({
name: `0-item-to-resize.${getExtension(imgUrl)}`,
name: opt.skipImageConvert ? `0-item.${getExtension(imgUrl)}` : `0-item-to-resize.${getExtension(imgUrl)}`,
url: imgUrl,
sha1: "",
});
Expand All @@ -134,17 +141,17 @@ async function getFolderWithUrlFromRssUrl(
);
console.log(blue(`→ ${items.length} items`));
if (items.length <= opt.rssSplitLength) {
fs.files.push(getFolderOfStories(items, !!opt.skipRssImageDl));
fs.files.push(await getFolderOfStories(items, opt));
} else {
fs.files.push(getFolderParts(items, !!opt.skipRssImageDl));
fs.files.push(await getFolderParts(items, opt));
}
}

return fss;
}

export function getItemFileName(item: RssItem) {
const title = convertToValidFilename(item.title!);
export function getItemFileName(item: RssItem, opt: ModOptions) {
const title = convertToValidFilename((opt.rssUseSubtitleAsTitle && item['itunes:subtitle'] || item.title)!);
return (
new Date(item.pubDate).getTime() +
` - ${title}.${getExtension(item.enclosure["@url"])}`
Expand All @@ -157,22 +164,26 @@ export function fixUrl(url: string): string {
.replace(/^.*http:\/\//g, "http://");
}

function getFolderOfStories(
async function getFolderOfStories(
items: RssItem[],
skipRssImageDl: boolean,
): FolderWithUrl {
opt: ModOptions,
): Promise<FolderWithUrlOrData> {
return {
name: i18next.t("storyQuestion"),
files: items.flatMap((item) => {
name: opt.i18n?.['storyQuestion'] || i18next.t("storyQuestion"),
files: (await Promise.all(items.map(async (item) => {
const itemFiles = [{
name: getItemFileName(item),
name: getItemFileName(item, opt),
url: fixUrl(item.enclosure["@url"]),
sha1: "",
}, {
name: getNameWithoutExt(getItemFileName(item, opt)) + '-metadata.json',
data:{...item, title: (opt.rssUseSubtitleAsTitle && item['itunes:subtitle']) || item.title},
sha1: "",
}];
const imageUrl = item["itunes:image"]?.["@href"];
if (!skipRssImageDl && imageUrl) {
const imageUrl = opt.customModule?.fetchRssItemImage ? await opt.customModule?.fetchRssItemImage(item, opt): item["itunes:image"]?.["@href"];
if (!opt.skipRssImageDl && imageUrl) {
itemFiles.push({
name: `${getNameWithoutExt(getItemFileName(item))}.item.${
name: `${getNameWithoutExt(getItemFileName(item, opt))}.item.${
getExtension(imageUrl)
}`,
url: imageUrl,
Expand All @@ -181,14 +192,14 @@ function getFolderOfStories(
}

return itemFiles;
}),
}))).flat(),
};
}

function getFolderParts(
async function getFolderParts(
items: RssItem[],
skipRssImageDl: boolean,
): FolderWithUrl {
opt: ModOptions,
): Promise<FolderWithUrlOrData> {
const partCount = Math.ceil(items.length / 10);
const parts: RssItem[][] = [];
for (let i = 0; i < partCount; i++) {
Expand All @@ -199,34 +210,48 @@ function getFolderParts(
}

return {
name: i18next.t("partQuestion"),
files: parts.map((part, index) => ({
name: `${i18next.t("partTitle")} ${index + 1}`,
files: [getFolderOfStories(part, skipRssImageDl)],
})),
name: opt.i18n?.['partQuestion'] || i18next.t("partQuestion"),
files: await Promise.all(parts.map(async (part, index) => ({
name: sprintf( i18next.t("partTitle"),index + 1),
files: [await getFolderOfStories(part, opt)],
}))),
};
}

async function writeFolderWithUrl(folder: FolderWithUrl, parentPath: string) {
async function writeFolderWithUrl(folder: FolderWithUrlOrData, parentPath: string) {
const path = join(parentPath, folder.name);
await Deno.mkdir(path, { recursive: true });
for (const file of folder.files) {
isFolder(file)
? await writeFolderWithUrl(file as FolderWithUrl, path)
: await writeFileWithUrl(file as FileWithUrl, path);
? await writeFolderWithUrl(file as FolderWithUrlOrData, path)
: await writeFileWithUrl(file as FileWithUrlOrData, path);
}
}

async function writeFileWithUrl(fileWithUrl: FileWithUrl, parentPath: string) {
const filePath = join(parentPath, fileWithUrl.name);
console.log(blue(`Download ${fileWithUrl.url}\n → ${filePath}`));
async function writeFileWithUrl(fileWithUrlOrData: FileWithUrlOrData, parentPath: string) {
const filePath = join(parentPath, fileWithUrlOrData.name);
console.log(blue(`Download ${fileWithUrlOrData.url}\n → ${filePath}`));

if (await exists(filePath)) {
console.log(green(` → skip`));
} else {
const resp = await fetch(fileWithUrl.url);
const file = await Deno.open(filePath, { create: true, write: true });
await resp.body?.pipeTo(file.writable);
} else if (fileWithUrlOrData.url) {
if (fileWithUrlOrData.url.startsWith('http')) {
const resp = await fetch(fileWithUrlOrData.url);
const file = await Deno.open(filePath, { create: true, write: true });
await resp.body?.pipeTo(file.writable);
} else {
const file = await Deno.open(filePath, { create: true, write: true });
await (await Deno.open(fileWithUrlOrData.url)).readable.pipeTo(file.writable);
}
} else if (fileWithUrlOrData.data) {
let toWrite = fileWithUrlOrData.data;
if (typeof toWrite === 'object') {
toWrite = JSON.stringify(toWrite);
}
if (typeof toWrite === 'string') {
toWrite = new TextEncoder().encode(toWrite)
}
await Deno.writeFile(filePath, toWrite as Uint8Array);
}
}

Expand Down
Loading
Loading