Skip to content

Commit 361dc09

Browse files
authored
feat: cli (#6)
1 parent 95df8b0 commit 361dc09

File tree

6 files changed

+244
-55
lines changed

6 files changed

+244
-55
lines changed

package-lock.json

Lines changed: 109 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"name": "bili-pc-mp4",
33
"version": "0.0.1",
44
"description": "MP4 convertor for videos downloaded by the Bilibili pc application.",
5-
"main": "index.js",
5+
"main": "dist/index.js",
6+
"bin": "dist/cli/index.js",
67
"scripts": {
78
"lint": "eslint --ext .ts,.tsx ./src",
89
"build": "npm run lint && tsc",
@@ -40,8 +41,10 @@
4041
"typescript": "^5.2.2"
4142
},
4243
"dependencies": {
44+
"@types/args": "^5.0.0",
45+
"args": "^5.0.3",
4346
"eloquent-ffmpeg": "^0.14.0",
4447
"ora": "^5.4.1",
4548
"sanitize-filename": "^1.6.3"
4649
}
47-
}
50+
}

src/cli/commands.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { listVideos, processFolder } from '../core';
2+
3+
interface CliOptions {
4+
input: string;
5+
output?: string;
6+
pageNumber?: boolean;
7+
silence?: boolean;
8+
bufferSize?: string;
9+
}
10+
11+
type CliCommand = (
12+
name?: string,
13+
subs?: string[],
14+
options?: Partial<CliOptions>,
15+
) => Promise<void>;
16+
17+
export const listCommand: CliCommand = async (_, subs = [], options = {}) => {
18+
const folderPath = subs[0] ?? options.input;
19+
20+
if (!folderPath) {
21+
throw new Error('Please specify the video folder path.');
22+
}
23+
await listVideos(folderPath);
24+
};
25+
26+
export const convertCommand: CliCommand = async (
27+
_,
28+
subs = [],
29+
options = {},
30+
) => {
31+
const folderPath = subs[0] ?? options.input;
32+
if (!folderPath) {
33+
throw new Error('Please specify the video folder path.');
34+
}
35+
const { output, pageNumber, silence, bufferSize } = options;
36+
await processFolder(folderPath, {
37+
output,
38+
pageNumber,
39+
silence,
40+
bufferSize: bufferSize ? parseInt(bufferSize) : undefined,
41+
});
42+
};

src/cli/index.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env node
2+
3+
import args from 'args';
4+
import { convertCommand, listCommand } from './commands';
5+
6+
args
7+
.option(
8+
'input',
9+
'The folder that contains videos downloaded by the Bilibili desktop app.',
10+
)
11+
.option(
12+
'output',
13+
'Optional: The path where the output will be saved, default to the current folder.',
14+
)
15+
.option(
16+
'page-number',
17+
'Optional: Whether to include the indexed page number in the output filename.',
18+
)
19+
.option(
20+
'silence',
21+
'Optional: Whether to suppress console output during processing.',
22+
)
23+
.option(
24+
'bufferSize',
25+
'Optional: The size of the buffer chunk when processing video files, default to 64MB.',
26+
)
27+
.command('convert', 'Convert downloaded videos.', convertCommand as any)
28+
.command('list', 'List all videos.', listCommand as any)
29+
.example(
30+
'npx bili-pc-mp4 ~/Movies/bilibili',
31+
'A simple command to convert all videos to mp4 and save to the current folder.',
32+
)
33+
.example(
34+
'npx bili-pc-mp4 convert -i ~/Movies/bilibili -o ~/Movies/converted -p',
35+
'A complete example that converts all videos and save to specified location, naming videos with the page number.',
36+
)
37+
.example('npx bili-pc-mp4 list ~/Movies/bilibili', 'List all videos.');
38+
39+
const flags = args.parse(process.argv);
40+
const subs = args.sub;
41+
42+
if (subs.length === 1) {
43+
convertCommand('convert', subs, flags);
44+
}

src/core/index.ts

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,53 +7,6 @@ import { createFolder } from '../lib';
77
import sanitize from 'sanitize-filename';
88
import ora from 'ora';
99

10-
/**
11-
* Represents the structure of a task.
12-
* @interface Task
13-
* @property {string} groupTitle - The group title of the task.
14-
* @property {string} title - The title of the task.
15-
* @property {number} p - The priority of the task.
16-
* @property {string} uname - The username associated with the task.
17-
* @property {string} status - The status of the task.
18-
* @property {string[]} videoFiles - An array of video file names.
19-
* @property {string[]} danmuFiles - An array of danmu file names.
20-
* @property {string[]} dirPath - The path to the folder.
21-
*/
22-
interface Task {
23-
id: string;
24-
groupTitle?: string;
25-
title?: string;
26-
p?: number;
27-
uname?: string;
28-
status?: 'completed' | string;
29-
videoFiles: string[];
30-
danmuFiles: string[];
31-
dirPath: string;
32-
}
33-
34-
/**
35-
* Options for processing a task.
36-
* @interface TaskOptions
37-
* @property {string} [outputPath] - The path where the output will be saved.
38-
* @property {boolean} [indexedP] - Whether to include the indexed priority in the output filename.
39-
* @property {boolean} [silence] - Whether to suppress console output during processing.
40-
* @property {boolean} [withDanmu] - Whether to include danmu (WIP: danmu implementation) in the processing.
41-
* @property {number} [bufferSize] - The size of the buffer used during decryption.
42-
*/
43-
interface TaskOptions {
44-
outputPath?: string;
45-
indexedP?: boolean;
46-
silence?: boolean;
47-
withDanmu?: boolean;
48-
bufferSize?: number;
49-
}
50-
51-
interface TaskOptions {
52-
outputPath?: string;
53-
indexedP?: boolean;
54-
withDanmu?: boolean; // WIP: danmu implementation
55-
}
56-
5710
const TMPDIR = join(__dirname, '..', 'tmp');
5811

5912
/**
@@ -186,19 +139,16 @@ async function processVideo(
186139
*/
187140
async function processTask(task: Task, options: TaskOptions = {}) {
188141
const { id, groupTitle, title, p, videoFiles, dirPath } = task;
189-
const { outputPath = '.', indexedP, silence } = options;
142+
const { output = '.', pageNumber, silence } = options;
190143
const spinner = ora();
191144

192-
const outputDirPath = join(
193-
outputPath,
194-
groupTitle ? sanitize(groupTitle) : id,
195-
);
145+
const outputDirPath = join(output, groupTitle ? sanitize(groupTitle) : id);
196146
await createFolder(outputDirPath);
197147

198148
if (!silence) {
199149
spinner.start(`Decrypting and combining ${title ?? id}...`);
200150
}
201-
const filename = `${indexedP ? p : ''}${title ? sanitize(title) : id}.mp4`;
151+
const filename = `${pageNumber ? p : ''}${title ? sanitize(title) : id}.mp4`;
202152
const outputFilePath = join(outputDirPath, filename);
203153
await processVideo(
204154
videoFiles.map(e => join(dirPath, e)),

src/global.d.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,44 @@
1+
/**
2+
* Represents the structure of a task.
3+
* @interface Task
4+
* @property {string} groupTitle - The group title of the task.
5+
* @property {string} title - The title of the task.
6+
* @property {number} p - The priority of the task.
7+
* @property {string} uname - The username associated with the task.
8+
* @property {string} status - The status of the task.
9+
* @property {string[]} videoFiles - An array of video file names.
10+
* @property {string[]} danmuFiles - An array of danmu file names.
11+
* @property {string[]} dirPath - The path to the folder.
12+
*/
13+
interface Task {
14+
id: string;
15+
groupTitle?: string;
16+
title?: string;
17+
p?: number;
18+
uname?: string;
19+
status?: 'completed' | string;
20+
videoFiles: string[];
21+
danmuFiles: string[];
22+
dirPath: string;
23+
}
24+
25+
/**
26+
* Options for processing a task.
27+
* @interface TaskOptions
28+
* @property {string} [output] - The path where the output will be saved.
29+
* @property {boolean} [pageNumber] - Whether to include the indexed page number in the output filename.
30+
* @property {boolean} [silence] - Whether to suppress console output during processing.
31+
* @property {boolean} [withDanmu] - Whether to include danmu (WIP: danmu implementation) in the processing.
32+
* @property {number} [bufferSize] - The size of the buffer chunk when processing video files.
33+
*/
34+
interface TaskOptions {
35+
output?: string;
36+
pageNumber?: boolean;
37+
silence?: boolean;
38+
withDanmu?: boolean;
39+
bufferSize?: number;
40+
}
41+
142
interface VideoInfo {
243
type: string;
344
codecid: number;

0 commit comments

Comments
 (0)