@opsimathically/vlc_playlist_generator 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +21 -0
- package/README.md +41 -0
- package/dist/index.d.mts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +363 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +334 -0
- package/dist/index.mjs.map +1 -0
- package/markdowns/code_naming_conventions.md +19 -0
- package/markdowns/zod_schemas.md +8 -0
- package/package.json +56 -0
- package/ts-to-zod.config.mjs +17 -0
- package/tsup.config.ts +14 -0
- package/typedoc.json +7 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jason Medeiros
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# VLC Playlist Generator
|
|
2
|
+
|
|
3
|
+
I was spending too much time every day trying to figure out what background noise to have playing while I worked, so I created this project which will just scan a directory, select some random content and shove it in a m3u playlist file. That way you can just queue up whatever random nonsense, let it play, and get to work.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# You can run it as a command line bin.
|
|
7
|
+
npx tsx ./src/index.ts --source-search-directory /home/your_user/Downloads/ \
|
|
8
|
+
--output-playlist-file-destination /tmp/my_playlist.m3u \
|
|
9
|
+
--number-of-results 25
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
// You can import the class as a typescript module.
|
|
14
|
+
import { VLCPlaylistGenerator } from '@opsimathically/vlc_playlist_generator';
|
|
15
|
+
|
|
16
|
+
// create class handle
|
|
17
|
+
const vlc_playlist_generator = new VLCPlaylistGenerator();
|
|
18
|
+
|
|
19
|
+
// generate playlist
|
|
20
|
+
await vlc_playlist_generator.createPlaylist({
|
|
21
|
+
number_of_results: 100,
|
|
22
|
+
output_playlist_file_destination: '/tmp/whatever_random_playlistname.m3u',
|
|
23
|
+
source_search_directory: '/home/your_user/Downloads/'
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @opsimathically/vlc_playlist_generator
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Building from source
|
|
34
|
+
|
|
35
|
+
This package is intended to be run via npm, but if you'd like to build from source,
|
|
36
|
+
clone this repo, enter directory, and run `npm install` for dev dependencies, then run
|
|
37
|
+
`npm run build`.
|
|
38
|
+
|
|
39
|
+
## Reference
|
|
40
|
+
|
|
41
|
+
[See API Reference for documentation](https://github.com/opsimathically/vlc_playlist_generator/tree/main/docs)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type create_playlist_params_t = {
|
|
2
|
+
source_search_directory: string;
|
|
3
|
+
output_playlist_file_destination: string;
|
|
4
|
+
number_of_results: number;
|
|
5
|
+
filename_matching_regular_expression?: RegExp | string;
|
|
6
|
+
};
|
|
7
|
+
declare class VLCPlaylistGenerator {
|
|
8
|
+
private readonly video_extensions;
|
|
9
|
+
constructor(params?: {
|
|
10
|
+
video_extensions?: string[];
|
|
11
|
+
});
|
|
12
|
+
createPlaylist(params: create_playlist_params_t): Promise<string>;
|
|
13
|
+
private validateCreatePlaylistParams;
|
|
14
|
+
private getFilenameMatcher;
|
|
15
|
+
private findVideoFiles;
|
|
16
|
+
private isVideoFile;
|
|
17
|
+
private doesFilenameMatchPattern;
|
|
18
|
+
private buildM3UContents;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { VLCPlaylistGenerator };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type create_playlist_params_t = {
|
|
2
|
+
source_search_directory: string;
|
|
3
|
+
output_playlist_file_destination: string;
|
|
4
|
+
number_of_results: number;
|
|
5
|
+
filename_matching_regular_expression?: RegExp | string;
|
|
6
|
+
};
|
|
7
|
+
declare class VLCPlaylistGenerator {
|
|
8
|
+
private readonly video_extensions;
|
|
9
|
+
constructor(params?: {
|
|
10
|
+
video_extensions?: string[];
|
|
11
|
+
});
|
|
12
|
+
createPlaylist(params: create_playlist_params_t): Promise<string>;
|
|
13
|
+
private validateCreatePlaylistParams;
|
|
14
|
+
private getFilenameMatcher;
|
|
15
|
+
private findVideoFiles;
|
|
16
|
+
private isVideoFile;
|
|
17
|
+
private doesFilenameMatchPattern;
|
|
18
|
+
private buildM3UContents;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { VLCPlaylistGenerator };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
32
|
+
|
|
33
|
+
// src/index.ts
|
|
34
|
+
var index_exports = {};
|
|
35
|
+
__export(index_exports, {
|
|
36
|
+
VLCPlaylistGenerator: () => VLCPlaylistGenerator
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
|
|
40
|
+
// src/classes/vlc_playlist_generator/VLCPlaylistGenerator.class.ts
|
|
41
|
+
var import_promises = require("fs/promises");
|
|
42
|
+
var import_node_path = __toESM(require("path"));
|
|
43
|
+
function ShuffleArray(params) {
|
|
44
|
+
const values_copy = [
|
|
45
|
+
...params.values
|
|
46
|
+
];
|
|
47
|
+
for (let index = values_copy.length - 1; index > 0; index--) {
|
|
48
|
+
const random_index = Math.floor(Math.random() * (index + 1));
|
|
49
|
+
const current_value = values_copy[index];
|
|
50
|
+
values_copy[index] = values_copy[random_index];
|
|
51
|
+
values_copy[random_index] = current_value;
|
|
52
|
+
}
|
|
53
|
+
return values_copy;
|
|
54
|
+
}
|
|
55
|
+
__name(ShuffleArray, "ShuffleArray");
|
|
56
|
+
var _VLCPlaylistGenerator = class _VLCPlaylistGenerator {
|
|
57
|
+
constructor(params = {}) {
|
|
58
|
+
__publicField(this, "video_extensions");
|
|
59
|
+
const fallback_extensions = [
|
|
60
|
+
".3gp",
|
|
61
|
+
".avi",
|
|
62
|
+
".m4v",
|
|
63
|
+
".mkv",
|
|
64
|
+
".mov",
|
|
65
|
+
".mp4",
|
|
66
|
+
".mpeg",
|
|
67
|
+
".mpg",
|
|
68
|
+
".ogv",
|
|
69
|
+
".webm",
|
|
70
|
+
".wmv"
|
|
71
|
+
];
|
|
72
|
+
const extensions_to_use = params.video_extensions ?? fallback_extensions;
|
|
73
|
+
this.video_extensions = new Set(extensions_to_use.map((extension) => extension.toLowerCase()));
|
|
74
|
+
}
|
|
75
|
+
async createPlaylist(params) {
|
|
76
|
+
await this.validateCreatePlaylistParams({
|
|
77
|
+
params
|
|
78
|
+
});
|
|
79
|
+
const source_directory_path = import_node_path.default.resolve(params.source_search_directory);
|
|
80
|
+
const output_playlist_path = import_node_path.default.resolve(params.output_playlist_file_destination);
|
|
81
|
+
const filename_matcher = this.getFilenameMatcher({
|
|
82
|
+
filename_matching_regular_expression: params.filename_matching_regular_expression
|
|
83
|
+
});
|
|
84
|
+
const filtered_files = await this.findVideoFiles({
|
|
85
|
+
directory_path: source_directory_path,
|
|
86
|
+
filename_matcher
|
|
87
|
+
});
|
|
88
|
+
const randomized_files = ShuffleArray({
|
|
89
|
+
values: filtered_files
|
|
90
|
+
});
|
|
91
|
+
const selected_files = randomized_files.slice(0, Math.min(params.number_of_results, randomized_files.length));
|
|
92
|
+
const playlist_contents = this.buildM3UContents({
|
|
93
|
+
selected_files
|
|
94
|
+
});
|
|
95
|
+
await (0, import_promises.mkdir)(import_node_path.default.dirname(output_playlist_path), {
|
|
96
|
+
recursive: true
|
|
97
|
+
});
|
|
98
|
+
await (0, import_promises.writeFile)(output_playlist_path, playlist_contents, "utf8");
|
|
99
|
+
return output_playlist_path;
|
|
100
|
+
}
|
|
101
|
+
async validateCreatePlaylistParams(params) {
|
|
102
|
+
if (params.params.source_search_directory.trim() === "") {
|
|
103
|
+
throw new Error("source_search_directory must not be empty.");
|
|
104
|
+
}
|
|
105
|
+
if (params.params.output_playlist_file_destination.trim() === "") {
|
|
106
|
+
throw new Error("output_playlist_file_destination must not be empty.");
|
|
107
|
+
}
|
|
108
|
+
if (!Number.isInteger(params.params.number_of_results)) {
|
|
109
|
+
throw new Error("number_of_results must be an integer value greater than 0.");
|
|
110
|
+
}
|
|
111
|
+
if (params.params.number_of_results <= 0) {
|
|
112
|
+
throw new Error("number_of_results must be greater than 0.");
|
|
113
|
+
}
|
|
114
|
+
const source_search_directory_stats = await (0, import_promises.stat)(params.params.source_search_directory).catch(() => null);
|
|
115
|
+
if (!source_search_directory_stats?.isDirectory()) {
|
|
116
|
+
throw new Error(`source_search_directory is not a valid directory: ${params.params.source_search_directory}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
getFilenameMatcher(params) {
|
|
120
|
+
if (params.filename_matching_regular_expression === void 0) {
|
|
121
|
+
return /^.*$/;
|
|
122
|
+
}
|
|
123
|
+
if (params.filename_matching_regular_expression instanceof RegExp) {
|
|
124
|
+
return params.filename_matching_regular_expression;
|
|
125
|
+
}
|
|
126
|
+
return new RegExp(params.filename_matching_regular_expression);
|
|
127
|
+
}
|
|
128
|
+
async findVideoFiles(params) {
|
|
129
|
+
const entries = await (0, import_promises.readdir)(params.directory_path, {
|
|
130
|
+
withFileTypes: true
|
|
131
|
+
});
|
|
132
|
+
const files = [];
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
const absolute_entry_path = import_node_path.default.join(params.directory_path, entry.name);
|
|
135
|
+
if (entry.isDirectory()) {
|
|
136
|
+
const nested_files = await this.findVideoFiles({
|
|
137
|
+
directory_path: absolute_entry_path,
|
|
138
|
+
filename_matcher: params.filename_matcher
|
|
139
|
+
});
|
|
140
|
+
files.push(...nested_files);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (!entry.isFile()) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (!this.isVideoFile({
|
|
147
|
+
file_name: entry.name
|
|
148
|
+
})) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (!this.doesFilenameMatchPattern({
|
|
152
|
+
file_name: entry.name,
|
|
153
|
+
filename_matcher: params.filename_matcher
|
|
154
|
+
})) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
files.push({
|
|
158
|
+
absolute_file_path: absolute_entry_path,
|
|
159
|
+
file_name: entry.name
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return files;
|
|
163
|
+
}
|
|
164
|
+
isVideoFile(params) {
|
|
165
|
+
const file_extension = import_node_path.default.extname(params.file_name).toLowerCase();
|
|
166
|
+
return this.video_extensions.has(file_extension);
|
|
167
|
+
}
|
|
168
|
+
doesFilenameMatchPattern(params) {
|
|
169
|
+
params.filename_matcher.lastIndex = 0;
|
|
170
|
+
return params.filename_matcher.test(params.file_name);
|
|
171
|
+
}
|
|
172
|
+
buildM3UContents(params) {
|
|
173
|
+
const m3u_lines = [
|
|
174
|
+
"#EXTM3U"
|
|
175
|
+
];
|
|
176
|
+
for (const selected_file of params.selected_files) {
|
|
177
|
+
m3u_lines.push(`#EXTINF:-1,${selected_file.file_name}`, selected_file.absolute_file_path);
|
|
178
|
+
}
|
|
179
|
+
return `${m3u_lines.join("\n")}
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
__name(_VLCPlaylistGenerator, "VLCPlaylistGenerator");
|
|
184
|
+
var VLCPlaylistGenerator = _VLCPlaylistGenerator;
|
|
185
|
+
|
|
186
|
+
// src/index.ts
|
|
187
|
+
function BuildUsageText() {
|
|
188
|
+
return [
|
|
189
|
+
"Usage:",
|
|
190
|
+
" node dist/index.js --source-search-directory <path> --output-playlist-file-destination <path> --number-of-results <number> [--filename-matching-regular-expression <regex>] [--filename-matching-regular-expression-flags <flags>]",
|
|
191
|
+
"",
|
|
192
|
+
"Required Arguments:",
|
|
193
|
+
" --source-search-directory, -s",
|
|
194
|
+
" --output-playlist-file-destination, -o",
|
|
195
|
+
" --number-of-results, -n",
|
|
196
|
+
"",
|
|
197
|
+
"Optional Arguments:",
|
|
198
|
+
" --filename-matching-regular-expression, -r Default: .*",
|
|
199
|
+
" --filename-matching-regular-expression-flags, -f",
|
|
200
|
+
" --help, -h"
|
|
201
|
+
].join("\n");
|
|
202
|
+
}
|
|
203
|
+
__name(BuildUsageText, "BuildUsageText");
|
|
204
|
+
function ParseRawCommandLineArguments(params) {
|
|
205
|
+
const parsed_arguments = {
|
|
206
|
+
help_requested: false
|
|
207
|
+
};
|
|
208
|
+
const option_aliases = /* @__PURE__ */ new Map([
|
|
209
|
+
[
|
|
210
|
+
"--source-search-directory",
|
|
211
|
+
"source_search_directory"
|
|
212
|
+
],
|
|
213
|
+
[
|
|
214
|
+
"-s",
|
|
215
|
+
"source_search_directory"
|
|
216
|
+
],
|
|
217
|
+
[
|
|
218
|
+
"--output-playlist-file-destination",
|
|
219
|
+
"output_playlist_file_destination"
|
|
220
|
+
],
|
|
221
|
+
[
|
|
222
|
+
"-o",
|
|
223
|
+
"output_playlist_file_destination"
|
|
224
|
+
],
|
|
225
|
+
[
|
|
226
|
+
"--number-of-results",
|
|
227
|
+
"number_of_results"
|
|
228
|
+
],
|
|
229
|
+
[
|
|
230
|
+
"-n",
|
|
231
|
+
"number_of_results"
|
|
232
|
+
],
|
|
233
|
+
[
|
|
234
|
+
"--filename-matching-regular-expression",
|
|
235
|
+
"filename_matching_regular_expression"
|
|
236
|
+
],
|
|
237
|
+
[
|
|
238
|
+
"-r",
|
|
239
|
+
"filename_matching_regular_expression"
|
|
240
|
+
],
|
|
241
|
+
[
|
|
242
|
+
"--filename-matching-regular-expression-flags",
|
|
243
|
+
"filename_matching_regular_expression_flags"
|
|
244
|
+
],
|
|
245
|
+
[
|
|
246
|
+
"-f",
|
|
247
|
+
"filename_matching_regular_expression_flags"
|
|
248
|
+
],
|
|
249
|
+
[
|
|
250
|
+
"--help",
|
|
251
|
+
"help_requested"
|
|
252
|
+
],
|
|
253
|
+
[
|
|
254
|
+
"-h",
|
|
255
|
+
"help_requested"
|
|
256
|
+
]
|
|
257
|
+
]);
|
|
258
|
+
for (let index = 0; index < params.command_line_tokens.length; index++) {
|
|
259
|
+
const raw_token = params.command_line_tokens[index];
|
|
260
|
+
const split_index = raw_token.indexOf("=");
|
|
261
|
+
const has_inline_value = split_index !== -1;
|
|
262
|
+
const option_key = has_inline_value ? raw_token.slice(0, split_index) : raw_token;
|
|
263
|
+
const parsed_option_key = option_aliases.get(option_key);
|
|
264
|
+
if (parsed_option_key === void 0) {
|
|
265
|
+
throw new Error(`Unknown argument: ${option_key}`);
|
|
266
|
+
}
|
|
267
|
+
if (parsed_option_key === "help_requested") {
|
|
268
|
+
parsed_arguments.help_requested = true;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const option_value = has_inline_value ? raw_token.slice(split_index + 1) : params.command_line_tokens[index + 1];
|
|
272
|
+
if (option_value === void 0 || option_value.startsWith("-")) {
|
|
273
|
+
throw new Error(`Missing value for argument: ${option_key}`);
|
|
274
|
+
}
|
|
275
|
+
parsed_arguments[parsed_option_key] = option_value;
|
|
276
|
+
if (!has_inline_value) {
|
|
277
|
+
index++;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return parsed_arguments;
|
|
281
|
+
}
|
|
282
|
+
__name(ParseRawCommandLineArguments, "ParseRawCommandLineArguments");
|
|
283
|
+
function ParseFilenameMatcher(params) {
|
|
284
|
+
const regex_pattern = params.filename_matching_regular_expression ?? ".*";
|
|
285
|
+
const regex_flags = params.filename_matching_regular_expression_flags ?? "";
|
|
286
|
+
return new RegExp(regex_pattern, regex_flags);
|
|
287
|
+
}
|
|
288
|
+
__name(ParseFilenameMatcher, "ParseFilenameMatcher");
|
|
289
|
+
function ParseCommandLineArguments(params) {
|
|
290
|
+
const raw_arguments = ParseRawCommandLineArguments({
|
|
291
|
+
command_line_tokens: params.command_line_tokens
|
|
292
|
+
});
|
|
293
|
+
if (raw_arguments.help_requested) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
if (raw_arguments.source_search_directory === void 0) {
|
|
297
|
+
throw new Error("Missing required argument: --source-search-directory");
|
|
298
|
+
}
|
|
299
|
+
if (raw_arguments.output_playlist_file_destination === void 0) {
|
|
300
|
+
throw new Error("Missing required argument: --output-playlist-file-destination");
|
|
301
|
+
}
|
|
302
|
+
if (raw_arguments.number_of_results === void 0) {
|
|
303
|
+
throw new Error("Missing required argument: --number-of-results");
|
|
304
|
+
}
|
|
305
|
+
const parsed_number_of_results = Number(raw_arguments.number_of_results);
|
|
306
|
+
if (!Number.isInteger(parsed_number_of_results)) {
|
|
307
|
+
throw new Error("--number-of-results must be an integer value.");
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
source_search_directory: raw_arguments.source_search_directory,
|
|
311
|
+
output_playlist_file_destination: raw_arguments.output_playlist_file_destination,
|
|
312
|
+
number_of_results: parsed_number_of_results,
|
|
313
|
+
filename_matching_regular_expression: ParseFilenameMatcher({
|
|
314
|
+
filename_matching_regular_expression: raw_arguments.filename_matching_regular_expression,
|
|
315
|
+
filename_matching_regular_expression_flags: raw_arguments.filename_matching_regular_expression_flags
|
|
316
|
+
})
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
__name(ParseCommandLineArguments, "ParseCommandLineArguments");
|
|
320
|
+
async function RunCommandLineApplication() {
|
|
321
|
+
try {
|
|
322
|
+
const command_line_arguments = ParseCommandLineArguments({
|
|
323
|
+
command_line_tokens: process.argv.slice(2)
|
|
324
|
+
});
|
|
325
|
+
if (command_line_arguments === null) {
|
|
326
|
+
console.log(BuildUsageText());
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const vlc_playlist_generator = new VLCPlaylistGenerator();
|
|
330
|
+
const output_playlist_path = await vlc_playlist_generator.createPlaylist({
|
|
331
|
+
source_search_directory: command_line_arguments.source_search_directory,
|
|
332
|
+
output_playlist_file_destination: command_line_arguments.output_playlist_file_destination,
|
|
333
|
+
number_of_results: command_line_arguments.number_of_results,
|
|
334
|
+
filename_matching_regular_expression: command_line_arguments.filename_matching_regular_expression
|
|
335
|
+
});
|
|
336
|
+
console.log(`Playlist generated: ${output_playlist_path}`);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
const error_message = error instanceof Error ? error.message : String(error);
|
|
339
|
+
console.error(error_message);
|
|
340
|
+
console.error("");
|
|
341
|
+
console.error(BuildUsageText());
|
|
342
|
+
process.exitCode = 1;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
__name(RunCommandLineApplication, "RunCommandLineApplication");
|
|
346
|
+
function IsDirectCommandLineExecution() {
|
|
347
|
+
if (typeof require === "undefined") {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
if (typeof module === "undefined") {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
return require.main === module;
|
|
354
|
+
}
|
|
355
|
+
__name(IsDirectCommandLineExecution, "IsDirectCommandLineExecution");
|
|
356
|
+
if (IsDirectCommandLineExecution()) {
|
|
357
|
+
void RunCommandLineApplication();
|
|
358
|
+
}
|
|
359
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
360
|
+
0 && (module.exports = {
|
|
361
|
+
VLCPlaylistGenerator
|
|
362
|
+
});
|
|
363
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/classes/vlc_playlist_generator/VLCPlaylistGenerator.class.ts"],"sourcesContent":["import { VLCPlaylistGenerator } from './classes/vlc_playlist_generator/VLCPlaylistGenerator.class';\n\ntype command_line_arguments_t = {\n source_search_directory: string;\n output_playlist_file_destination: string;\n number_of_results: number;\n filename_matching_regular_expression: RegExp | string;\n};\n\ntype raw_command_line_arguments_t = {\n source_search_directory?: string;\n output_playlist_file_destination?: string;\n number_of_results?: string;\n filename_matching_regular_expression?: string;\n filename_matching_regular_expression_flags?: string;\n help_requested: boolean;\n};\n\nexport { VLCPlaylistGenerator };\n\nfunction BuildUsageText(): string {\n return [\n 'Usage:',\n ' node dist/index.js --source-search-directory <path> --output-playlist-file-destination <path> --number-of-results <number> [--filename-matching-regular-expression <regex>] [--filename-matching-regular-expression-flags <flags>]',\n '',\n 'Required Arguments:',\n ' --source-search-directory, -s',\n ' --output-playlist-file-destination, -o',\n ' --number-of-results, -n',\n '',\n 'Optional Arguments:',\n ' --filename-matching-regular-expression, -r Default: .*',\n ' --filename-matching-regular-expression-flags, -f',\n ' --help, -h'\n ].join('\\n');\n}\n\nfunction ParseRawCommandLineArguments(params: {\n command_line_tokens: string[];\n}): raw_command_line_arguments_t {\n const parsed_arguments: raw_command_line_arguments_t = {\n help_requested: false\n };\n const option_aliases = new Map<string, keyof raw_command_line_arguments_t>([\n ['--source-search-directory', 'source_search_directory'],\n ['-s', 'source_search_directory'],\n ['--output-playlist-file-destination', 'output_playlist_file_destination'],\n ['-o', 'output_playlist_file_destination'],\n ['--number-of-results', 'number_of_results'],\n ['-n', 'number_of_results'],\n [\n '--filename-matching-regular-expression',\n 'filename_matching_regular_expression'\n ],\n ['-r', 'filename_matching_regular_expression'],\n [\n '--filename-matching-regular-expression-flags',\n 'filename_matching_regular_expression_flags'\n ],\n ['-f', 'filename_matching_regular_expression_flags'],\n ['--help', 'help_requested'],\n ['-h', 'help_requested']\n ]);\n\n for (let index = 0; index < params.command_line_tokens.length; index++) {\n const raw_token = params.command_line_tokens[index];\n const split_index = raw_token.indexOf('=');\n const has_inline_value = split_index !== -1;\n const option_key = has_inline_value\n ? raw_token.slice(0, split_index)\n : raw_token;\n const parsed_option_key = option_aliases.get(option_key);\n\n if (parsed_option_key === undefined) {\n throw new Error(`Unknown argument: ${option_key}`);\n }\n\n if (parsed_option_key === 'help_requested') {\n parsed_arguments.help_requested = true;\n continue;\n }\n\n const option_value = has_inline_value\n ? raw_token.slice(split_index + 1)\n : params.command_line_tokens[index + 1];\n\n if (option_value === undefined || option_value.startsWith('-')) {\n throw new Error(`Missing value for argument: ${option_key}`);\n }\n\n parsed_arguments[parsed_option_key] = option_value;\n\n if (!has_inline_value) {\n index++;\n }\n }\n\n return parsed_arguments;\n}\n\nfunction ParseFilenameMatcher(params: {\n filename_matching_regular_expression?: string;\n filename_matching_regular_expression_flags?: string;\n}): RegExp | string {\n const regex_pattern = params.filename_matching_regular_expression ?? '.*';\n const regex_flags = params.filename_matching_regular_expression_flags ?? '';\n\n return new RegExp(regex_pattern, regex_flags);\n}\n\nfunction ParseCommandLineArguments(params: {\n command_line_tokens: string[];\n}): command_line_arguments_t | null {\n const raw_arguments = ParseRawCommandLineArguments({\n command_line_tokens: params.command_line_tokens\n });\n\n if (raw_arguments.help_requested) {\n return null;\n }\n\n if (raw_arguments.source_search_directory === undefined) {\n throw new Error('Missing required argument: --source-search-directory');\n }\n\n if (raw_arguments.output_playlist_file_destination === undefined) {\n throw new Error(\n 'Missing required argument: --output-playlist-file-destination'\n );\n }\n\n if (raw_arguments.number_of_results === undefined) {\n throw new Error('Missing required argument: --number-of-results');\n }\n\n const parsed_number_of_results = Number(raw_arguments.number_of_results);\n\n if (!Number.isInteger(parsed_number_of_results)) {\n throw new Error('--number-of-results must be an integer value.');\n }\n\n return {\n source_search_directory: raw_arguments.source_search_directory,\n output_playlist_file_destination:\n raw_arguments.output_playlist_file_destination,\n number_of_results: parsed_number_of_results,\n filename_matching_regular_expression: ParseFilenameMatcher({\n filename_matching_regular_expression:\n raw_arguments.filename_matching_regular_expression,\n filename_matching_regular_expression_flags:\n raw_arguments.filename_matching_regular_expression_flags\n })\n };\n}\n\nasync function RunCommandLineApplication(): Promise<void> {\n try {\n const command_line_arguments = ParseCommandLineArguments({\n command_line_tokens: process.argv.slice(2)\n });\n\n if (command_line_arguments === null) {\n console.log(BuildUsageText());\n return;\n }\n\n const vlc_playlist_generator = new VLCPlaylistGenerator();\n const output_playlist_path = await vlc_playlist_generator.createPlaylist(\n {\n source_search_directory:\n command_line_arguments.source_search_directory,\n output_playlist_file_destination:\n command_line_arguments.output_playlist_file_destination,\n number_of_results: command_line_arguments.number_of_results,\n filename_matching_regular_expression:\n command_line_arguments.filename_matching_regular_expression\n }\n );\n\n console.log(`Playlist generated: ${output_playlist_path}`);\n } catch (error) {\n const error_message =\n error instanceof Error ? error.message : String(error);\n console.error(error_message);\n console.error('');\n console.error(BuildUsageText());\n process.exitCode = 1;\n }\n}\n\nfunction IsDirectCommandLineExecution(): boolean {\n if (typeof require === 'undefined') {\n return false;\n }\n\n if (typeof module === 'undefined') {\n return false;\n }\n\n return require.main === module;\n}\n\nif (IsDirectCommandLineExecution()) {\n void RunCommandLineApplication();\n}\n","import { mkdir, readdir, stat, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\ntype create_playlist_params_t = {\n source_search_directory: string;\n output_playlist_file_destination: string;\n number_of_results: number;\n filename_matching_regular_expression?: RegExp | string;\n};\n\ntype discovered_video_file_t = {\n absolute_file_path: string;\n file_name: string;\n};\n\nfunction ShuffleArray<value_t>(params: { values: value_t[] }): value_t[] {\n const values_copy = [...params.values];\n\n for (let index = values_copy.length - 1; index > 0; index--) {\n const random_index = Math.floor(Math.random() * (index + 1));\n const current_value = values_copy[index];\n values_copy[index] = values_copy[random_index];\n values_copy[random_index] = current_value;\n }\n\n return values_copy;\n}\n\nexport class VLCPlaylistGenerator {\n private readonly video_extensions: Set<string>;\n\n constructor(\n params: {\n video_extensions?: string[];\n } = {}\n ) {\n const fallback_extensions = [\n '.3gp',\n '.avi',\n '.m4v',\n '.mkv',\n '.mov',\n '.mp4',\n '.mpeg',\n '.mpg',\n '.ogv',\n '.webm',\n '.wmv'\n ];\n\n const extensions_to_use = params.video_extensions ?? fallback_extensions;\n this.video_extensions = new Set(\n extensions_to_use.map((extension) => extension.toLowerCase())\n );\n }\n\n async createPlaylist(params: create_playlist_params_t): Promise<string> {\n await this.validateCreatePlaylistParams({ params });\n\n const source_directory_path = path.resolve(params.source_search_directory);\n const output_playlist_path = path.resolve(\n params.output_playlist_file_destination\n );\n const filename_matcher = this.getFilenameMatcher({\n filename_matching_regular_expression:\n params.filename_matching_regular_expression\n });\n const filtered_files = await this.findVideoFiles({\n directory_path: source_directory_path,\n filename_matcher\n });\n const randomized_files = ShuffleArray({ values: filtered_files });\n const selected_files = randomized_files.slice(\n 0,\n Math.min(params.number_of_results, randomized_files.length)\n );\n const playlist_contents = this.buildM3UContents({\n selected_files\n });\n\n await mkdir(path.dirname(output_playlist_path), { recursive: true });\n await writeFile(output_playlist_path, playlist_contents, 'utf8');\n\n return output_playlist_path;\n }\n\n private async validateCreatePlaylistParams(params: {\n params: create_playlist_params_t;\n }): Promise<void> {\n if (params.params.source_search_directory.trim() === '') {\n throw new Error('source_search_directory must not be empty.');\n }\n\n if (params.params.output_playlist_file_destination.trim() === '') {\n throw new Error('output_playlist_file_destination must not be empty.');\n }\n\n if (!Number.isInteger(params.params.number_of_results)) {\n throw new Error(\n 'number_of_results must be an integer value greater than 0.'\n );\n }\n\n if (params.params.number_of_results <= 0) {\n throw new Error('number_of_results must be greater than 0.');\n }\n\n const source_search_directory_stats = await stat(\n params.params.source_search_directory\n ).catch(() => null);\n\n if (!source_search_directory_stats?.isDirectory()) {\n throw new Error(\n `source_search_directory is not a valid directory: ${params.params.source_search_directory}`\n );\n }\n }\n\n private getFilenameMatcher(params: {\n filename_matching_regular_expression?: RegExp | string;\n }): RegExp {\n if (params.filename_matching_regular_expression === undefined) {\n return /^.*$/;\n }\n\n if (params.filename_matching_regular_expression instanceof RegExp) {\n return params.filename_matching_regular_expression;\n }\n\n return new RegExp(params.filename_matching_regular_expression);\n }\n\n private async findVideoFiles(params: {\n directory_path: string;\n filename_matcher: RegExp;\n }): Promise<discovered_video_file_t[]> {\n const entries = await readdir(params.directory_path, {\n withFileTypes: true\n });\n const files: discovered_video_file_t[] = [];\n\n for (const entry of entries) {\n const absolute_entry_path = path.join(params.directory_path, entry.name);\n\n if (entry.isDirectory()) {\n const nested_files = await this.findVideoFiles({\n directory_path: absolute_entry_path,\n filename_matcher: params.filename_matcher\n });\n files.push(...nested_files);\n continue;\n }\n\n if (!entry.isFile()) {\n continue;\n }\n\n if (!this.isVideoFile({ file_name: entry.name })) {\n continue;\n }\n\n if (\n !this.doesFilenameMatchPattern({\n file_name: entry.name,\n filename_matcher: params.filename_matcher\n })\n ) {\n continue;\n }\n\n files.push({\n absolute_file_path: absolute_entry_path,\n file_name: entry.name\n });\n }\n\n return files;\n }\n\n private isVideoFile(params: { file_name: string }): boolean {\n const file_extension = path.extname(params.file_name).toLowerCase();\n return this.video_extensions.has(file_extension);\n }\n\n private doesFilenameMatchPattern(params: {\n file_name: string;\n filename_matcher: RegExp;\n }): boolean {\n params.filename_matcher.lastIndex = 0;\n return params.filename_matcher.test(params.file_name);\n }\n\n private buildM3UContents(params: {\n selected_files: discovered_video_file_t[];\n }): string {\n const m3u_lines = ['#EXTM3U'];\n\n for (const selected_file of params.selected_files) {\n m3u_lines.push(\n `#EXTINF:-1,${selected_file.file_name}`,\n selected_file.absolute_file_path\n );\n }\n\n return `${m3u_lines.join('\\n')}\\n`;\n }\n}\n"],"mappings":"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","names":["ShuffleArray","params","values_copy","values","index","length","random_index","Math","floor","random","current_value","VLCPlaylistGenerator","video_extensions","fallback_extensions","extensions_to_use","Set","map","extension","toLowerCase","createPlaylist","validateCreatePlaylistParams","source_directory_path","path","resolve","source_search_directory","output_playlist_path","output_playlist_file_destination","filename_matcher","getFilenameMatcher","filename_matching_regular_expression","filtered_files","findVideoFiles","directory_path","randomized_files","selected_files","slice","min","number_of_results","playlist_contents","buildM3UContents","mkdir","dirname","recursive","writeFile","trim","Error","Number","isInteger","source_search_directory_stats","stat","catch","isDirectory","undefined","RegExp","entries","readdir","withFileTypes","files","entry","absolute_entry_path","join","name","nested_files","push","isFile","isVideoFile","file_name","doesFilenameMatchPattern","absolute_file_path","file_extension","extname","has","lastIndex","test","m3u_lines","selected_file","BuildUsageText","join","ParseRawCommandLineArguments","params","parsed_arguments","help_requested","option_aliases","Map","index","command_line_tokens","length","raw_token","split_index","indexOf","has_inline_value","option_key","slice","parsed_option_key","get","undefined","Error","option_value","startsWith","ParseFilenameMatcher","regex_pattern","filename_matching_regular_expression","regex_flags","filename_matching_regular_expression_flags","RegExp","ParseCommandLineArguments","raw_arguments","source_search_directory","output_playlist_file_destination","number_of_results","parsed_number_of_results","Number","isInteger","RunCommandLineApplication","command_line_arguments","process","argv","console","log","vlc_playlist_generator","VLCPlaylistGenerator","output_playlist_path","createPlaylist","error","error_message","message","String","exitCode","IsDirectCommandLineExecution","require","module","main"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
4
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
+
}) : x)(function(x) {
|
|
7
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
10
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
11
|
+
|
|
12
|
+
// src/classes/vlc_playlist_generator/VLCPlaylistGenerator.class.ts
|
|
13
|
+
import { mkdir, readdir, stat, writeFile } from "fs/promises";
|
|
14
|
+
import path from "path";
|
|
15
|
+
function ShuffleArray(params) {
|
|
16
|
+
const values_copy = [
|
|
17
|
+
...params.values
|
|
18
|
+
];
|
|
19
|
+
for (let index = values_copy.length - 1; index > 0; index--) {
|
|
20
|
+
const random_index = Math.floor(Math.random() * (index + 1));
|
|
21
|
+
const current_value = values_copy[index];
|
|
22
|
+
values_copy[index] = values_copy[random_index];
|
|
23
|
+
values_copy[random_index] = current_value;
|
|
24
|
+
}
|
|
25
|
+
return values_copy;
|
|
26
|
+
}
|
|
27
|
+
__name(ShuffleArray, "ShuffleArray");
|
|
28
|
+
var _VLCPlaylistGenerator = class _VLCPlaylistGenerator {
|
|
29
|
+
constructor(params = {}) {
|
|
30
|
+
__publicField(this, "video_extensions");
|
|
31
|
+
const fallback_extensions = [
|
|
32
|
+
".3gp",
|
|
33
|
+
".avi",
|
|
34
|
+
".m4v",
|
|
35
|
+
".mkv",
|
|
36
|
+
".mov",
|
|
37
|
+
".mp4",
|
|
38
|
+
".mpeg",
|
|
39
|
+
".mpg",
|
|
40
|
+
".ogv",
|
|
41
|
+
".webm",
|
|
42
|
+
".wmv"
|
|
43
|
+
];
|
|
44
|
+
const extensions_to_use = params.video_extensions ?? fallback_extensions;
|
|
45
|
+
this.video_extensions = new Set(extensions_to_use.map((extension) => extension.toLowerCase()));
|
|
46
|
+
}
|
|
47
|
+
async createPlaylist(params) {
|
|
48
|
+
await this.validateCreatePlaylistParams({
|
|
49
|
+
params
|
|
50
|
+
});
|
|
51
|
+
const source_directory_path = path.resolve(params.source_search_directory);
|
|
52
|
+
const output_playlist_path = path.resolve(params.output_playlist_file_destination);
|
|
53
|
+
const filename_matcher = this.getFilenameMatcher({
|
|
54
|
+
filename_matching_regular_expression: params.filename_matching_regular_expression
|
|
55
|
+
});
|
|
56
|
+
const filtered_files = await this.findVideoFiles({
|
|
57
|
+
directory_path: source_directory_path,
|
|
58
|
+
filename_matcher
|
|
59
|
+
});
|
|
60
|
+
const randomized_files = ShuffleArray({
|
|
61
|
+
values: filtered_files
|
|
62
|
+
});
|
|
63
|
+
const selected_files = randomized_files.slice(0, Math.min(params.number_of_results, randomized_files.length));
|
|
64
|
+
const playlist_contents = this.buildM3UContents({
|
|
65
|
+
selected_files
|
|
66
|
+
});
|
|
67
|
+
await mkdir(path.dirname(output_playlist_path), {
|
|
68
|
+
recursive: true
|
|
69
|
+
});
|
|
70
|
+
await writeFile(output_playlist_path, playlist_contents, "utf8");
|
|
71
|
+
return output_playlist_path;
|
|
72
|
+
}
|
|
73
|
+
async validateCreatePlaylistParams(params) {
|
|
74
|
+
if (params.params.source_search_directory.trim() === "") {
|
|
75
|
+
throw new Error("source_search_directory must not be empty.");
|
|
76
|
+
}
|
|
77
|
+
if (params.params.output_playlist_file_destination.trim() === "") {
|
|
78
|
+
throw new Error("output_playlist_file_destination must not be empty.");
|
|
79
|
+
}
|
|
80
|
+
if (!Number.isInteger(params.params.number_of_results)) {
|
|
81
|
+
throw new Error("number_of_results must be an integer value greater than 0.");
|
|
82
|
+
}
|
|
83
|
+
if (params.params.number_of_results <= 0) {
|
|
84
|
+
throw new Error("number_of_results must be greater than 0.");
|
|
85
|
+
}
|
|
86
|
+
const source_search_directory_stats = await stat(params.params.source_search_directory).catch(() => null);
|
|
87
|
+
if (!source_search_directory_stats?.isDirectory()) {
|
|
88
|
+
throw new Error(`source_search_directory is not a valid directory: ${params.params.source_search_directory}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
getFilenameMatcher(params) {
|
|
92
|
+
if (params.filename_matching_regular_expression === void 0) {
|
|
93
|
+
return /^.*$/;
|
|
94
|
+
}
|
|
95
|
+
if (params.filename_matching_regular_expression instanceof RegExp) {
|
|
96
|
+
return params.filename_matching_regular_expression;
|
|
97
|
+
}
|
|
98
|
+
return new RegExp(params.filename_matching_regular_expression);
|
|
99
|
+
}
|
|
100
|
+
async findVideoFiles(params) {
|
|
101
|
+
const entries = await readdir(params.directory_path, {
|
|
102
|
+
withFileTypes: true
|
|
103
|
+
});
|
|
104
|
+
const files = [];
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
const absolute_entry_path = path.join(params.directory_path, entry.name);
|
|
107
|
+
if (entry.isDirectory()) {
|
|
108
|
+
const nested_files = await this.findVideoFiles({
|
|
109
|
+
directory_path: absolute_entry_path,
|
|
110
|
+
filename_matcher: params.filename_matcher
|
|
111
|
+
});
|
|
112
|
+
files.push(...nested_files);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!entry.isFile()) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!this.isVideoFile({
|
|
119
|
+
file_name: entry.name
|
|
120
|
+
})) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (!this.doesFilenameMatchPattern({
|
|
124
|
+
file_name: entry.name,
|
|
125
|
+
filename_matcher: params.filename_matcher
|
|
126
|
+
})) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
files.push({
|
|
130
|
+
absolute_file_path: absolute_entry_path,
|
|
131
|
+
file_name: entry.name
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return files;
|
|
135
|
+
}
|
|
136
|
+
isVideoFile(params) {
|
|
137
|
+
const file_extension = path.extname(params.file_name).toLowerCase();
|
|
138
|
+
return this.video_extensions.has(file_extension);
|
|
139
|
+
}
|
|
140
|
+
doesFilenameMatchPattern(params) {
|
|
141
|
+
params.filename_matcher.lastIndex = 0;
|
|
142
|
+
return params.filename_matcher.test(params.file_name);
|
|
143
|
+
}
|
|
144
|
+
buildM3UContents(params) {
|
|
145
|
+
const m3u_lines = [
|
|
146
|
+
"#EXTM3U"
|
|
147
|
+
];
|
|
148
|
+
for (const selected_file of params.selected_files) {
|
|
149
|
+
m3u_lines.push(`#EXTINF:-1,${selected_file.file_name}`, selected_file.absolute_file_path);
|
|
150
|
+
}
|
|
151
|
+
return `${m3u_lines.join("\n")}
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
__name(_VLCPlaylistGenerator, "VLCPlaylistGenerator");
|
|
156
|
+
var VLCPlaylistGenerator = _VLCPlaylistGenerator;
|
|
157
|
+
|
|
158
|
+
// src/index.ts
|
|
159
|
+
function BuildUsageText() {
|
|
160
|
+
return [
|
|
161
|
+
"Usage:",
|
|
162
|
+
" node dist/index.js --source-search-directory <path> --output-playlist-file-destination <path> --number-of-results <number> [--filename-matching-regular-expression <regex>] [--filename-matching-regular-expression-flags <flags>]",
|
|
163
|
+
"",
|
|
164
|
+
"Required Arguments:",
|
|
165
|
+
" --source-search-directory, -s",
|
|
166
|
+
" --output-playlist-file-destination, -o",
|
|
167
|
+
" --number-of-results, -n",
|
|
168
|
+
"",
|
|
169
|
+
"Optional Arguments:",
|
|
170
|
+
" --filename-matching-regular-expression, -r Default: .*",
|
|
171
|
+
" --filename-matching-regular-expression-flags, -f",
|
|
172
|
+
" --help, -h"
|
|
173
|
+
].join("\n");
|
|
174
|
+
}
|
|
175
|
+
__name(BuildUsageText, "BuildUsageText");
|
|
176
|
+
function ParseRawCommandLineArguments(params) {
|
|
177
|
+
const parsed_arguments = {
|
|
178
|
+
help_requested: false
|
|
179
|
+
};
|
|
180
|
+
const option_aliases = /* @__PURE__ */ new Map([
|
|
181
|
+
[
|
|
182
|
+
"--source-search-directory",
|
|
183
|
+
"source_search_directory"
|
|
184
|
+
],
|
|
185
|
+
[
|
|
186
|
+
"-s",
|
|
187
|
+
"source_search_directory"
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
"--output-playlist-file-destination",
|
|
191
|
+
"output_playlist_file_destination"
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
"-o",
|
|
195
|
+
"output_playlist_file_destination"
|
|
196
|
+
],
|
|
197
|
+
[
|
|
198
|
+
"--number-of-results",
|
|
199
|
+
"number_of_results"
|
|
200
|
+
],
|
|
201
|
+
[
|
|
202
|
+
"-n",
|
|
203
|
+
"number_of_results"
|
|
204
|
+
],
|
|
205
|
+
[
|
|
206
|
+
"--filename-matching-regular-expression",
|
|
207
|
+
"filename_matching_regular_expression"
|
|
208
|
+
],
|
|
209
|
+
[
|
|
210
|
+
"-r",
|
|
211
|
+
"filename_matching_regular_expression"
|
|
212
|
+
],
|
|
213
|
+
[
|
|
214
|
+
"--filename-matching-regular-expression-flags",
|
|
215
|
+
"filename_matching_regular_expression_flags"
|
|
216
|
+
],
|
|
217
|
+
[
|
|
218
|
+
"-f",
|
|
219
|
+
"filename_matching_regular_expression_flags"
|
|
220
|
+
],
|
|
221
|
+
[
|
|
222
|
+
"--help",
|
|
223
|
+
"help_requested"
|
|
224
|
+
],
|
|
225
|
+
[
|
|
226
|
+
"-h",
|
|
227
|
+
"help_requested"
|
|
228
|
+
]
|
|
229
|
+
]);
|
|
230
|
+
for (let index = 0; index < params.command_line_tokens.length; index++) {
|
|
231
|
+
const raw_token = params.command_line_tokens[index];
|
|
232
|
+
const split_index = raw_token.indexOf("=");
|
|
233
|
+
const has_inline_value = split_index !== -1;
|
|
234
|
+
const option_key = has_inline_value ? raw_token.slice(0, split_index) : raw_token;
|
|
235
|
+
const parsed_option_key = option_aliases.get(option_key);
|
|
236
|
+
if (parsed_option_key === void 0) {
|
|
237
|
+
throw new Error(`Unknown argument: ${option_key}`);
|
|
238
|
+
}
|
|
239
|
+
if (parsed_option_key === "help_requested") {
|
|
240
|
+
parsed_arguments.help_requested = true;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const option_value = has_inline_value ? raw_token.slice(split_index + 1) : params.command_line_tokens[index + 1];
|
|
244
|
+
if (option_value === void 0 || option_value.startsWith("-")) {
|
|
245
|
+
throw new Error(`Missing value for argument: ${option_key}`);
|
|
246
|
+
}
|
|
247
|
+
parsed_arguments[parsed_option_key] = option_value;
|
|
248
|
+
if (!has_inline_value) {
|
|
249
|
+
index++;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return parsed_arguments;
|
|
253
|
+
}
|
|
254
|
+
__name(ParseRawCommandLineArguments, "ParseRawCommandLineArguments");
|
|
255
|
+
function ParseFilenameMatcher(params) {
|
|
256
|
+
const regex_pattern = params.filename_matching_regular_expression ?? ".*";
|
|
257
|
+
const regex_flags = params.filename_matching_regular_expression_flags ?? "";
|
|
258
|
+
return new RegExp(regex_pattern, regex_flags);
|
|
259
|
+
}
|
|
260
|
+
__name(ParseFilenameMatcher, "ParseFilenameMatcher");
|
|
261
|
+
function ParseCommandLineArguments(params) {
|
|
262
|
+
const raw_arguments = ParseRawCommandLineArguments({
|
|
263
|
+
command_line_tokens: params.command_line_tokens
|
|
264
|
+
});
|
|
265
|
+
if (raw_arguments.help_requested) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
if (raw_arguments.source_search_directory === void 0) {
|
|
269
|
+
throw new Error("Missing required argument: --source-search-directory");
|
|
270
|
+
}
|
|
271
|
+
if (raw_arguments.output_playlist_file_destination === void 0) {
|
|
272
|
+
throw new Error("Missing required argument: --output-playlist-file-destination");
|
|
273
|
+
}
|
|
274
|
+
if (raw_arguments.number_of_results === void 0) {
|
|
275
|
+
throw new Error("Missing required argument: --number-of-results");
|
|
276
|
+
}
|
|
277
|
+
const parsed_number_of_results = Number(raw_arguments.number_of_results);
|
|
278
|
+
if (!Number.isInteger(parsed_number_of_results)) {
|
|
279
|
+
throw new Error("--number-of-results must be an integer value.");
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
source_search_directory: raw_arguments.source_search_directory,
|
|
283
|
+
output_playlist_file_destination: raw_arguments.output_playlist_file_destination,
|
|
284
|
+
number_of_results: parsed_number_of_results,
|
|
285
|
+
filename_matching_regular_expression: ParseFilenameMatcher({
|
|
286
|
+
filename_matching_regular_expression: raw_arguments.filename_matching_regular_expression,
|
|
287
|
+
filename_matching_regular_expression_flags: raw_arguments.filename_matching_regular_expression_flags
|
|
288
|
+
})
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
__name(ParseCommandLineArguments, "ParseCommandLineArguments");
|
|
292
|
+
async function RunCommandLineApplication() {
|
|
293
|
+
try {
|
|
294
|
+
const command_line_arguments = ParseCommandLineArguments({
|
|
295
|
+
command_line_tokens: process.argv.slice(2)
|
|
296
|
+
});
|
|
297
|
+
if (command_line_arguments === null) {
|
|
298
|
+
console.log(BuildUsageText());
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const vlc_playlist_generator = new VLCPlaylistGenerator();
|
|
302
|
+
const output_playlist_path = await vlc_playlist_generator.createPlaylist({
|
|
303
|
+
source_search_directory: command_line_arguments.source_search_directory,
|
|
304
|
+
output_playlist_file_destination: command_line_arguments.output_playlist_file_destination,
|
|
305
|
+
number_of_results: command_line_arguments.number_of_results,
|
|
306
|
+
filename_matching_regular_expression: command_line_arguments.filename_matching_regular_expression
|
|
307
|
+
});
|
|
308
|
+
console.log(`Playlist generated: ${output_playlist_path}`);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
const error_message = error instanceof Error ? error.message : String(error);
|
|
311
|
+
console.error(error_message);
|
|
312
|
+
console.error("");
|
|
313
|
+
console.error(BuildUsageText());
|
|
314
|
+
process.exitCode = 1;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
__name(RunCommandLineApplication, "RunCommandLineApplication");
|
|
318
|
+
function IsDirectCommandLineExecution() {
|
|
319
|
+
if (typeof __require === "undefined") {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
if (typeof module === "undefined") {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
return __require.main === module;
|
|
326
|
+
}
|
|
327
|
+
__name(IsDirectCommandLineExecution, "IsDirectCommandLineExecution");
|
|
328
|
+
if (IsDirectCommandLineExecution()) {
|
|
329
|
+
void RunCommandLineApplication();
|
|
330
|
+
}
|
|
331
|
+
export {
|
|
332
|
+
VLCPlaylistGenerator
|
|
333
|
+
};
|
|
334
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/classes/vlc_playlist_generator/VLCPlaylistGenerator.class.ts","../src/index.ts"],"sourcesContent":["import { mkdir, readdir, stat, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\ntype create_playlist_params_t = {\n source_search_directory: string;\n output_playlist_file_destination: string;\n number_of_results: number;\n filename_matching_regular_expression?: RegExp | string;\n};\n\ntype discovered_video_file_t = {\n absolute_file_path: string;\n file_name: string;\n};\n\nfunction ShuffleArray<value_t>(params: { values: value_t[] }): value_t[] {\n const values_copy = [...params.values];\n\n for (let index = values_copy.length - 1; index > 0; index--) {\n const random_index = Math.floor(Math.random() * (index + 1));\n const current_value = values_copy[index];\n values_copy[index] = values_copy[random_index];\n values_copy[random_index] = current_value;\n }\n\n return values_copy;\n}\n\nexport class VLCPlaylistGenerator {\n private readonly video_extensions: Set<string>;\n\n constructor(\n params: {\n video_extensions?: string[];\n } = {}\n ) {\n const fallback_extensions = [\n '.3gp',\n '.avi',\n '.m4v',\n '.mkv',\n '.mov',\n '.mp4',\n '.mpeg',\n '.mpg',\n '.ogv',\n '.webm',\n '.wmv'\n ];\n\n const extensions_to_use = params.video_extensions ?? fallback_extensions;\n this.video_extensions = new Set(\n extensions_to_use.map((extension) => extension.toLowerCase())\n );\n }\n\n async createPlaylist(params: create_playlist_params_t): Promise<string> {\n await this.validateCreatePlaylistParams({ params });\n\n const source_directory_path = path.resolve(params.source_search_directory);\n const output_playlist_path = path.resolve(\n params.output_playlist_file_destination\n );\n const filename_matcher = this.getFilenameMatcher({\n filename_matching_regular_expression:\n params.filename_matching_regular_expression\n });\n const filtered_files = await this.findVideoFiles({\n directory_path: source_directory_path,\n filename_matcher\n });\n const randomized_files = ShuffleArray({ values: filtered_files });\n const selected_files = randomized_files.slice(\n 0,\n Math.min(params.number_of_results, randomized_files.length)\n );\n const playlist_contents = this.buildM3UContents({\n selected_files\n });\n\n await mkdir(path.dirname(output_playlist_path), { recursive: true });\n await writeFile(output_playlist_path, playlist_contents, 'utf8');\n\n return output_playlist_path;\n }\n\n private async validateCreatePlaylistParams(params: {\n params: create_playlist_params_t;\n }): Promise<void> {\n if (params.params.source_search_directory.trim() === '') {\n throw new Error('source_search_directory must not be empty.');\n }\n\n if (params.params.output_playlist_file_destination.trim() === '') {\n throw new Error('output_playlist_file_destination must not be empty.');\n }\n\n if (!Number.isInteger(params.params.number_of_results)) {\n throw new Error(\n 'number_of_results must be an integer value greater than 0.'\n );\n }\n\n if (params.params.number_of_results <= 0) {\n throw new Error('number_of_results must be greater than 0.');\n }\n\n const source_search_directory_stats = await stat(\n params.params.source_search_directory\n ).catch(() => null);\n\n if (!source_search_directory_stats?.isDirectory()) {\n throw new Error(\n `source_search_directory is not a valid directory: ${params.params.source_search_directory}`\n );\n }\n }\n\n private getFilenameMatcher(params: {\n filename_matching_regular_expression?: RegExp | string;\n }): RegExp {\n if (params.filename_matching_regular_expression === undefined) {\n return /^.*$/;\n }\n\n if (params.filename_matching_regular_expression instanceof RegExp) {\n return params.filename_matching_regular_expression;\n }\n\n return new RegExp(params.filename_matching_regular_expression);\n }\n\n private async findVideoFiles(params: {\n directory_path: string;\n filename_matcher: RegExp;\n }): Promise<discovered_video_file_t[]> {\n const entries = await readdir(params.directory_path, {\n withFileTypes: true\n });\n const files: discovered_video_file_t[] = [];\n\n for (const entry of entries) {\n const absolute_entry_path = path.join(params.directory_path, entry.name);\n\n if (entry.isDirectory()) {\n const nested_files = await this.findVideoFiles({\n directory_path: absolute_entry_path,\n filename_matcher: params.filename_matcher\n });\n files.push(...nested_files);\n continue;\n }\n\n if (!entry.isFile()) {\n continue;\n }\n\n if (!this.isVideoFile({ file_name: entry.name })) {\n continue;\n }\n\n if (\n !this.doesFilenameMatchPattern({\n file_name: entry.name,\n filename_matcher: params.filename_matcher\n })\n ) {\n continue;\n }\n\n files.push({\n absolute_file_path: absolute_entry_path,\n file_name: entry.name\n });\n }\n\n return files;\n }\n\n private isVideoFile(params: { file_name: string }): boolean {\n const file_extension = path.extname(params.file_name).toLowerCase();\n return this.video_extensions.has(file_extension);\n }\n\n private doesFilenameMatchPattern(params: {\n file_name: string;\n filename_matcher: RegExp;\n }): boolean {\n params.filename_matcher.lastIndex = 0;\n return params.filename_matcher.test(params.file_name);\n }\n\n private buildM3UContents(params: {\n selected_files: discovered_video_file_t[];\n }): string {\n const m3u_lines = ['#EXTM3U'];\n\n for (const selected_file of params.selected_files) {\n m3u_lines.push(\n `#EXTINF:-1,${selected_file.file_name}`,\n selected_file.absolute_file_path\n );\n }\n\n return `${m3u_lines.join('\\n')}\\n`;\n }\n}\n","import { VLCPlaylistGenerator } from './classes/vlc_playlist_generator/VLCPlaylistGenerator.class';\n\ntype command_line_arguments_t = {\n source_search_directory: string;\n output_playlist_file_destination: string;\n number_of_results: number;\n filename_matching_regular_expression: RegExp | string;\n};\n\ntype raw_command_line_arguments_t = {\n source_search_directory?: string;\n output_playlist_file_destination?: string;\n number_of_results?: string;\n filename_matching_regular_expression?: string;\n filename_matching_regular_expression_flags?: string;\n help_requested: boolean;\n};\n\nexport { VLCPlaylistGenerator };\n\nfunction BuildUsageText(): string {\n return [\n 'Usage:',\n ' node dist/index.js --source-search-directory <path> --output-playlist-file-destination <path> --number-of-results <number> [--filename-matching-regular-expression <regex>] [--filename-matching-regular-expression-flags <flags>]',\n '',\n 'Required Arguments:',\n ' --source-search-directory, -s',\n ' --output-playlist-file-destination, -o',\n ' --number-of-results, -n',\n '',\n 'Optional Arguments:',\n ' --filename-matching-regular-expression, -r Default: .*',\n ' --filename-matching-regular-expression-flags, -f',\n ' --help, -h'\n ].join('\\n');\n}\n\nfunction ParseRawCommandLineArguments(params: {\n command_line_tokens: string[];\n}): raw_command_line_arguments_t {\n const parsed_arguments: raw_command_line_arguments_t = {\n help_requested: false\n };\n const option_aliases = new Map<string, keyof raw_command_line_arguments_t>([\n ['--source-search-directory', 'source_search_directory'],\n ['-s', 'source_search_directory'],\n ['--output-playlist-file-destination', 'output_playlist_file_destination'],\n ['-o', 'output_playlist_file_destination'],\n ['--number-of-results', 'number_of_results'],\n ['-n', 'number_of_results'],\n [\n '--filename-matching-regular-expression',\n 'filename_matching_regular_expression'\n ],\n ['-r', 'filename_matching_regular_expression'],\n [\n '--filename-matching-regular-expression-flags',\n 'filename_matching_regular_expression_flags'\n ],\n ['-f', 'filename_matching_regular_expression_flags'],\n ['--help', 'help_requested'],\n ['-h', 'help_requested']\n ]);\n\n for (let index = 0; index < params.command_line_tokens.length; index++) {\n const raw_token = params.command_line_tokens[index];\n const split_index = raw_token.indexOf('=');\n const has_inline_value = split_index !== -1;\n const option_key = has_inline_value\n ? raw_token.slice(0, split_index)\n : raw_token;\n const parsed_option_key = option_aliases.get(option_key);\n\n if (parsed_option_key === undefined) {\n throw new Error(`Unknown argument: ${option_key}`);\n }\n\n if (parsed_option_key === 'help_requested') {\n parsed_arguments.help_requested = true;\n continue;\n }\n\n const option_value = has_inline_value\n ? raw_token.slice(split_index + 1)\n : params.command_line_tokens[index + 1];\n\n if (option_value === undefined || option_value.startsWith('-')) {\n throw new Error(`Missing value for argument: ${option_key}`);\n }\n\n parsed_arguments[parsed_option_key] = option_value;\n\n if (!has_inline_value) {\n index++;\n }\n }\n\n return parsed_arguments;\n}\n\nfunction ParseFilenameMatcher(params: {\n filename_matching_regular_expression?: string;\n filename_matching_regular_expression_flags?: string;\n}): RegExp | string {\n const regex_pattern = params.filename_matching_regular_expression ?? '.*';\n const regex_flags = params.filename_matching_regular_expression_flags ?? '';\n\n return new RegExp(regex_pattern, regex_flags);\n}\n\nfunction ParseCommandLineArguments(params: {\n command_line_tokens: string[];\n}): command_line_arguments_t | null {\n const raw_arguments = ParseRawCommandLineArguments({\n command_line_tokens: params.command_line_tokens\n });\n\n if (raw_arguments.help_requested) {\n return null;\n }\n\n if (raw_arguments.source_search_directory === undefined) {\n throw new Error('Missing required argument: --source-search-directory');\n }\n\n if (raw_arguments.output_playlist_file_destination === undefined) {\n throw new Error(\n 'Missing required argument: --output-playlist-file-destination'\n );\n }\n\n if (raw_arguments.number_of_results === undefined) {\n throw new Error('Missing required argument: --number-of-results');\n }\n\n const parsed_number_of_results = Number(raw_arguments.number_of_results);\n\n if (!Number.isInteger(parsed_number_of_results)) {\n throw new Error('--number-of-results must be an integer value.');\n }\n\n return {\n source_search_directory: raw_arguments.source_search_directory,\n output_playlist_file_destination:\n raw_arguments.output_playlist_file_destination,\n number_of_results: parsed_number_of_results,\n filename_matching_regular_expression: ParseFilenameMatcher({\n filename_matching_regular_expression:\n raw_arguments.filename_matching_regular_expression,\n filename_matching_regular_expression_flags:\n raw_arguments.filename_matching_regular_expression_flags\n })\n };\n}\n\nasync function RunCommandLineApplication(): Promise<void> {\n try {\n const command_line_arguments = ParseCommandLineArguments({\n command_line_tokens: process.argv.slice(2)\n });\n\n if (command_line_arguments === null) {\n console.log(BuildUsageText());\n return;\n }\n\n const vlc_playlist_generator = new VLCPlaylistGenerator();\n const output_playlist_path = await vlc_playlist_generator.createPlaylist(\n {\n source_search_directory:\n command_line_arguments.source_search_directory,\n output_playlist_file_destination:\n command_line_arguments.output_playlist_file_destination,\n number_of_results: command_line_arguments.number_of_results,\n filename_matching_regular_expression:\n command_line_arguments.filename_matching_regular_expression\n }\n );\n\n console.log(`Playlist generated: ${output_playlist_path}`);\n } catch (error) {\n const error_message =\n error instanceof Error ? error.message : String(error);\n console.error(error_message);\n console.error('');\n console.error(BuildUsageText());\n process.exitCode = 1;\n }\n}\n\nfunction IsDirectCommandLineExecution(): boolean {\n if (typeof require === 'undefined') {\n return false;\n }\n\n if (typeof module === 'undefined') {\n return false;\n }\n\n return require.main === module;\n}\n\nif (IsDirectCommandLineExecution()) {\n void RunCommandLineApplication();\n}\n"],"mappings":"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","names":["mkdir","readdir","stat","writeFile","path","ShuffleArray","params","values_copy","values","index","length","random_index","Math","floor","random","current_value","VLCPlaylistGenerator","video_extensions","fallback_extensions","extensions_to_use","Set","map","extension","toLowerCase","createPlaylist","validateCreatePlaylistParams","source_directory_path","path","resolve","source_search_directory","output_playlist_path","output_playlist_file_destination","filename_matcher","getFilenameMatcher","filename_matching_regular_expression","filtered_files","findVideoFiles","directory_path","randomized_files","selected_files","slice","min","number_of_results","playlist_contents","buildM3UContents","mkdir","dirname","recursive","writeFile","trim","Error","Number","isInteger","source_search_directory_stats","stat","catch","isDirectory","undefined","RegExp","entries","readdir","withFileTypes","files","entry","absolute_entry_path","join","name","nested_files","push","isFile","isVideoFile","file_name","doesFilenameMatchPattern","absolute_file_path","file_extension","extname","has","lastIndex","test","m3u_lines","selected_file","BuildUsageText","join","ParseRawCommandLineArguments","params","parsed_arguments","help_requested","option_aliases","Map","index","command_line_tokens","length","raw_token","split_index","indexOf","has_inline_value","option_key","slice","parsed_option_key","get","undefined","Error","option_value","startsWith","ParseFilenameMatcher","regex_pattern","filename_matching_regular_expression","regex_flags","filename_matching_regular_expression_flags","RegExp","ParseCommandLineArguments","raw_arguments","source_search_directory","output_playlist_file_destination","number_of_results","parsed_number_of_results","Number","isInteger","RunCommandLineApplication","command_line_arguments","process","argv","console","log","vlc_playlist_generator","VLCPlaylistGenerator","output_playlist_path","createPlaylist","error","error_message","message","String","exitCode","IsDirectCommandLineExecution","require","module","main"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Code Naming Conventions
|
|
2
|
+
|
|
3
|
+
Since all code in typescript is mostly a class, method, variable, or function, we use specific case semantics so that reading code is much easier.
|
|
4
|
+
|
|
5
|
+
- **let some_var:** Variable names use snake_case.
|
|
6
|
+
- **FunctionName:** Individual functions use pascal case.
|
|
7
|
+
- **let some_class = new SomeClass:** Class names are pascal case.
|
|
8
|
+
- **some_class.someMethod:** Method names are camel case.
|
|
9
|
+
|
|
10
|
+
Be aware that this is what we use, not what other librarys that we use, use.
|
|
11
|
+
|
|
12
|
+
# Types/Interfaces/Typescript
|
|
13
|
+
|
|
14
|
+
- **typename_t:** typenames are affixed with \_t.
|
|
15
|
+
- **interfacename_i:** interfaces are affixed with \_i.
|
|
16
|
+
|
|
17
|
+
# Runtime Schema Validation
|
|
18
|
+
|
|
19
|
+
- **sometypename_t_zods:** Zod type schemas are defined by matching their type, along with \_zods.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Zod Schemas
|
|
2
|
+
|
|
3
|
+
We use zod for runtime data validation. The way we do this is by using ts-to-zod, which compiles runtime loadable zod objects from existing typescript types and interfaces.
|
|
4
|
+
|
|
5
|
+
[ts-to-zod github](https://github.com/fabien0102/ts-to-zod)
|
|
6
|
+
|
|
7
|
+
- **Autogenerated Zod Schemas:** ./src/zod_type_validators/
|
|
8
|
+
- **ts-to-zod configuration:** ./ts-to-zod.config.mjs
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opsimathically/vlc_playlist_generator",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A m3u format playlist generator designed for generating input for vlc.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "npx ts-to-zod && tsup",
|
|
10
|
+
"test": "node --require ts-node/register --require tsconfig-paths/register --test test/**/*.test.ts",
|
|
11
|
+
"ts-to-zod": "npx ts-to-zod",
|
|
12
|
+
"docs": "typedoc"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"set_keywords"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/opsimathically/vlc_playlist_generator",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
20
|
+
"@types/node": "^22.19.1",
|
|
21
|
+
"@types/node-forge": "^1.3.11",
|
|
22
|
+
"@types/wtfnode": "^0.7.3",
|
|
23
|
+
"esbuild": "^0.25.0",
|
|
24
|
+
"eslint": "^9.20.1",
|
|
25
|
+
"globals": "^15.14.0",
|
|
26
|
+
"prettier": "^3.5.0",
|
|
27
|
+
"ts-node": "^10.9.2",
|
|
28
|
+
"tsconfig-paths": "^4.2.0",
|
|
29
|
+
"tsup": "^8.5.1",
|
|
30
|
+
"typedoc": "^0.28.1",
|
|
31
|
+
"typedoc-plugin-markdown": "^4.6.0",
|
|
32
|
+
"typescript": "^5.7.3",
|
|
33
|
+
"typescript-eslint": "^8.24.0",
|
|
34
|
+
"wtfnode": "^0.10.0"
|
|
35
|
+
},
|
|
36
|
+
"author": "Jason Medeiros",
|
|
37
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/opsimathically/vlc_playlist_generator.git"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@swc/core": "^1.11.24",
|
|
47
|
+
"@types/command-line-args": "^5.2.3",
|
|
48
|
+
"@types/command-line-usage": "^5.0.4",
|
|
49
|
+
"command-line-args": "^6.0.1",
|
|
50
|
+
"command-line-usage": "^7.0.3",
|
|
51
|
+
"sharp": "^0.34.1",
|
|
52
|
+
"source-map-support": "^0.5.21",
|
|
53
|
+
"ts-to-zod": "^5.1.0",
|
|
54
|
+
"zod": "^4.3.5"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ts-to-zod configuration. Add elements to the exported default array below to extend
|
|
3
|
+
* this configuration across multiple files.
|
|
4
|
+
*
|
|
5
|
+
* @type {import("ts-to-zod").TsToZodConfig}
|
|
6
|
+
*/
|
|
7
|
+
export default [
|
|
8
|
+
{
|
|
9
|
+
name: 'zod-to-ts',
|
|
10
|
+
input: './src/types/custom_zod_types/custom_zod_types.d.ts',
|
|
11
|
+
output:
|
|
12
|
+
'./src/zod_type_validators/custom_ts_to_zod_generated_validators.ts',
|
|
13
|
+
// this is where we define how types are converted into schemas. We just take the type name
|
|
14
|
+
// and append _zods to identify that it's a schema not a type.
|
|
15
|
+
getSchemaName: (id) => id + '_zods'
|
|
16
|
+
}
|
|
17
|
+
];
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ["src/index.ts"],
|
|
5
|
+
format: ["cjs", "esm"],
|
|
6
|
+
dts: true,
|
|
7
|
+
sourcemap: true,
|
|
8
|
+
clean: true,
|
|
9
|
+
external: [
|
|
10
|
+
"./transpilers/swc.js",
|
|
11
|
+
"source-map-support",
|
|
12
|
+
"@cspotcode/source-map-support",
|
|
13
|
+
], // Mark it external
|
|
14
|
+
});
|