Compare commits

..

3 Commits

Author SHA1 Message Date
TwistedUmbrellaX
f0113691e8 Merge branch '10.10' into regex-season 2024-11-26 10:02:27 -05:00
rlauu
983d1e346b Update VisualizationController.cs 2024-11-25 20:32:05 +01:00
rlauu
700c025fef Per season regex 2024-11-25 20:26:20 +01:00
12 changed files with 286 additions and 13 deletions

View File

@ -33,7 +33,11 @@ public class ChapterAnalyzer(ILogger<ChapterAnalyzer> logger) : IMediaFileAnalyz
AnalysisMode mode,
CancellationToken cancellationToken)
{
var expression = mode switch
var expression = Plugin.Instance!.GetSeasonRegex(analysisQueue[0].SeasonId, mode);
if (string.IsNullOrWhiteSpace(expression))
{
expression = mode switch
{
AnalysisMode.Introduction => _config.ChapterAnalyzerIntroductionPattern,
AnalysisMode.Credits => _config.ChapterAnalyzerEndCreditsPattern,
@ -41,6 +45,7 @@ public class ChapterAnalyzer(ILogger<ChapterAnalyzer> logger) : IMediaFileAnalyz
AnalysisMode.Preview => _config.ChapterAnalyzerPreviewPattern,
_ => throw new ArgumentOutOfRangeException(nameof(mode), $"Unexpected analysis mode: {mode}")
};
}
if (string.IsNullOrWhiteSpace(expression))
{

View File

@ -471,6 +471,12 @@
<option value="None">None</option>
</select>
</label>
<label for="regexRecap" style="margin-right: 1.5em; display: inline-block">
<span>Recap Chapter Regex</span>
<input type="text" id="regexRecap" class="emby-input" />
</label>
</label>
<br />
<label for="actionIntro" style="margin-right: 1.5em; display: inline-block">
<span>Introduction analysis</span>
<select is="emby-select" id="actionIntro" class="emby-select-withcolor emby-select">
@ -480,6 +486,11 @@
<option value="None">None</option>
</select>
</label>
<label for="regexIntro" style="margin-right: 1.5em; display: inline-block">
<span>Introduction Chapter Regex</span>
<input type="text" id="regexIntro" class="emby-input" />
</label>
<br />
<label for="actionCredits" style="margin-right: 1.5em; display: inline-block">
<span>Credits (Outro) analysis</span>
<select is="emby-select" id="actionCredits" class="emby-select-withcolor emby-select">
@ -490,6 +501,11 @@
<option value="None">None</option>
</select>
</label>
<label for="regexCredits" style="margin-right: 1.5em; display: inline-block">
<span>Credits (Outro) Chapter Regex</span>
<input type="text" id="regexCredits" class="emby-input" />
</label>
<br />
<label for="actionPreview" style="margin-right: 1.5em; display: inline-block">
<span>Preview analysis</span>
<select is="emby-select" id="actionPreview" class="emby-select-withcolor emby-select">
@ -498,6 +514,10 @@
<option value="None">None</option>
</select>
</label>
<label for="regexPreview" style="margin-right: 1.5em; display: inline-block">
<span>Preview Chapter Regex</span>
<input type="text" id="regexPreview" class="emby-input" />
</label>
</div>
<br />
@ -797,9 +817,13 @@
// visualizer elements
var analyzerActionsSection = document.querySelector("div#analyzerActionsSection");
var actionIntro = analyzerActionsSection.querySelector("select#actionIntro");
var regexIntro = analyzerActionsSection.querySelector("input#regexIntro");
var actionCredits = analyzerActionsSection.querySelector("select#actionCredits");
var regexCredits = analyzerActionsSection.querySelector("input#regexCredits");
var actionRecap = analyzerActionsSection.querySelector("select#actionRecap");
var regexRecap = analyzerActionsSection.querySelector("input#regexRecap");
var actionPreview = analyzerActionsSection.querySelector("select#actionPreview");
var regexPreview = analyzerActionsSection.querySelector("input#regexPreview");
var saveAnalyzerActionsButton = analyzerActionsSection.querySelector("button#saveAnalyzerActions");
var canvas = document.querySelector("canvas#troubleshooter");
var selectShow = document.querySelector("select#troubleshooterShow");
@ -1082,6 +1106,11 @@
actionCredits.value = analyzerActions.Credits || "Default";
actionRecap.value = analyzerActions.Recap || "Default";
actionPreview.value = analyzerActions.Preview || "Default";
const analyzerRegexs = await getJson("Intros/AnalyzerRegexs/" + encodeURI(selectSeason.value));
regexIntro.value = analyzerRegexs.Introduction || "";
regexCredits.value = analyzerRegexs.Credits || "";
regexRecap.value = analyzerRegexs.Recap || "";
regexPreview.value = analyzerRegexs.Preview || "";
analyzerActionsSection.style.display = "unset";
// show the erase season button
@ -1539,7 +1568,7 @@
saveAnalyzerActionsButton.addEventListener("click", () => {
Dashboard.showLoadingMsg();
var url = "Intros/AnalyzerActions/UpdateSeason";
var url1 = "Intros/AnalyzerActions/UpdateSeason";
const actions = {
id: selectSeason.value,
analyzerActions: {
@ -1550,7 +1579,20 @@
},
};
fetchWithAuth(url, "POST", JSON.stringify(actions));
var url2 = "Intros/AnalyzerRegexs/UpdateSeason";
const regexs = {
id: selectSeason.value,
regexs: {
Introduction: regexIntro.value,
Credits: regexCredits.value,
Recap: regexRecap.value,
Preview: regexPreview.value,
},
};
fetchWithAuth(url1, "POST", JSON.stringify(actions));
fetchWithAuth(url2, "POST", JSON.stringify(regexs));
Dashboard.alert("Analyzer actions updated for " + selectSeason.value + " of " + selectShow.value);
Dashboard.hideLoadingMsg();

View File

@ -105,6 +105,28 @@ public class VisualizationController(ILogger<VisualizationController> logger, Me
return Ok(analyzerActions);
}
/// <summary>
/// Returns the analyzer actions for the provided season.
/// </summary>
/// <param name="seasonId">Season ID.</param>
/// <returns>List of episode titles.</returns>
[HttpGet("AnalyzerRegexs/{SeasonId}")]
public ActionResult<IReadOnlyDictionary<AnalysisMode, string>> GetSeasonRegexs([FromRoute] Guid seasonId)
{
if (!Plugin.Instance!.QueuedMediaItems.ContainsKey(seasonId))
{
return NotFound();
}
var seasonRegexs = new Dictionary<AnalysisMode, string>();
foreach (var mode in Enum.GetValues<AnalysisMode>())
{
seasonRegexs[mode] = Plugin.Instance!.GetSeasonRegex(seasonId, mode);
}
return Ok(seasonRegexs);
}
/// <summary>
/// Returns the names and unique identifiers of all episodes in the provided season.
/// </summary>
@ -227,6 +249,19 @@ public class VisualizationController(ILogger<VisualizationController> logger, Me
return NoContent();
}
/// <summary>
/// Updates the analyzer regexs for the provided season.
/// </summary>
/// <param name="request">Update analyzer regexs request.</param>
/// <returns>No content.</returns>
[HttpPost("AnalyzerRegexs/UpdateSeason")]
public async Task<ActionResult> UpdateAnalyzerRegexs([FromBody] UpdateSeasonRegexRequest request)
{
await Plugin.Instance!.SetSeasonRegexAsync(request.Id, request.SeasonRegexs).ConfigureAwait(false);
return NoContent();
}
private static string GetProductionYear(Guid seriesId)
{
return seriesId == Guid.Empty

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace IntroSkipper.Data
{
/// <summary>
/// /// Update analyzer actions request.
/// </summary>
public class UpdateSeasonRegexRequest
{
/// <summary>
/// Gets or sets season ID.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// Gets or sets analyzer actions.
/// </summary>
public IReadOnlyDictionary<AnalysisMode, string> SeasonRegexs { get; set; } = new Dictionary<AnalysisMode, string>();
}
}

View File

@ -22,12 +22,14 @@ public class DbSeasonInfo
/// <param name="mode">Analysis mode.</param>
/// <param name="action">Analyzer action.</param>
/// <param name="episodeIds">Episode IDs.</param>
public DbSeasonInfo(Guid seasonId, AnalysisMode mode, AnalyzerAction action, IEnumerable<Guid>? episodeIds = null)
/// <param name="regex">Regex.</param>
public DbSeasonInfo(Guid seasonId, AnalysisMode mode, AnalyzerAction action, IEnumerable<Guid>? episodeIds = null, string? regex = null)
{
SeasonId = seasonId;
Type = mode;
Action = action;
EpisodeIds = episodeIds ?? [];
Regex = regex ?? string.Empty;
}
/// <summary>
@ -56,4 +58,9 @@ public class DbSeasonInfo
/// Gets the season number.
/// </summary>
public IEnumerable<Guid> EpisodeIds { get; private set; } = [];
/// <summary>
/// Gets the season number.
/// </summary>
public string Regex { get; private set; } = string.Empty;
}

View File

@ -99,6 +99,9 @@ public class IntroSkipperDbContext : DbContext
(c1, c2) => (c1 ?? new List<Guid>()).SequenceEqual(c2 ?? new List<Guid>()),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()));
entity.Property(e => e.Regex)
.HasDefaultValue(string.Empty);
});
base.OnModelCreating(modelBuilder);

View File

@ -0,0 +1,79 @@
// <auto-generated />
using System;
using IntroSkipper.Db;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IntroSkipper.Migrations
{
[DbContext(typeof(IntroSkipperDbContext))]
[Migration("20241125172633_SeasonRegex")]
partial class SeasonRegex
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.11");
modelBuilder.Entity("IntroSkipper.Db.DbSeasonInfo", b =>
{
b.Property<Guid>("SeasonId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<int>("Action")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<string>("EpisodeIds")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Regex")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("");
b.HasKey("SeasonId", "Type");
b.HasIndex("SeasonId");
b.ToTable("DbSeasonInfo", (string)null);
});
modelBuilder.Entity("IntroSkipper.Db.DbSegment", b =>
{
b.Property<Guid>("ItemId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<double>("End")
.ValueGeneratedOnAdd()
.HasColumnType("REAL")
.HasDefaultValue(0.0);
b.Property<double>("Start")
.ValueGeneratedOnAdd()
.HasColumnType("REAL")
.HasDefaultValue(0.0);
b.HasKey("ItemId", "Type");
b.HasIndex("ItemId");
b.ToTable("DbSegment", (string)null);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IntroSkipper.Migrations
{
/// <inheritdoc />
public partial class SeasonRegex : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Regex",
table: "DbSeasonInfo",
type: "TEXT",
nullable: false,
defaultValue: string.Empty);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Regex",
table: "DbSeasonInfo");
}
}
}

View File

@ -3,6 +3,7 @@ using System;
using IntroSkipper.Db;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
@ -14,7 +15,7 @@ namespace IntroSkipper.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.10");
modelBuilder.HasAnnotation("ProductVersion", "8.0.11");
modelBuilder.Entity("IntroSkipper.Db.DbSeasonInfo", b =>
{
@ -33,6 +34,12 @@ namespace IntroSkipper.Migrations
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Regex")
.IsRequired()
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValue("");
b.HasKey("SeasonId", "Type");
b.HasIndex("SeasonId");

View File

@ -308,6 +308,35 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
.ToDictionary(s => s.Type, s => s.EpisodeIds);
}
internal string GetSeasonRegex(Guid id, AnalysisMode mode)
{
using var db = new IntroSkipperDbContext(_dbPath);
return db.DbSeasonInfo.FirstOrDefault(s => s.SeasonId == id && s.Type == mode)?.Regex ?? string.Empty;
}
internal async Task SetSeasonRegexAsync(Guid id, IReadOnlyDictionary<AnalysisMode, string> regexs)
{
using var db = new IntroSkipperDbContext(_dbPath);
var existingEntries = await db.DbSeasonInfo
.Where(s => s.SeasonId == id)
.ToDictionaryAsync(s => s.Type)
.ConfigureAwait(false);
foreach (var (mode, regex) in regexs)
{
if (existingEntries.TryGetValue(mode, out var existing))
{
db.Entry(existing).Property(s => s.Regex).CurrentValue = regex;
}
else
{
db.DbSeasonInfo.Add(new DbSeasonInfo(id, mode, AnalyzerAction.Default, regex: regex));
}
}
await db.SaveChangesAsync().ConfigureAwait(false);
}
internal AnalyzerAction GetAnalyzerAction(Guid id, AnalysisMode mode)
{
using var db = new IntroSkipperDbContext(_dbPath);

View File

@ -48,7 +48,7 @@ https://manifest.intro-skipper.org/manifest.json
- #### [Scheduled tasks fail instantly](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#scheduled-tasks-fail-instantly)
- #### [Plugin settings not saved](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#plugin-settings-not-saved)
- #### [Skip button is not visible](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#skip-button-is-not-visible)
- #### [Auto skip is not working](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#auto-skip-is-not-working)
- #### [Autoskip is not working](https://github.com/intro-skipper/intro-skipper/wiki/Troubleshooting#autoskip-is-not-working)
## [API Documentation](https://github.com/intro-skipper/intro-skipper/blob/master/docs/api.md)

View File

@ -15,6 +15,22 @@
"sourceUrl": "https://github.com/intro-skipper/intro-skipper/releases/download/10.10/v1.10.10.11/intro-skipper-v1.10.10.11.zip",
"checksum": "30a71fd3996e0fbe9076371539b1ca56",
"timestamp": "2024-11-25T17:07:33Z"
},
{
"version": "1.10.10.10",
"changelog": "- See the full changelog at [GitHub](https://github.com/intro-skipper/intro-skipper/releases/tag/10.10/v1.10.10.10)\n",
"targetAbi": "10.10.3.0",
"sourceUrl": "https://github.com/intro-skipper/intro-skipper/releases/download/10.10/v1.10.10.10/intro-skipper-v1.10.10.10.zip",
"checksum": "96ec29871e726ece01fbf7ccb24ae392",
"timestamp": "2024-11-24T18:33:26Z"
},
{
"version": "1.10.10.9",
"changelog": "- See the full changelog at [GitHub](https://github.com/intro-skipper/intro-skipper/releases/tag/10.10/v1.10.10.9)\n",
"targetAbi": "10.10.2.0",
"sourceUrl": "https://github.com/intro-skipper/intro-skipper/releases/download/10.10/v1.10.10.9/intro-skipper-v1.10.10.9.zip",
"checksum": "a39436ee4476a4e74acbafbaf1008dfb",
"timestamp": "2024-11-17T21:14:18Z"
}
]
}