tunecamp 1.0.0

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.
Files changed (132) hide show
  1. package/.env.local +2 -0
  2. package/.vercel/README.txt +11 -0
  3. package/.vercel/project.json +1 -0
  4. package/LICENSE +22 -0
  5. package/README.md +554 -0
  6. package/dist/cli.d.ts +6 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +172 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/generator/embedGenerator.d.ts +38 -0
  11. package/dist/generator/embedGenerator.d.ts.map +1 -0
  12. package/dist/generator/embedGenerator.js +92 -0
  13. package/dist/generator/embedGenerator.js.map +1 -0
  14. package/dist/generator/feedGenerator.d.ts +50 -0
  15. package/dist/generator/feedGenerator.d.ts.map +1 -0
  16. package/dist/generator/feedGenerator.js +167 -0
  17. package/dist/generator/feedGenerator.js.map +1 -0
  18. package/dist/generator/podcastFeedGenerator.d.ts +54 -0
  19. package/dist/generator/podcastFeedGenerator.d.ts.map +1 -0
  20. package/dist/generator/podcastFeedGenerator.js +173 -0
  21. package/dist/generator/podcastFeedGenerator.js.map +1 -0
  22. package/dist/generator/proceduralCoverGenerator.d.ts +51 -0
  23. package/dist/generator/proceduralCoverGenerator.d.ts.map +1 -0
  24. package/dist/generator/proceduralCoverGenerator.js +228 -0
  25. package/dist/generator/proceduralCoverGenerator.js.map +1 -0
  26. package/dist/generator/siteGenerator.d.ts +55 -0
  27. package/dist/generator/siteGenerator.d.ts.map +1 -0
  28. package/dist/generator/siteGenerator.js +539 -0
  29. package/dist/generator/siteGenerator.js.map +1 -0
  30. package/dist/generator/templateEngine.d.ts +13 -0
  31. package/dist/generator/templateEngine.d.ts.map +1 -0
  32. package/dist/generator/templateEngine.js +146 -0
  33. package/dist/generator/templateEngine.js.map +1 -0
  34. package/dist/index.d.ts +12 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +32 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/parser/catalogParser.d.ts +13 -0
  39. package/dist/parser/catalogParser.d.ts.map +1 -0
  40. package/dist/parser/catalogParser.js +120 -0
  41. package/dist/parser/catalogParser.js.map +1 -0
  42. package/dist/tools/generate-codes.d.ts +14 -0
  43. package/dist/tools/generate-codes.d.ts.map +1 -0
  44. package/dist/tools/generate-codes.js +274 -0
  45. package/dist/tools/generate-codes.js.map +1 -0
  46. package/dist/tools/generate-sea-pair.d.ts +14 -0
  47. package/dist/tools/generate-sea-pair.d.ts.map +1 -0
  48. package/dist/tools/generate-sea-pair.js +111 -0
  49. package/dist/tools/generate-sea-pair.js.map +1 -0
  50. package/dist/types/index.d.ts +117 -0
  51. package/dist/types/index.d.ts.map +1 -0
  52. package/dist/types/index.js +5 -0
  53. package/dist/types/index.js.map +1 -0
  54. package/dist/utils/audioUtils.d.ts +9 -0
  55. package/dist/utils/audioUtils.d.ts.map +1 -0
  56. package/dist/utils/audioUtils.js +67 -0
  57. package/dist/utils/audioUtils.js.map +1 -0
  58. package/dist/utils/configUtils.d.ts +11 -0
  59. package/dist/utils/configUtils.d.ts.map +1 -0
  60. package/dist/utils/configUtils.js +50 -0
  61. package/dist/utils/configUtils.js.map +1 -0
  62. package/dist/utils/fileUtils.d.ts +14 -0
  63. package/dist/utils/fileUtils.d.ts.map +1 -0
  64. package/dist/utils/fileUtils.js +73 -0
  65. package/dist/utils/fileUtils.js.map +1 -0
  66. package/examples/artist-free/README.md +36 -0
  67. package/examples/artist-paycurtain/README.md +49 -0
  68. package/examples/label/README.md +33 -0
  69. package/gundb-keypair.json +8 -0
  70. package/logo.svg +30 -0
  71. package/package-lock.json +1176 -0
  72. package/package.json +42 -0
  73. package/public/assets/community-registry.js +291 -0
  74. package/public/assets/download-stats.js +263 -0
  75. package/public/assets/player.js +219 -0
  76. package/public/assets/style.css +1170 -0
  77. package/public/assets/theme-widget.js +353 -0
  78. package/public/assets/unlock-codes.js +225 -0
  79. package/public/atom.xml +22 -0
  80. package/public/catalog.m3u +3 -0
  81. package/public/feed.xml +22 -0
  82. package/public/image.png +0 -0
  83. package/public/index.html +249 -0
  84. package/public/logo.svg +30 -0
  85. package/public/releases/chirichetto/Homologo - Chirichetto.wav +0 -0
  86. package/public/releases/chirichetto/cover.png +0 -0
  87. package/public/releases/chirichetto/embed-code.txt +16 -0
  88. package/public/releases/chirichetto/embed-compact.txt +8 -0
  89. package/public/releases/chirichetto/embed.html +39 -0
  90. package/public/releases/chirichetto/index.html +389 -0
  91. package/public/releases/chirichetto/playlist.m3u +3 -0
  92. package/templates/dark/assets/community-registry.js +291 -0
  93. package/templates/dark/assets/download-stats.js +263 -0
  94. package/templates/dark/assets/player.js +219 -0
  95. package/templates/dark/assets/style.css +740 -0
  96. package/templates/dark/index.hbs +73 -0
  97. package/templates/dark/layout.hbs +84 -0
  98. package/templates/dark/release.hbs +212 -0
  99. package/templates/default/assets/community-registry.js +291 -0
  100. package/templates/default/assets/download-stats.js +263 -0
  101. package/templates/default/assets/player.js +219 -0
  102. package/templates/default/assets/style.css +1170 -0
  103. package/templates/default/assets/theme-widget.js +353 -0
  104. package/templates/default/assets/unlock-codes.js +225 -0
  105. package/templates/default/index.hbs +188 -0
  106. package/templates/default/layout.hbs +117 -0
  107. package/templates/default/release.hbs +553 -0
  108. package/templates/minimal/assets/community-registry.js +291 -0
  109. package/templates/minimal/assets/download-stats.js +263 -0
  110. package/templates/minimal/assets/player.js +219 -0
  111. package/templates/minimal/assets/style.css +796 -0
  112. package/templates/minimal/index.hbs +73 -0
  113. package/templates/minimal/layout.hbs +84 -0
  114. package/templates/minimal/release.hbs +212 -0
  115. package/templates/retro/assets/community-registry.js +291 -0
  116. package/templates/retro/assets/download-stats.js +263 -0
  117. package/templates/retro/assets/player.js +219 -0
  118. package/templates/retro/assets/style.css +872 -0
  119. package/templates/retro/index.hbs +73 -0
  120. package/templates/retro/layout.hbs +84 -0
  121. package/templates/retro/release.hbs +212 -0
  122. package/templates/translucent/assets/community-registry.js +291 -0
  123. package/templates/translucent/assets/download-stats.js +263 -0
  124. package/templates/translucent/assets/player.js +219 -0
  125. package/templates/translucent/assets/style.css +1352 -0
  126. package/templates/translucent/index.hbs +73 -0
  127. package/templates/translucent/layout.hbs +84 -0
  128. package/templates/translucent/release.hbs +212 -0
  129. package/website/community.html +492 -0
  130. package/website/index.html +195 -0
  131. package/website/styles.css +396 -0
  132. package/website/tunecamp.svg +30 -0
package/dist/cli.js ADDED
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Command-line interface for Tunecamp
4
+ */
5
+ import { Command } from 'commander';
6
+ import { Tunecamp } from './index.js';
7
+ import fs from 'fs-extra';
8
+ import path from 'path';
9
+ import chalk from 'chalk';
10
+ import { fileURLToPath } from 'url';
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+ const program = new Command();
14
+ program
15
+ .name('tunecamp')
16
+ .description('Static site generator for musicians and labels')
17
+ .version('0.1.0');
18
+ program
19
+ .command('build')
20
+ .description('Build a static site from catalog')
21
+ .argument('<input>', 'Input directory containing catalog')
22
+ .option('-o, --output <dir>', 'Output directory', './public')
23
+ .option('-t, --theme <name>', 'Theme name (overrides catalog.yaml)')
24
+ .option('-b, --basePath <path>', 'Base path for deployment (overrides catalog.yaml)')
25
+ .option('-v, --verbose', 'Verbose output', false)
26
+ .action(async (input, options) => {
27
+ try {
28
+ const generator = new Tunecamp({
29
+ inputDir: path.resolve(input),
30
+ outputDir: path.resolve(options.output),
31
+ theme: options.theme,
32
+ basePath: options.basePath,
33
+ verbose: options.verbose,
34
+ });
35
+ await generator.build();
36
+ }
37
+ catch (error) {
38
+ console.error(chalk.red('Error:'), error);
39
+ process.exit(1);
40
+ }
41
+ });
42
+ program
43
+ .command('init')
44
+ .description('Initialize a new catalog')
45
+ .argument('<directory>', 'Directory to initialize')
46
+ .action(async (directory) => {
47
+ try {
48
+ const targetDir = path.resolve(directory);
49
+ if (await fs.pathExists(targetDir)) {
50
+ const files = await fs.readdir(targetDir);
51
+ if (files.length > 0) {
52
+ console.error(chalk.red('Error: Directory is not empty'));
53
+ process.exit(1);
54
+ }
55
+ }
56
+ await initializeCatalog(targetDir);
57
+ console.log(chalk.green('✅ Catalog initialized successfully!'));
58
+ console.log(chalk.blue(`\nNext steps:`));
59
+ console.log(` cd ${directory}`);
60
+ console.log(` # Add your music files to releases/`);
61
+ console.log(` tunecamp build . -o public`);
62
+ }
63
+ catch (error) {
64
+ console.error(chalk.red('Error:'), error);
65
+ process.exit(1);
66
+ }
67
+ });
68
+ program
69
+ .command('serve')
70
+ .description('Serve the generated site locally')
71
+ .argument('[directory]', 'Directory to serve', './public')
72
+ .option('-p, --port <port>', 'Port number', '3000')
73
+ .action(async (directory, options) => {
74
+ const http = await import('http');
75
+ const fs = await import('fs');
76
+ const path = await import('path');
77
+ const port = parseInt(options.port);
78
+ const dir = path.resolve(directory);
79
+ const server = http.createServer((req, res) => {
80
+ let filePath = path.join(dir, req.url === '/' ? 'index.html' : req.url);
81
+ fs.readFile(filePath, (err, data) => {
82
+ if (err) {
83
+ res.writeHead(404);
84
+ res.end('Not found');
85
+ return;
86
+ }
87
+ const ext = path.extname(filePath);
88
+ const contentTypes = {
89
+ '.html': 'text/html',
90
+ '.css': 'text/css',
91
+ '.js': 'text/javascript',
92
+ '.json': 'application/json',
93
+ '.png': 'image/png',
94
+ '.jpg': 'image/jpeg',
95
+ '.jpeg': 'image/jpeg',
96
+ '.gif': 'image/gif',
97
+ '.svg': 'image/svg+xml',
98
+ '.mp3': 'audio/mpeg',
99
+ '.flac': 'audio/flac',
100
+ '.ogg': 'audio/ogg',
101
+ '.wav': 'audio/wav',
102
+ };
103
+ res.writeHead(200, { 'Content-Type': contentTypes[ext] || 'text/plain' });
104
+ res.end(data);
105
+ });
106
+ });
107
+ server.listen(port, () => {
108
+ console.log(chalk.green(`🎵 Server running at http://localhost:${port}`));
109
+ console.log(chalk.blue(`Serving: ${dir}`));
110
+ });
111
+ });
112
+ async function initializeCatalog(targetDir) {
113
+ await fs.ensureDir(targetDir);
114
+ // Create catalog.yaml
115
+ const catalogYaml = `title: "My Music Catalog"
116
+ description: "Independent music releases"
117
+ url: "https://example.com"
118
+ basePath: "" # Leave empty for root deployment, use "/repo-name" for subdirectories
119
+ theme: "default"
120
+ language: "en"
121
+ `;
122
+ await fs.writeFile(path.join(targetDir, 'catalog.yaml'), catalogYaml);
123
+ // Create artist.yaml
124
+ const artistYaml = `name: "Artist Name"
125
+ bio: "Write your biography here."
126
+ links:
127
+ - website: "https://example.com"
128
+ - bandcamp: "https://artistname.bandcamp.com"
129
+ `;
130
+ await fs.writeFile(path.join(targetDir, 'artist.yaml'), artistYaml);
131
+ // Create releases directory with example
132
+ const exampleReleaseDir = path.join(targetDir, 'releases', 'example-album');
133
+ await fs.ensureDir(path.join(exampleReleaseDir, 'tracks'));
134
+ const releaseYaml = `title: "Example Album"
135
+ date: "${new Date().toISOString().split('T')[0]}"
136
+ description: "An amazing album"
137
+ download: "free"
138
+ genres:
139
+ - "Electronic"
140
+ - "Experimental"
141
+ `;
142
+ await fs.writeFile(path.join(exampleReleaseDir, 'release.yaml'), releaseYaml);
143
+ // Create README
144
+ const readme = `# My Music Catalog
145
+
146
+ This is your Tunecamp catalog.
147
+
148
+ ## Structure
149
+
150
+ - \`catalog.yaml\` - Main catalog configuration
151
+ - \`artist.yaml\` - Artist information
152
+ - \`releases/\` - Your music releases
153
+ - Each subdirectory is a release
154
+ - Add \`release.yaml\` to configure each release
155
+ - Add audio files and cover art
156
+
157
+ ## Usage
158
+
159
+ 1. Add your music files to \`releases/your-album-name/tracks/\`
160
+ 2. Add cover art (cover.jpg, cover.png, etc.)
161
+ 3. Configure \`release.yaml\` for each album
162
+ 4. Build: \`tunecamp build . -o public\`
163
+ 5. Deploy the \`public\` folder
164
+
165
+ ## Documentation
166
+
167
+ See https://github.com/scobru/tunecamp for full documentation.
168
+ `;
169
+ await fs.writeFile(path.join(targetDir, 'README.md'), readme);
170
+ }
171
+ program.parse();
172
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Embed Widget Generator for Tunecamp
3
+ * Creates embeddable HTML widgets for releases
4
+ */
5
+ import { Catalog, Release } from "../types/index.js";
6
+ export interface EmbedOptions {
7
+ siteUrl: string;
8
+ basePath: string;
9
+ }
10
+ /**
11
+ * Generates embeddable HTML widgets
12
+ */
13
+ export declare class EmbedGenerator {
14
+ private catalog;
15
+ private options;
16
+ constructor(catalog: Catalog, options: EmbedOptions);
17
+ /**
18
+ * Get the full URL for a path
19
+ */
20
+ private getUrl;
21
+ /**
22
+ * Generate embed HTML for a release
23
+ */
24
+ generateReleaseEmbed(release: Release): string;
25
+ /**
26
+ * Generate compact embed (smaller widget)
27
+ */
28
+ generateCompactEmbed(release: Release): string;
29
+ /**
30
+ * Generate iframe embed code
31
+ */
32
+ generateIframeEmbed(release: Release, width?: number, height?: number): string;
33
+ /**
34
+ * Escape HTML special characters
35
+ */
36
+ private escapeHtml;
37
+ }
38
+ //# sourceMappingURL=embedGenerator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedGenerator.d.ts","sourceRoot":"","sources":["../../src/generator/embedGenerator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,cAAc;IACvB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,OAAO,CAAe;gBAElB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY;IAKnD;;OAEG;IACH,OAAO,CAAC,MAAM;IAMd;;OAEG;IACH,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM;IA8B9C;;OAEG;IACH,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM;IAiB9C;;OAEG;IACH,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,GAAE,MAAY,EAAE,MAAM,GAAE,MAAY,GAAG,MAAM;IAKxF;;OAEG;IACH,OAAO,CAAC,UAAU;CAQrB"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Embed Widget Generator for Tunecamp
3
+ * Creates embeddable HTML widgets for releases
4
+ */
5
+ import path from "path";
6
+ /**
7
+ * Generates embeddable HTML widgets
8
+ */
9
+ export class EmbedGenerator {
10
+ catalog;
11
+ options;
12
+ constructor(catalog, options) {
13
+ this.catalog = catalog;
14
+ this.options = options;
15
+ }
16
+ /**
17
+ * Get the full URL for a path
18
+ */
19
+ getUrl(relativePath) {
20
+ const base = this.options.siteUrl.replace(/\/$/, "");
21
+ const basePath = this.options.basePath || "";
22
+ return `${base}${basePath}/${relativePath}`.replace(/([^:]\/)\/+/g, "$1");
23
+ }
24
+ /**
25
+ * Generate embed HTML for a release
26
+ */
27
+ generateReleaseEmbed(release) {
28
+ const releaseUrl = this.getUrl(`releases/${release.slug}/index.html`);
29
+ const coverUrl = release.coverPath
30
+ ? this.getUrl(`releases/${release.slug}/${path.basename(release.coverPath)}`)
31
+ : null;
32
+ const artistName = this.catalog.artist?.name || "Unknown Artist";
33
+ const trackCount = release.tracks.length;
34
+ const firstTrackUrl = release.tracks.length > 0
35
+ ? this.getUrl(`releases/${release.slug}/${path.basename(release.tracks[0].file)}`)
36
+ : null;
37
+ // Compact embed widget with inline styles
38
+ return `<!-- Tunecamp Embed: ${release.config.title} -->
39
+ <div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;max-width:400px;background:#1e293b;border-radius:12px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,0.3);">
40
+ <a href="${releaseUrl}" target="_blank" style="text-decoration:none;color:inherit;display:block;">
41
+ ${coverUrl ? `<img src="${coverUrl}" alt="${this.escapeHtml(release.config.title)}" style="width:100%;display:block;aspect-ratio:1;object-fit:cover;">` : `<div style="width:100%;aspect-ratio:1;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;font-size:4rem;color:rgba(255,255,255,0.3);">♪</div>`}
42
+ <div style="padding:1rem;">
43
+ <div style="font-size:1.1rem;font-weight:600;color:#f1f5f9;margin-bottom:0.25rem;">${this.escapeHtml(release.config.title)}</div>
44
+ <div style="font-size:0.9rem;color:#94a3b8;">${this.escapeHtml(artistName)}</div>
45
+ <div style="font-size:0.8rem;color:#64748b;margin-top:0.5rem;">${trackCount} track${trackCount !== 1 ? 's' : ''}</div>
46
+ </div>
47
+ </a>
48
+ ${firstTrackUrl ? `<audio controls style="width:100%;height:40px;" preload="none"><source src="${firstTrackUrl}" type="audio/mpeg">Your browser does not support audio.</audio>` : ''}
49
+ <div style="padding:0.5rem 1rem;background:#0f172a;font-size:0.7rem;color:#64748b;text-align:right;">
50
+ Powered by <a href="https://github.com/scobru/tunecamp" target="_blank" style="color:#6366f1;text-decoration:none;">Tunecamp</a>
51
+ </div>
52
+ </div>
53
+ <!-- End Tunecamp Embed -->`;
54
+ }
55
+ /**
56
+ * Generate compact embed (smaller widget)
57
+ */
58
+ generateCompactEmbed(release) {
59
+ const releaseUrl = this.getUrl(`releases/${release.slug}/index.html`);
60
+ const coverUrl = release.coverPath
61
+ ? this.getUrl(`releases/${release.slug}/${path.basename(release.coverPath)}`)
62
+ : null;
63
+ const artistName = this.catalog.artist?.name || "Unknown Artist";
64
+ return `<!-- Tunecamp Compact Embed -->
65
+ <a href="${releaseUrl}" target="_blank" style="display:inline-flex;align-items:center;gap:0.75rem;padding:0.5rem;background:#1e293b;border-radius:8px;text-decoration:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;max-width:300px;">
66
+ ${coverUrl ? `<img src="${coverUrl}" alt="${this.escapeHtml(release.config.title)}" style="width:48px;height:48px;border-radius:4px;object-fit:cover;">` : `<div style="width:48px;height:48px;border-radius:4px;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:flex;align-items:center;justify-content:center;color:rgba(255,255,255,0.5);">♪</div>`}
67
+ <div style="min-width:0;">
68
+ <div style="font-size:0.9rem;font-weight:500;color:#f1f5f9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${this.escapeHtml(release.config.title)}</div>
69
+ <div style="font-size:0.75rem;color:#94a3b8;">${this.escapeHtml(artistName)}</div>
70
+ </div>
71
+ </a>`;
72
+ }
73
+ /**
74
+ * Generate iframe embed code
75
+ */
76
+ generateIframeEmbed(release, width = 400, height = 300) {
77
+ const embedUrl = this.getUrl(`releases/${release.slug}/embed.html`);
78
+ return `<iframe src="${embedUrl}" width="${width}" height="${height}" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>`;
79
+ }
80
+ /**
81
+ * Escape HTML special characters
82
+ */
83
+ escapeHtml(str) {
84
+ return str
85
+ .replace(/&/g, "&amp;")
86
+ .replace(/</g, "&lt;")
87
+ .replace(/>/g, "&gt;")
88
+ .replace(/"/g, "&quot;")
89
+ .replace(/'/g, "&#039;");
90
+ }
91
+ }
92
+ //# sourceMappingURL=embedGenerator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedGenerator.js","sourceRoot":"","sources":["../../src/generator/embedGenerator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * RSS and Atom Feed Generator for Tunecamp
3
+ * Generates feed.xml (RSS 2.0) and atom.xml feeds
4
+ */
5
+ import { Catalog } from "../types/index.js";
6
+ export interface FeedOptions {
7
+ siteUrl: string;
8
+ basePath: string;
9
+ }
10
+ /**
11
+ * Generates RSS 2.0 and Atom feeds from catalog
12
+ */
13
+ export declare class FeedGenerator {
14
+ private catalog;
15
+ private options;
16
+ constructor(catalog: Catalog, options: FeedOptions);
17
+ /**
18
+ * Get the full URL for a path
19
+ */
20
+ private getUrl;
21
+ /**
22
+ * Format date for RSS (RFC 822)
23
+ */
24
+ private formatRssDate;
25
+ /**
26
+ * Format date for Atom (ISO 8601)
27
+ */
28
+ private formatAtomDate;
29
+ /**
30
+ * Escape XML special characters
31
+ */
32
+ private escapeXml;
33
+ /**
34
+ * Generate RSS 2.0 feed
35
+ */
36
+ generateRssFeed(): string;
37
+ /**
38
+ * Generate a single RSS item for a release
39
+ */
40
+ private generateRssItem;
41
+ /**
42
+ * Generate Atom feed
43
+ */
44
+ generateAtomFeed(): string;
45
+ /**
46
+ * Generate a single Atom entry for a release
47
+ */
48
+ private generateAtomEntry;
49
+ }
50
+ //# sourceMappingURL=feedGenerator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedGenerator.d.ts","sourceRoot":"","sources":["../../src/generator/feedGenerator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,OAAO,EAAW,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,aAAa;IACtB,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,OAAO,CAAc;gBAEjB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW;IAKlD;;OAEG;IACH,OAAO,CAAC,MAAM;IAMd;;OAEG;IACH,OAAO,CAAC,aAAa;IAKrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAKtB;;OAEG;IACH,OAAO,CAAC,SAAS;IASjB;;OAEG;IACH,eAAe,IAAI,MAAM;IAmCzB;;OAEG;IACH,OAAO,CAAC,eAAe;IA0BvB;;OAEG;IACH,gBAAgB,IAAI,MAAM;IAkC1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;CA6B5B"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * RSS and Atom Feed Generator for Tunecamp
3
+ * Generates feed.xml (RSS 2.0) and atom.xml feeds
4
+ */
5
+ import path from "path";
6
+ /**
7
+ * Generates RSS 2.0 and Atom feeds from catalog
8
+ */
9
+ export class FeedGenerator {
10
+ catalog;
11
+ options;
12
+ constructor(catalog, options) {
13
+ this.catalog = catalog;
14
+ this.options = options;
15
+ }
16
+ /**
17
+ * Get the full URL for a path
18
+ */
19
+ getUrl(relativePath) {
20
+ const base = this.options.siteUrl.replace(/\/$/, "");
21
+ const basePath = this.options.basePath || "";
22
+ return `${base}${basePath}/${relativePath}`.replace(/([^:]\/)\/+/g, "$1");
23
+ }
24
+ /**
25
+ * Format date for RSS (RFC 822)
26
+ */
27
+ formatRssDate(dateStr) {
28
+ const date = new Date(dateStr);
29
+ return date.toUTCString();
30
+ }
31
+ /**
32
+ * Format date for Atom (ISO 8601)
33
+ */
34
+ formatAtomDate(dateStr) {
35
+ const date = new Date(dateStr);
36
+ return date.toISOString();
37
+ }
38
+ /**
39
+ * Escape XML special characters
40
+ */
41
+ escapeXml(str) {
42
+ return str
43
+ .replace(/&/g, "&amp;")
44
+ .replace(/</g, "&lt;")
45
+ .replace(/>/g, "&gt;")
46
+ .replace(/"/g, "&quot;")
47
+ .replace(/'/g, "&apos;");
48
+ }
49
+ /**
50
+ * Generate RSS 2.0 feed
51
+ */
52
+ generateRssFeed() {
53
+ const title = this.escapeXml(this.catalog.config.title);
54
+ const description = this.escapeXml(this.catalog.config.description || "Music releases");
55
+ const link = this.getUrl("");
56
+ const language = this.catalog.config.language || "en";
57
+ const artistName = this.catalog.artist?.name || "Unknown Artist";
58
+ const now = new Date().toUTCString();
59
+ // Sort releases by date (newest first)
60
+ const sortedReleases = [...this.catalog.releases].sort((a, b) => new Date(b.config.date).getTime() - new Date(a.config.date).getTime());
61
+ const items = sortedReleases
62
+ .filter((release) => !release.config.unlisted)
63
+ .map((release) => this.generateRssItem(release))
64
+ .join("\n");
65
+ return `<?xml version="1.0" encoding="UTF-8"?>
66
+ <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
67
+ <channel>
68
+ <title>${title}</title>
69
+ <link>${link}</link>
70
+ <description>${description}</description>
71
+ <language>${language}</language>
72
+ <lastBuildDate>${now}</lastBuildDate>
73
+ <generator>Tunecamp</generator>
74
+ <atom:link href="${this.getUrl("feed.xml")}" rel="self" type="application/rss+xml"/>
75
+ <itunes:author>${this.escapeXml(artistName)}</itunes:author>
76
+ <itunes:category text="Music"/>
77
+ ${items}
78
+ </channel>
79
+ </rss>`;
80
+ }
81
+ /**
82
+ * Generate a single RSS item for a release
83
+ */
84
+ generateRssItem(release) {
85
+ const title = this.escapeXml(release.config.title);
86
+ const description = this.escapeXml(release.config.description || `New release: ${release.config.title}`);
87
+ const link = this.getUrl(`releases/${release.slug}/index.html`);
88
+ const pubDate = this.formatRssDate(release.config.date);
89
+ const guid = this.getUrl(`releases/${release.slug}/`);
90
+ let coverEnclosure = "";
91
+ if (release.coverPath) {
92
+ const coverUrl = this.getUrl(`releases/${release.slug}/${path.basename(release.coverPath)}`);
93
+ coverEnclosure = ` <enclosure url="${coverUrl}" type="image/jpeg"/>`;
94
+ }
95
+ const trackCount = release.tracks.length;
96
+ const genres = release.config.genres?.map((g) => this.escapeXml(g)).join(", ") || "";
97
+ return ` <item>
98
+ <title>${title}</title>
99
+ <link>${link}</link>
100
+ <description><![CDATA[${description}${trackCount > 0 ? ` (${trackCount} tracks)` : ""}${genres ? ` - ${genres}` : ""}]]></description>
101
+ <pubDate>${pubDate}</pubDate>
102
+ <guid isPermaLink="true">${guid}</guid>
103
+ ${coverEnclosure}
104
+ </item>`;
105
+ }
106
+ /**
107
+ * Generate Atom feed
108
+ */
109
+ generateAtomFeed() {
110
+ const title = this.escapeXml(this.catalog.config.title);
111
+ const subtitle = this.escapeXml(this.catalog.config.description || "Music releases");
112
+ const link = this.getUrl("");
113
+ const feedUrl = this.getUrl("atom.xml");
114
+ const artistName = this.catalog.artist?.name || "Unknown Artist";
115
+ const now = this.formatAtomDate(new Date().toISOString());
116
+ // Sort releases by date (newest first)
117
+ const sortedReleases = [...this.catalog.releases].sort((a, b) => new Date(b.config.date).getTime() - new Date(a.config.date).getTime());
118
+ const entries = sortedReleases
119
+ .filter((release) => !release.config.unlisted)
120
+ .map((release) => this.generateAtomEntry(release))
121
+ .join("\n");
122
+ return `<?xml version="1.0" encoding="UTF-8"?>
123
+ <feed xmlns="http://www.w3.org/2005/Atom">
124
+ <title>${title}</title>
125
+ <subtitle>${subtitle}</subtitle>
126
+ <link href="${link}" rel="alternate" type="text/html"/>
127
+ <link href="${feedUrl}" rel="self" type="application/atom+xml"/>
128
+ <id>${link}</id>
129
+ <updated>${now}</updated>
130
+ <generator uri="https://github.com/scobru/tunecamp" version="0.1.0">Tunecamp</generator>
131
+ <author>
132
+ <name>${this.escapeXml(artistName)}</name>
133
+ </author>
134
+ ${entries}
135
+ </feed>`;
136
+ }
137
+ /**
138
+ * Generate a single Atom entry for a release
139
+ */
140
+ generateAtomEntry(release) {
141
+ const title = this.escapeXml(release.config.title);
142
+ const summary = this.escapeXml(release.config.description || `New release: ${release.config.title}`);
143
+ const link = this.getUrl(`releases/${release.slug}/index.html`);
144
+ const id = this.getUrl(`releases/${release.slug}/`);
145
+ const updated = this.formatAtomDate(release.config.date);
146
+ const published = this.formatAtomDate(release.config.date);
147
+ const trackCount = release.tracks.length;
148
+ const genres = release.config.genres?.join(", ") || "";
149
+ let content = `<p>${summary}</p>`;
150
+ if (trackCount > 0) {
151
+ content += `<p>Tracks: ${trackCount}</p>`;
152
+ }
153
+ if (genres) {
154
+ content += `<p>Genres: ${genres}</p>`;
155
+ }
156
+ return ` <entry>
157
+ <title>${title}</title>
158
+ <link href="${link}" rel="alternate" type="text/html"/>
159
+ <id>${id}</id>
160
+ <updated>${updated}</updated>
161
+ <published>${published}</published>
162
+ <summary>${summary}</summary>
163
+ <content type="html"><![CDATA[${content}]]></content>
164
+ </entry>`;
165
+ }
166
+ }
167
+ //# sourceMappingURL=feedGenerator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedGenerator.js","sourceRoot":"","sources":["../../src/generator/feedGenerator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Podcast RSS Feed Generator for Tunecamp
3
+ * Creates podcast-specific RSS feeds for episodic content
4
+ */
5
+ import { Catalog } from "../types/index.js";
6
+ export interface PodcastOptions {
7
+ siteUrl: string;
8
+ basePath: string;
9
+ podcastTitle?: string;
10
+ podcastDescription?: string;
11
+ podcastAuthor?: string;
12
+ podcastEmail?: string;
13
+ podcastCategory?: string;
14
+ podcastImage?: string;
15
+ explicit?: boolean;
16
+ }
17
+ /**
18
+ * Generates podcast-specific RSS 2.0 feeds
19
+ */
20
+ export declare class PodcastFeedGenerator {
21
+ private catalog;
22
+ private options;
23
+ constructor(catalog: Catalog, options: PodcastOptions);
24
+ /**
25
+ * Get the full URL for a path
26
+ */
27
+ private getUrl;
28
+ /**
29
+ * Format date for RSS (RFC 822)
30
+ */
31
+ private formatRssDate;
32
+ /**
33
+ * Escape XML special characters
34
+ */
35
+ private escapeXml;
36
+ /**
37
+ * Format duration in HH:MM:SS format for iTunes
38
+ */
39
+ private formatItunesDuration;
40
+ /**
41
+ * Get MIME type for audio file
42
+ */
43
+ private getAudioMimeType;
44
+ /**
45
+ * Generate Podcast RSS feed
46
+ * Each track becomes an episode
47
+ */
48
+ generatePodcastFeed(): string;
49
+ /**
50
+ * Generate a single episode item
51
+ */
52
+ private generateEpisodeItem;
53
+ }
54
+ //# sourceMappingURL=podcastFeedGenerator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"podcastFeedGenerator.d.ts","sourceRoot":"","sources":["../../src/generator/podcastFeedGenerator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,OAAO,EAA0B,MAAM,mBAAmB,CAAC;AAEpE,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,qBAAa,oBAAoB;IAC7B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,OAAO,CAAiB;gBAEpB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc;IAKrD;;OAEG;IACH,OAAO,CAAC,MAAM;IAMd;;OAEG;IACH,OAAO,CAAC,aAAa;IAKrB;;OAEG;IACH,OAAO,CAAC,SAAS;IASjB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAQ5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;;OAGG;IACH,mBAAmB,IAAI,MAAM;IA2E7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;CAiC9B"}