Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2cfcb4c
feat(export): add wrap-in-folder and zip output options.
Luke-Bilhorn Mar 1, 2026
d5e457e
Include HTML/audio in wrap/zip, auto-wrap on 3+ files, avoid name col…
Luke-Bilhorn Mar 1, 2026
340552b
- Updated to use JSzip and - Removed uses of Archiver, - Updated to i…
Luke-Bilhorn Mar 2, 2026
85995df
Revert "- Updated to use JSzip and - Removed uses of Archiver, - Upda…
Luke-Bilhorn Mar 23, 2026
e687add
Audio is included in exports.
Luke-Bilhorn Mar 24, 2026
4c85453
feat(export): always wrap output; simplify output folder structure.
Luke-Bilhorn Mar 24, 2026
c9592b2
persist last folder used for downlading exports
Luke-Bilhorn Mar 24, 2026
087876a
Separated the include timestamps checkbox from the audio export butto…
Luke-Bilhorn Mar 25, 2026
2c48c28
Make the unvalidated USFM card match the others and fix show/hide bugs.
Luke-Bilhorn Mar 26, 2026
5206e2b
Persist text export option selection if going steps 2-> 3-> 2
Luke-Bilhorn Mar 30, 2026
ceec58f
Merge branch '672-from-dev-now' into dev-tools-sql-with-lukes-changes
TimRl Mar 30, 2026
c842852
Implemented audio exporter initialization and update FFmpeg path retr…
TimRl Mar 30, 2026
7629b2c
Enhanced metadata synchronization in project configuration
TimRl Mar 30, 2026
81e4bcb
added error handling for output stream in zipDirectory function
TimRl Mar 30, 2026
e90bc79
Merge branch 'dev-tools-sql' into dev-tools-sql-with-lukes-changes
TimRl Mar 30, 2026
be7bf97
Merge branch 'dev' into dev-tools-sql-with-lukes-changes
TimRl Mar 30, 2026
bdbb605
Refactored USFM export options in projectExportView.ts. Removed hidde…
TimRl Mar 31, 2026
19640c5
Removed zip output functionality from export options and related UI e…
TimRl Mar 31, 2026
15e5171
Merge branch 'dev-tools-sql-with-lukes-changes' into 672-from-dev-now
TimRl Mar 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/activationHelpers/contextAware/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from "../../utils/codexNotebookUtils";
import { jumpToCellInNotebook } from "../../utils";
import { setTargetFont } from "../../projectManager/projectInitializers";
import { CodexExportFormat, exportCodexContent } from "../../exportHandler/exportHandler";
import { CodexExportFormat, exportCodexContent, type ExportOptions } from "../../exportHandler/exportHandler";


import { createEditAnalysisProvider } from "../../providers/EditAnalysisView/EditAnalysisViewProvider";
Expand Down Expand Up @@ -158,7 +158,7 @@ export async function registerCommands(context: vscode.ExtensionContext) {
format: CodexExportFormat;
userSelectedPath: string;
filesToExport: string[];
options?: { skipValidation?: boolean; removeIds?: boolean; };
options?: ExportOptions;
}) => {
await exportCodexContent(format, userSelectedPath, filesToExport, options);
}
Expand Down
30 changes: 11 additions & 19 deletions src/exportHandler/audioExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import { getFFmpegPath } from "../utils/ffmpegManager";

const execAsync = promisify(exec);

let extensionContext: vscode.ExtensionContext | undefined;

export const initializeAudioExporter = (context: vscode.ExtensionContext): void => {
extensionContext = context;
};

// Debug logging for audio export diagnostics
const DEBUG = false;
function debug(...args: any[]) {
Expand All @@ -29,11 +35,6 @@ function sanitizeFileComponent(input: string): string {
.replace(/_+/g, "_");
}

function formatDateForFolder(d: Date): string {
const pad = (n: number, w = 2) => String(n).padStart(w, "0");
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
}

// REMOVE: This doesn't seem to be used anywhere
/**
* Parses a cell reference ID (from globalReferences) to extract book, chapter, and verse.
Expand Down Expand Up @@ -358,7 +359,7 @@ async function convertToWav(
originalExt: string,
sampleRate: number = 48000
): Promise<Uint8Array> {
const ffmpegBinaryPath = await getFFmpegPath();
const ffmpegBinaryPath = await getFFmpegPath(extensionContext);
if (!ffmpegBinaryPath) {
throw new Error("FFmpeg not available");
}
Expand Down Expand Up @@ -481,17 +482,8 @@ export async function exportAudioAttachments(
}
const workspaceFolder = workspaceFolders[0];

// Resolve project name
const projectConfig = vscode.workspace.getConfiguration("codex-project-manager");
let projectName = projectConfig.get<string>("projectName", "");
if (!projectName) {
projectName = basename(workspaceFolder.uri.fsPath);
}

const dateStamp = formatDateForFolder(new Date());
const exportRoot = vscode.Uri.file(userSelectedPath);
const finalExportDir = vscode.Uri.joinPath(exportRoot, "export", `${sanitizeFileComponent(projectName)}-${dateStamp}`);
await vscode.workspace.fs.createDirectory(finalExportDir);
const exportDir = vscode.Uri.file(userSelectedPath);
await vscode.workspace.fs.createDirectory(exportDir);

const includeTimestamps = !!options?.includeTimestamps;
const selectedFiles = filesToExport.map((p) => vscode.Uri.file(p));
Expand All @@ -516,7 +508,7 @@ export async function exportAudioAttachments(
progress.report({ message: `Processing ${basename(file.fsPath)} (${index + 1}/${selectedFiles.length})`, increment });

const bookCode = basename(file.fsPath).split(".")[0] || "BOOK";
const bookFolder = vscode.Uri.joinPath(finalExportDir, sanitizeFileComponent(bookCode));
const bookFolder = vscode.Uri.joinPath(exportDir, sanitizeFileComponent(bookCode));
await vscode.workspace.fs.createDirectory(bookFolder);

let notebook: CodexNotebookAsJSONData;
Expand Down Expand Up @@ -629,7 +621,7 @@ export async function exportAudioAttachments(
}

debug(`Export summary: ${copiedCount} files copied, ${missingCount} skipped`);
vscode.window.showInformationMessage(`Audio export completed: ${copiedCount} files copied${missingCount ? `, ${missingCount} skipped` : ""}. Output: ${finalExportDir.fsPath}`);
vscode.window.showInformationMessage(`Audio export completed: ${copiedCount} files copied${missingCount ? `, ${missingCount} skipped` : ""}. Output: ${exportDir.fsPath}`);
}
);
}
Expand Down
59 changes: 38 additions & 21 deletions src/exportHandler/exportHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as vscode from "vscode";
import JSZip from "jszip";
import { CodexCellTypes } from "../../types/enums";
import { basename } from "path";
import * as path from "path";
Expand All @@ -8,6 +7,7 @@ import { exec } from "child_process";
import { promisify } from "util";
import { removeHtmlTags, generateSrtData } from "./subtitleUtils";
import { generateVttData } from "./vttUtils";

// import { exportRtfWithPandoc } from "../../webviews/codex-webviews/src/NewSourceUploader/importers/rtf/pandocNodeBridge";

const execAsync = promisify(exec);
Expand Down Expand Up @@ -215,6 +215,8 @@ export enum CodexExportFormat {
export interface ExportOptions {
skipValidation?: boolean;
removeIds?: boolean;
includeAudio?: boolean;
includeTimestamps?: boolean;
}

// IDML Round-trip export: Uses idmlExporter or biblicaExporter based on filename
Expand Down Expand Up @@ -1608,65 +1610,80 @@ export async function exportCodexContent(
filesToExport: string[],
options?: ExportOptions
) {
// Check if audio export should also be included alongside the text format export
const includeAudio = (options as any)?.includeAudio === true && format !== CodexExportFormat.AUDIO;
const includeAudio = options?.includeAudio === true && format !== CodexExportFormat.AUDIO;
const isMulti = includeAudio;

// Always create a wrapper folder
const projectConfig = vscode.workspace.getConfiguration("codex-project-manager");
const projectName = projectConfig.get<string>("projectName", "") ||
basename(vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? "export");
const dateStamp = new Date().toISOString().slice(0, 10);
const formatLabel = isMulti ? "multi" : format;
const baseName = `${projectName}-${formatLabel}-${dateStamp}`;
let candidate = path.join(userSelectedPath, baseName);
let suffix = 1;
while (fs.existsSync(candidate)) {
candidate = path.join(userSelectedPath, `${baseName}-${suffix}`);
suffix++;
}
const wrapperPath = candidate;

// In multi mode, each export type gets its own subfolder
const formatPath = isMulti ? path.join(wrapperPath, format) : wrapperPath;
const audioPath = isMulti ? path.join(wrapperPath, "audio") : wrapperPath;

// Prepare export promises
const exportPromises: Promise<void>[] = [];

// Add text format export
switch (format) {
case CodexExportFormat.PLAINTEXT:
exportPromises.push(exportCodexContentAsPlaintext(userSelectedPath, filesToExport, options));
exportPromises.push(exportCodexContentAsPlaintext(formatPath, filesToExport, options));
break;
case CodexExportFormat.USFM:
exportPromises.push(exportCodexContentAsUsfm(userSelectedPath, filesToExport, options));
exportPromises.push(exportCodexContentAsUsfm(formatPath, filesToExport, options));
break;
case CodexExportFormat.HTML:
exportPromises.push(exportCodexContentAsHtml(userSelectedPath, filesToExport, options));
exportPromises.push(exportCodexContentAsHtml(formatPath, filesToExport, options));
break;
case CodexExportFormat.AUDIO: {
const { exportAudioAttachments } = await import("./audioExporter");
exportPromises.push(exportAudioAttachments(userSelectedPath, filesToExport, { includeTimestamps: (options as any)?.includeTimestamps }));
exportPromises.push(exportAudioAttachments(wrapperPath, filesToExport, { includeTimestamps: options?.includeTimestamps }));
break;
}
case CodexExportFormat.SUBTITLES_VTT_WITH_STYLES:
exportPromises.push(exportCodexContentAsSubtitlesVtt(userSelectedPath, filesToExport, options, true));
exportPromises.push(exportCodexContentAsSubtitlesVtt(formatPath, filesToExport, options, true));
break;
case CodexExportFormat.SUBTITLES_VTT_WITHOUT_STYLES:
exportPromises.push(exportCodexContentAsSubtitlesVtt(userSelectedPath, filesToExport, options, false));
exportPromises.push(exportCodexContentAsSubtitlesVtt(formatPath, filesToExport, options, false));
break;
case CodexExportFormat.SUBTITLES_SRT:
exportPromises.push(exportCodexContentAsSubtitlesSrt(userSelectedPath, filesToExport, options));
exportPromises.push(exportCodexContentAsSubtitlesSrt(formatPath, filesToExport, options));
break;
case CodexExportFormat.XLIFF:
exportPromises.push(exportCodexContentAsXliff(userSelectedPath, filesToExport, options));
exportPromises.push(exportCodexContentAsXliff(formatPath, filesToExport, options));
break;
case CodexExportFormat.CSV:
exportPromises.push(exportCodexContentAsCsv(userSelectedPath, filesToExport, options));
exportPromises.push(exportCodexContentAsCsv(formatPath, filesToExport, options));
break;
case CodexExportFormat.TSV:
exportPromises.push(exportCodexContentAsTsv(userSelectedPath, filesToExport, options));
exportPromises.push(exportCodexContentAsTsv(formatPath, filesToExport, options));
break;
case CodexExportFormat.REBUILD_EXPORT:
exportPromises.push(exportCodexContentAsRebuild(userSelectedPath, filesToExport, options));
exportPromises.push(exportCodexContentAsRebuild(formatPath, filesToExport, options));
break;
case CodexExportFormat.BACKTRANSLATIONS:
exportPromises.push(exportCodexContentAsBacktranslations(userSelectedPath, filesToExport, options));
exportPromises.push(exportCodexContentAsBacktranslations(formatPath, filesToExport, options));
break;
}

// Add audio export if requested alongside text format
if (includeAudio) {
const { exportAudioAttachments } = await import("./audioExporter");
exportPromises.push(
exportAudioAttachments(userSelectedPath, filesToExport, {
includeTimestamps: (options as any)?.includeTimestamps
exportAudioAttachments(audioPath, filesToExport, {
includeTimestamps: options?.includeTimestamps
})
);
}

// Execute all exports in parallel
await Promise.all(exportPromises);
}

Expand Down
2 changes: 2 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
import { initializeAudioProcessor } from "./utils/audioProcessor";
import { initializeAudioMerger } from "./utils/audioMerger";
import { initializeAudioExtractor } from "./utils/audioExtractor";
import { initializeAudioExporter } from "./exportHandler/audioExporter";
import { checkTools, getUnavailableTools } from "./utils/toolsManager";
import { initToolPreferences, setNativeGitAvailable, getGitToolMode, getSqliteToolMode } from "./utils/toolPreferences";
import { downloadFFmpeg } from "./utils/ffmpegManager";
Expand Down Expand Up @@ -322,6 +323,7 @@ export async function activate(context: vscode.ExtensionContext) {
initializeAudioProcessor(context);
initializeAudioMerger(context);
initializeAudioExtractor(context);
initializeAudioExporter(context);

// Register and show splash screen immediately before anything else
try {
Expand Down
Loading