Added transcription of audio and video files to local data sources (#1081)

Co-authored-by: Paul Koudelka <106623909+PaulKoudelka@users.noreply.github.com>
This commit is contained in:
Thorsten SommerandPaul Koudelka authored and GitHub committed 2026-10-18 12:06:15 +02:00
1 parent 8642ce2d81
commit 752f7d828a
82 files changed
+3231 -302

No files matched your search

@@ -11,4 +11,5 @@ public sealed record EmbeddingStateFile(
DateTimeOffset CreationUtc,
DateTimeOffset LastWriteUtc,
DateTimeOffset EmbeddedAtUtc,
int ChunkCount);
int ChunkCount,
string? TranscriptKey = null);
@@ -26,6 +26,8 @@ internal sealed class EmbeddingStateFileEntity
public int ChunkCount { get; set; }
public string? TranscriptKey { get; set; }
public EmbeddingStateDataSourceEntity? DataSource { get; set; }
public List<EmbeddingStateChunkEntity> Chunks { get; set; } = [];
@@ -80,6 +80,7 @@ internal sealed class IndexStoreDbContext(DbContextOptions<IndexStoreDbContext>
entity.Property(file => file.LastWriteUtc).HasColumnName("last_write_utc").HasConversion(utcDateTimeOffsetConverter).IsRequired();
entity.Property(file => file.EmbeddedAtUtc).HasColumnName("embedded_at_utc").HasConversion(utcDateTimeOffsetConverter).IsRequired();
entity.Property(file => file.ChunkCount).HasColumnName("chunk_count");
entity.Property(file => file.TranscriptKey).HasColumnName("transcript_key");
entity.HasIndex(file => file.DataSourceId).HasDatabaseName("idx_embedded_files_data_source");
entity.HasIndex(file => file.AbsolutePath).HasDatabaseName("idx_embedded_files_absolute_path");
@@ -11,6 +11,7 @@ internal static class IndexStoreSchemaMigrator
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Migrations.DropFileConfidenceLevel))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Migrations.DropDataSourceName))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Migrations.MailboxIndex))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Migrations.MediaTranscriptKey))]
public static async Task MigrateAsync(IndexStoreDbContext context, CancellationToken token)
{
await context.Database.MigrateAsync(token);
@@ -0,0 +1,35 @@
#nullable disable
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace AIStudio.Tools.Databases.IndexStore.Migrations;
/// <summary>
/// Adds the key of the transcript to the indexed files, for audio and video files which a data source transcribed.
/// </summary>
/// <remarks>
/// The transcripts lie below the data directory under the hash of the content of their media file.
/// The index has to know which transcript belongs to which file, so that AI Studio can remove a
/// transcript once its file is gone or changed. Every other file keeps NULL.
/// </remarks>
[DbContext(typeof(IndexStoreDbContext))]
[Migration("20261018000000_MediaTranscriptKey")]
public partial class MediaTranscriptKey : Migration
{
/// <remarks>
/// Both directions go through raw SQL, like DropDataSourceName: the SQLite provider answers
/// DropColumn by rebuilding the table, and dropping the old embedded_files table would let the
/// cascade of the foreign key in embedding_chunks take every chunk with it. Adding a nullable
/// column is native in SQLite and leaves the rows alone.
/// </remarks>
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("ALTER TABLE embedded_files ADD COLUMN transcript_key TEXT NULL;");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("ALTER TABLE embedded_files DROP COLUMN transcript_key;");
}
}
@@ -119,6 +119,10 @@ partial class IndexStoreDbContextModelSnapshot : ModelSnapshot
.HasColumnType("TEXT")
.HasColumnName("relative_path");
entity.Property<string>("TranscriptKey")
.HasColumnType("TEXT")
.HasColumnName("transcript_key");
entity.HasKey("ParentFileId");
entity.HasIndex("AbsolutePath")
@@ -148,7 +148,8 @@ public sealed partial class SqliteIndexStoreClientImplementation(string name, st
file.FileSize,
file.LastWriteUtc,
file.EmbeddedAtUtc,
file.ChunkCount);
file.ChunkCount,
file.TranscriptKey);
}
var permanentFailures = await context.PermanentIndexingFailures
@@ -690,6 +691,7 @@ public sealed partial class SqliteIndexStoreClientImplementation(string name, st
fileEntity.LastWriteUtc = file.LastWriteUtc;
fileEntity.EmbeddedAtUtc = file.EmbeddedAtUtc;
fileEntity.ChunkCount = file.ChunkCount;
fileEntity.TranscriptKey = file.TranscriptKey;
}
private static void ApplyPermanentFailure(IndexingFailureEntity failureEntity, string dataSourceId, PermanentIndexingFailure failure)