mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-08-11 16:32:10 +00:00
Add resumable batch media transcription
This commit is contained in:
parent
e9f1373c71
commit
d13215c5b3
@ -16,6 +16,10 @@
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body2" Class="mb-3">
|
||||
@T("Supported audio and video files are transcribed automatically without an additional dialog. Each transcript is stored next to its media file as '<media-file>.transcript.md' and reused when an interrupted run is continued.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTextSwitch Label="@T("Include subfolders?")" Disabled="@this.isProcessingBatch" Value="@this.includeSubdirectories" ValueChanged="@(v => this.includeSubdirectories = v)" LabelOn="@T("Yes, process files in subfolders as well")" LabelOff="@T("No, only process files in the selected folder")"/>
|
||||
|
||||
@if (this.includeSubdirectories)
|
||||
|
||||
@ -0,0 +1,166 @@
|
||||
using System.Text;
|
||||
|
||||
using AIStudio.Tools.Media;
|
||||
using AIStudio.Tools.Rust;
|
||||
|
||||
namespace AIStudio.Assistants.BatchProcessing;
|
||||
|
||||
public partial class AssistantBatchProcessing
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads a document through the Rust content stream or resolves a persistent
|
||||
/// transcript for an audio or video file.
|
||||
/// </summary>
|
||||
private Task<string?> LoadInputContentAsync(BatchProcessingFileResult fileResult, CancellationToken token)
|
||||
{
|
||||
return IsTranscribableMedia(fileResult.FilePath)
|
||||
? this.LoadMediaTranscriptAsync(fileResult, token)
|
||||
: this.LoadDocumentContentAsync(fileResult);
|
||||
}
|
||||
|
||||
private async Task<string?> LoadDocumentContentAsync(BatchProcessingFileResult fileResult)
|
||||
{
|
||||
FileExtractionResult extraction;
|
||||
try
|
||||
{
|
||||
extraction = await this.RustService.ReadArbitraryFileData(fileResult.FilePath, int.MaxValue);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, string.Format(T("Was not able to read the file: {0}"), e.Message), e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!extraction.HasUsableContent)
|
||||
{
|
||||
this.Logger.LogError("Reading the batch file '{FilePath}' failed: code={ErrorCode}, message='{ErrorMessage}'.", fileResult.FilePath, extraction.ErrorCode, extraction.ErrorMessage);
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, extraction.ToUserMessage(fileResult.FileName));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (extraction.Outcome is FileExtractionOutcome.PARTIAL)
|
||||
{
|
||||
this.Logger.LogWarning("Parts of the batch file '{FilePath}' could not be read: pages={FailedPages}.", fileResult.FilePath, string.Join(", ", extraction.FailedPages));
|
||||
await this.MessageBus.SendWarning(new(Icons.Material.Filled.Description, extraction.ToPartialUserMessage(fileResult.FileName)));
|
||||
}
|
||||
|
||||
if (extraction.HasExtensionMismatch)
|
||||
{
|
||||
this.Logger.LogWarning("The batch file '{FilePath}' is actually a '{DetectedFormat}'.", fileResult.FilePath, extraction.DetectedFormat);
|
||||
await this.MessageBus.SendWarning(new(Icons.Material.Filled.RuleFolder, extraction.ToExtensionMismatchUserMessage(fileResult.FileName)));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(extraction.Content))
|
||||
return extraction.Content;
|
||||
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("Was not able to extract any text from this file."));
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> LoadMediaTranscriptAsync(BatchProcessingFileResult fileResult, CancellationToken token)
|
||||
{
|
||||
var transcriptFilePath = GetTranscriptFilePath(fileResult.FilePath);
|
||||
if (File.Exists(transcriptFilePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var existingTranscript = await File.ReadAllTextAsync(transcriptFilePath, token);
|
||||
if (!string.IsNullOrWhiteSpace(existingTranscript))
|
||||
{
|
||||
this.Logger.LogInformation("Reusing the existing batch transcript '{TranscriptFilePath}' for media file '{MediaFilePath}'.", transcriptFilePath, fileResult.FilePath);
|
||||
return existingTranscript;
|
||||
}
|
||||
|
||||
this.Logger.LogWarning("The existing batch transcript '{TranscriptFilePath}' for media file '{MediaFilePath}' is empty and will be replaced.", transcriptFilePath, fileResult.FilePath);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.CANCELED, T("The batch run was canceled."));
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, string.Format(T("Was not able to read the existing transcript: {0}"), e.Message), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.MediaTranscriptionService.HasUsableTranscriptionProvider)
|
||||
{
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("No usable transcription provider is configured."));
|
||||
return null;
|
||||
}
|
||||
|
||||
var transcription = await this.MediaTranscriptionService.TranscribeAsync(fileResult.FilePath, token);
|
||||
if (transcription.Status is MediaTranscriptionResultStatus.CANCELLED)
|
||||
{
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.CANCELED, T("The batch run was canceled."));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (transcription.Status is not MediaTranscriptionResultStatus.SUCCEEDED)
|
||||
{
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, transcription.UserMessage);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(transcription.Text))
|
||||
{
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("The transcription provider returned an empty transcript."));
|
||||
return null;
|
||||
}
|
||||
|
||||
return await this.StoreMediaTranscriptAsync(fileResult, transcriptFilePath, transcription.Text);
|
||||
}
|
||||
|
||||
private async Task<string?> StoreMediaTranscriptAsync(BatchProcessingFileResult fileResult, string transcriptFilePath, string transcript)
|
||||
{
|
||||
var tempFilePath = transcriptFilePath + ".tmp";
|
||||
try
|
||||
{
|
||||
// Complete the small persistence step even if cancellation arrived
|
||||
// after transcription, so the expensive provider result can be
|
||||
// reused when the interrupted batch is continued.
|
||||
await File.WriteAllTextAsync(tempFilePath, transcript, new UTF8Encoding(false), CancellationToken.None);
|
||||
File.Move(tempFilePath, transcriptFilePath, true);
|
||||
this.Logger.LogInformation("Stored the batch transcript '{TranscriptFilePath}' next to media file '{MediaFilePath}'.", transcriptFilePath, fileResult.FilePath);
|
||||
return transcript;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, string.Format(T("Was not able to store the transcript next to the media file: {0}"), e.Message), e);
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(tempFilePath))
|
||||
File.Delete(tempFilePath);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogWarning(e, "Was not able to remove the temporary batch transcript '{TempFilePath}'.", tempFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTranscribableMedia(string filePath) => FileTypes.IsAllowedPath(filePath, FileTypes.AUDIO, FileTypes.VIDEO);
|
||||
|
||||
private static string GetTranscriptFilePath(string mediaFilePath) => mediaFilePath + TRANSCRIPT_FILE_SUFFIX;
|
||||
|
||||
private static bool HasReusableTranscript(string mediaFilePath)
|
||||
{
|
||||
var transcriptFilePath = GetTranscriptFilePath(mediaFilePath);
|
||||
try
|
||||
{
|
||||
return File.Exists(transcriptFilePath) && new FileInfo(transcriptFilePath).Length > 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// The concrete read error is reported when the affected file is
|
||||
// processed. Here we only decide whether a provider is required.
|
||||
return File.Exists(transcriptFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,42 +167,9 @@ public partial class AssistantBatchProcessing
|
||||
/// </remarks>
|
||||
private async Task ProcessOneFileAsync(BatchProcessingFileResult fileResult, string resolvedOutputDirectory, CancellationToken token)
|
||||
{
|
||||
FileExtractionResult extraction;
|
||||
try
|
||||
{
|
||||
extraction = await this.RustService.ReadArbitraryFileData(fileResult.FilePath, int.MaxValue);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, string.Format(T("Was not able to read the file: {0}"), e.Message), e);
|
||||
var fileContent = await this.LoadInputContentAsync(fileResult, token);
|
||||
if (fileContent is null)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!extraction.HasUsableContent)
|
||||
{
|
||||
this.Logger.LogError("Reading the batch file '{FilePath}' failed: code={ErrorCode}, message='{ErrorMessage}'.", fileResult.FilePath, extraction.ErrorCode, extraction.ErrorMessage);
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, extraction.ToUserMessage(fileResult.FileName));
|
||||
return;
|
||||
}
|
||||
|
||||
if (extraction.Outcome is FileExtractionOutcome.PARTIAL)
|
||||
{
|
||||
this.Logger.LogWarning("Parts of the batch file '{FilePath}' could not be read: pages={FailedPages}.", fileResult.FilePath, string.Join(", ", extraction.FailedPages));
|
||||
await this.MessageBus.SendWarning(new(Icons.Material.Filled.Description, extraction.ToPartialUserMessage(fileResult.FileName)));
|
||||
}
|
||||
|
||||
if (extraction.HasExtensionMismatch)
|
||||
{
|
||||
this.Logger.LogWarning("The batch file '{FilePath}' is actually a '{DetectedFormat}'.", fileResult.FilePath, extraction.DetectedFormat);
|
||||
await this.MessageBus.SendWarning(new(Icons.Material.Filled.RuleFolder, extraction.ToExtensionMismatchUserMessage(fileResult.FileName)));
|
||||
}
|
||||
|
||||
var fileContent = extraction.Content;
|
||||
if (string.IsNullOrWhiteSpace(fileContent))
|
||||
{
|
||||
this.FinishFileResult(fileResult, BatchProcessingFileStatus.FAILED, T("Was not able to extract any text from this file."));
|
||||
return;
|
||||
}
|
||||
|
||||
string aiAnswer;
|
||||
try
|
||||
|
||||
@ -120,6 +120,9 @@ public partial class AssistantBatchProcessing
|
||||
foreach (var file in Directory.EnumerateFiles(this.inputDirectory, pattern, searchOption))
|
||||
{
|
||||
var normalizedFile = Path.GetFullPath(file);
|
||||
if (IsTranscriptArtifact(normalizedFile))
|
||||
continue;
|
||||
|
||||
if (isOutputSeparateFolder)
|
||||
{
|
||||
if (normalizedFile.StartsWith(outputDirectoryPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
@ -178,6 +181,16 @@ public partial class AssistantBatchProcessing
|
||||
return fileName.EndsWith(RESULT_FILE_SUFFIX, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks for persistent or interrupted media transcript artifacts. They
|
||||
/// always live beside their source file, independently of the output folder.
|
||||
/// </summary>
|
||||
private static bool IsTranscriptArtifact(string filePath)
|
||||
{
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
return fileName.EndsWith(TRANSCRIPT_FILE_SUFFIX, StringComparison.OrdinalIgnoreCase) || fileName.EndsWith(TRANSCRIPT_FILE_SUFFIX + ".tmp", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the form, finds the documents, and creates the output folder.
|
||||
/// </summary>
|
||||
@ -215,6 +228,13 @@ public partial class AssistantBatchProcessing
|
||||
return null;
|
||||
}
|
||||
|
||||
var requiresTranscription = files.Any(file => IsTranscribableMedia(file) && !HasReusableTranscript(file));
|
||||
if (requiresTranscription && !this.MediaTranscriptionService.HasUsableTranscriptionProvider)
|
||||
{
|
||||
this.AddInputIssue(T("The selected files include audio or video without an existing transcript, but no usable transcription provider is configured. Configure one in the transcription settings or remove the media patterns."));
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(resolvedOutputDirectory);
|
||||
|
||||
@ -15,6 +15,7 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
|
||||
private const string DEFAULT_RESULTS_FILENAME = "batch-results.csv";
|
||||
private const string CSV_EXTENSION = ".csv";
|
||||
private const string RESULT_FILE_SUFFIX = "_result.md";
|
||||
private const string TRANSCRIPT_FILE_SUFFIX = ".transcript.md";
|
||||
private const string TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
/// <summary>
|
||||
@ -27,7 +28,7 @@ public partial class AssistantBatchProcessing : AssistantBaseCore<SettingsDialog
|
||||
|
||||
protected override string Title => T("Batch Processing Assistant");
|
||||
|
||||
protected override string Description => T("Process all documents of a folder in one batch run: each document is converted to Markdown and sent to the AI along with your instructions. You choose whether each answer is stored as its own Markdown file or whether all answers are collected in one CSV results table. A log records what happened to every document, so a run which was interrupted or produced errors can be continued later. A single failing document never stops the entire run.");
|
||||
protected override string Description => T("Process all documents and media files of a folder in one batch run: documents are converted to Markdown, while audio and video files are transcribed automatically, before their content is sent to the AI along with your instructions. You choose whether each answer is stored as its own Markdown file or whether all answers are collected in one CSV results table. A log records what happened to every file, so a run which was interrupted or produced errors can be continued later. A single failing file never stops the entire run.");
|
||||
|
||||
protected override string SystemPrompt => this.BuildSystemPrompt();
|
||||
|
||||
|
||||
@ -331,6 +331,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T4242312602"] = "Send to .
|
||||
-- Copy result
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T83711157"] = "Copy result"
|
||||
|
||||
-- The transcription provider returned an empty transcript.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1080540822"] = "The transcription provider returned an empty transcript."
|
||||
|
||||
-- Name of the results table (optional)
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1127380661"] = "Name of the results table (optional)"
|
||||
|
||||
@ -343,9 +346,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- Please use only single asterisks as wildcards, e.g., *.pdf or report-*.docx.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1187528282"] = "Please use only single asterisks as wildcards, e.g., *.pdf or report-*.docx."
|
||||
|
||||
-- Supported audio and video files are transcribed automatically without an additional dialog. Each transcript is stored next to its media file as '<media-file>.transcript.md' and reused when an interrupted run is continued.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T120341322"] = "Supported audio and video files are transcribed automatically without an additional dialog. Each transcript is stored next to its media file as '<media-file>.transcript.md' and reused when an interrupted run is continued."
|
||||
|
||||
-- Instructions
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T1221801316"] = "Instructions"
|
||||
|
||||
-- Process all documents and media files of a folder in one batch run: documents are converted to Markdown, while audio and video files are transcribed automatically, before their content is sent to the AI along with your instructions. You choose whether each answer is stored as its own Markdown file or whether all answers are collected in one CSV results table. A log records what happened to every file, so a run which was interrupted or produced errors can be continued later. A single failing file never stops the entire run.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T131887991"] = "Process all documents and media files of a folder in one batch run: documents are converted to Markdown, while audio and video files are transcribed automatically, before their content is sent to the AI along with your instructions. You choose whether each answer is stored as its own Markdown file or whether all answers are collected in one CSV results table. A log records what happened to every file, so a run which was interrupted or produced errors can be continued later. A single failing file never stops the entire run."
|
||||
|
||||
-- Batch Processing Assistant
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T132410578"] = "Batch Processing Assistant"
|
||||
|
||||
@ -415,18 +424,30 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- We always write a log named log.csv, which lists every document with its processing time, the model, the status, and the details of any error. Its columns are separated by a vertical bar, so you can open it with any spreadsheet application. When you start another run on the same output folder, we ask you whether to continue that run: documents which failed or are missing in the log are then processed again. When no output folder is selected, everything is written to the subfolder 'ai-results' within the input folder.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T225540173"] = "We always write a log named log.csv, which lists every document with its processing time, the model, the status, and the details of any error. Its columns are separated by a vertical bar, so you can open it with any spreadsheet application. When you start another run on the same output folder, we ask you whether to continue that run: documents which failed or are missing in the log are then processed again. When no output folder is selected, everything is written to the subfolder 'ai-results' within the input folder."
|
||||
|
||||
-- No usable transcription provider is configured.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2282521655"] = "No usable transcription provider is configured."
|
||||
|
||||
-- Was not able to create the output folder: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2290092642"] = "Was not able to create the output folder: {0}"
|
||||
|
||||
-- The AI answer was empty.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T230354366"] = "The AI answer was empty."
|
||||
|
||||
-- The batch run finished, but {0} files could not be processed. See the progress table and log for details.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2334361705"] = "The batch run finished, but {0} files could not be processed. See the progress table and log for details."
|
||||
|
||||
-- The AI request failed: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2376918044"] = "The AI request failed: {0}"
|
||||
|
||||
-- Done
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2379421585"] = "Done"
|
||||
|
||||
-- The selected files include audio or video without an existing transcript, but no usable transcription provider is configured. Configure one in the transcription settings or remove the media patterns.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2390162661"] = "The selected files include audio or video without an existing transcript, but no usable transcription provider is configured. Configure one in the transcription settings or remove the media patterns."
|
||||
|
||||
-- Was not able to read the existing transcript: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2397111152"] = "Was not able to read the existing transcript: {0}"
|
||||
|
||||
-- File patterns
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2460883298"] = "File patterns"
|
||||
|
||||
@ -463,6 +484,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- Was not able to write the result file: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T2991295581"] = "Was not able to write the result file: {0}"
|
||||
|
||||
-- The batch run finished, but one file could not be processed. See the progress table and log for details.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3201532790"] = "The batch run finished, but one file could not be processed. See the progress table and log for details."
|
||||
|
||||
-- Please select the folder that contains the documents you want to process.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T33077198"] = "Please select the folder that contains the documents you want to process."
|
||||
|
||||
@ -487,6 +511,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- Please remove empty file patterns. Separate valid patterns with a single semicolon.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T368919579"] = "Please remove empty file patterns. Separate valid patterns with a single semicolon."
|
||||
|
||||
-- Was not able to store the transcript next to the media file: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3691287653"] = "Was not able to store the transcript next to the media file: {0}"
|
||||
|
||||
-- Time
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T3756319748"] = "Time"
|
||||
|
||||
@ -535,6 +562,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- Please use file name patterns without folder paths, e.g., *.pdf or report-*.docx.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T515934256"] = "Please use file name patterns without folder paths, e.g., *.pdf or report-*.docx."
|
||||
|
||||
-- Was not able to read the results table of the previous run. Its completed documents cannot be restored and will be processed again.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T544244392"] = "Was not able to read the results table of the previous run. Its completed documents cannot be restored and will be processed again."
|
||||
|
||||
-- Yes, process files in subfolders as well
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T618448696"] = "Yes, process files in subfolders as well"
|
||||
|
||||
@ -553,9 +583,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING
|
||||
-- A separate output folder is excluded from document discovery. This includes the default 'ai-results' folder, so results from an earlier run are not processed again. If the input folder itself is used for output, known batch result files are excluded instead.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T822136905"] = "A separate output folder is excluded from document discovery. This includes the default 'ai-results' folder, so results from an earlier run are not processed again. If the input folder itself is used for output, known batch result files are excluded instead."
|
||||
|
||||
-- Process all documents of a folder in one batch run: each document is converted to Markdown and sent to the AI along with your instructions. You choose whether each answer is stored as its own Markdown file or whether all answers are collected in one CSV results table. A log records what happened to every document, so a run which was interrupted or produced errors can be continued later. A single failing document never stops the entire run.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::ASSISTANTBATCHPROCESSING::T998860382"] = "Process all documents of a folder in one batch run: each document is converted to Markdown and sent to the AI along with your instructions. You choose whether each answer is stored as its own Markdown file or whether all answers are collected in one CSV results table. A log records what happened to every document, so a run which was interrupted or produced errors can be continued later. A single failing document never stops the entire run."
|
||||
|
||||
-- One CSV results table, where each answer becomes one row
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::BATCHPROCESSING::BATCHPROCESSINGOUTPUTMODEEXTENSIONS::T1515293131"] = "One CSV results table, where each answer becomes one row"
|
||||
|
||||
@ -6508,6 +6535,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T22
|
||||
-- When enabled, new batch runs start with the defaults configured below.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2592677194"] = "When enabled, new batch runs start with the defaults configured below."
|
||||
|
||||
-- Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2594325620"] = "Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats."
|
||||
|
||||
-- Subfolders are included
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T2607092632"] = "Subfolders are included"
|
||||
|
||||
@ -6586,9 +6616,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T74
|
||||
-- Leave empty when an input folder should be selected for every batch run.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T762890100"] = "Leave empty when an input folder should be selected for every batch run."
|
||||
|
||||
-- Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGBATCHPROCESSING::T953507412"] = "Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx."
|
||||
|
||||
-- Preselect one of your chat templates?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCHAT::T1402022556"] = "Preselect one of your chat templates?"
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
<MudText Typo="Typo.h6" Class="mb-3">@T("Input")</MudText>
|
||||
<ConfigurationDirectory OptionDescription="@T("Default input folder")" Disabled="@this.DefaultsDisabled" DirectoryDialogTitle="@T("Select the default input folder")" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.InputDirectory)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.InputDirectory = value)" OptionHelp="@T("Leave empty when an input folder should be selected for every batch run.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.InputDirectory, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationText OptionDescription="@T("Default file patterns")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.FilterAlt" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.FilePatterns)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.FilePatterns = value)" ResetValue="@(() => DataBatchProcessing.DEFAULT_FILE_PATTERNS)" ResetButtonText="@T("Restore default patterns")" OptionHelp="@T("Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.FilePatterns, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationText OptionDescription="@T("Default file patterns")" Disabled="@this.DefaultsDisabled" Icon="@Icons.Material.Filled.FilterAlt" Text="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.FilePatterns)" TextUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.FilePatterns = value)" ResetValue="@(() => DataBatchProcessing.DEFAULT_FILE_PATTERNS)" ResetButtonText="@T("Restore default patterns")" OptionHelp="@T("Separate multiple file patterns with a semicolon, e.g., *.pdf;*.docx. The standard patterns include all supported audio and video formats.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.FilePatterns, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationOption OptionDescription="@T("Include subfolders by default?")" Disabled="@this.DefaultsDisabled" LabelOn="@T("Subfolders are included")" LabelOff="@T("Only the selected folder is processed")" State="@(() => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeSubdirectories)" StateUpdate="@(value => this.SettingsManager.ConfigurationData.BatchProcessing.IncludeSubdirectories = value)" IsLocked="() => ManagedConfiguration.TryGet(x => x.BatchProcessing, x => x.IncludeSubdirectories, out var meta) && meta.IsLocked"/>
|
||||
|
||||
<MudText Typo="Typo.h6" Class="mb-3 mt-6">@T("Instructions")</MudText>
|
||||
|
||||
@ -406,7 +406,7 @@ CONFIG["SETTINGS"] = {}
|
||||
--
|
||||
-- Configure the default file patterns and whether subfolders are included.
|
||||
-- Separate multiple patterns with semicolons.
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.FilePatterns"] = "*.pdf;*.docx;*.pptx;*.xlsx;*.md;*.txt"
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.FilePatterns"] = "*.pdf;*.docx;*.pptx;*.xlsx;*.md;*.txt;*.mp3;*.wav;*.wave;*.aac;*.flac;*.ogg;*.opus;*.m4a;*.m4b;*.wma;*.alac;*.aif;*.aiff;*.caf;*.mp4;*.m4v;*.avi;*.mkv;*.mov;*.wmv;*.flv;*.webm"
|
||||
-- CONFIG["SETTINGS"]["DataBatchProcessing.IncludeSubdirectories"] = false
|
||||
--
|
||||
-- Configure the default instruction source.
|
||||
|
||||
@ -11,7 +11,7 @@ namespace AIStudio.Settings.DataModel;
|
||||
/// <param name="configSelection">The managed-configuration selector.</param>
|
||||
public sealed class DataBatchProcessing(Expression<Func<Data, DataBatchProcessing>>? configSelection = null)
|
||||
{
|
||||
public const string DEFAULT_FILE_PATTERNS = "*.pdf;*.docx;*.pptx;*.xlsx;*.md;*.txt";
|
||||
public const string DEFAULT_FILE_PATTERNS = "*.pdf;*.docx;*.pptx;*.xlsx;*.md;*.txt;*.mp3;*.wav;*.wave;*.aac;*.flac;*.ogg;*.opus;*.m4a;*.m4b;*.wma;*.alac;*.aif;*.aiff;*.caf;*.mp4;*.m4v;*.avi;*.mkv;*.mov;*.wmv;*.flv;*.webm";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes an unmanaged Batch Processing settings instance.
|
||||
|
||||
@ -61,6 +61,9 @@ public sealed class MediaTranscriptionService(
|
||||
return this.activeBatches.Contains(owner);
|
||||
}
|
||||
|
||||
/// <summary>Gets whether the currently configured transcription provider can be used.</summary>
|
||||
public bool HasUsableTranscriptionProvider => this.ResolveProvider() is not null;
|
||||
|
||||
/// <summary>Gets the last retained state for one owner.</summary>
|
||||
public MediaImportSnapshot? GetSnapshot(MediaImportOwner owner)
|
||||
{
|
||||
@ -403,12 +406,12 @@ public sealed class MediaTranscriptionService(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transcribes a voice recording independently of the visible import lane.
|
||||
/// Transcribes an audio or video file without starting a visible import operation.
|
||||
/// </summary>
|
||||
/// <param name="mediaPath">Voice recording path.</param>
|
||||
/// <param name="mediaPath">Audio or video file path.</param>
|
||||
/// <param name="token">Caller cancellation token.</param>
|
||||
/// <returns>A typed terminal result.</returns>
|
||||
public async Task<MediaTranscriptionResult> TranscribeVoiceAsync(string mediaPath, CancellationToken token = default)
|
||||
public async Task<MediaTranscriptionResult> TranscribeAsync(string mediaPath, CancellationToken token = default)
|
||||
{
|
||||
this.ThrowIfDisposed();
|
||||
var operation = this.CreateOperation(null, token);
|
||||
@ -423,6 +426,14 @@ public sealed class MediaTranscriptionService(
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transcribes a voice recording independently of the visible import lane.
|
||||
/// </summary>
|
||||
/// <param name="mediaPath">Voice recording path.</param>
|
||||
/// <param name="token">Caller cancellation token.</param>
|
||||
/// <returns>A typed terminal result.</returns>
|
||||
public Task<MediaTranscriptionResult> TranscribeVoiceAsync(string mediaPath, CancellationToken token = default) => this.TranscribeAsync(mediaPath, token);
|
||||
|
||||
/// <summary>Cancels only the queued or active operation belonging to one owner.</summary>
|
||||
public async Task StopAsync(MediaImportOwner owner)
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user