Revamped transcoding subtitles

This commit is contained in:
Alex Kim
2024-12-12 02:41:30 +11:00
parent 1d0d99c79b
commit 3fb20a8ca2
8 changed files with 215 additions and 361 deletions

View File

@@ -4,7 +4,8 @@ import {
BaseItemDto,
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client";
import { Settings } from "../atoms/settings";
import { Settings, useSettings } from "../atoms/settings";
import { StreamRanker, SubtitleStreamRanker } from "../streamRanker";
interface PlaySettings {
item: BaseItemDto;
@@ -14,9 +15,13 @@ interface PlaySettings {
subtitleIndex?: number | undefined;
}
// Used getting default values for the next player.
export function getDefaultPlaySettings(
item: BaseItemDto,
settings: Settings
settings: Settings,
previousIndex?: number,
previousItem?: BaseItemDto,
previousSource?: MediaSourceInfo
): PlaySettings {
if (item.Type === "Program") {
return {
@@ -41,7 +46,22 @@ export function getDefaultPlaySettings(
(x) => x.Type === "Audio"
)?.Index;
// TODO: Need to most common next subtitle index as an option.
// We prefer the previous track over the default track.
let trackOptions = {};
const mediaStreams = mediaSource?.MediaStreams ?? [];
if (settings?.rememberSubtitleSelections) {
if (previousIndex !== undefined && previousSource) {
const subtitleRanker = new SubtitleStreamRanker();
const ranker = new StreamRanker(subtitleRanker);
ranker.rankStream(
previousIndex,
previousSource,
mediaStreams,
trackOptions
);
}
}
const finalSubtitleIndex = mediaSource?.DefaultAudioStreamIndex;
// 4. Get default bitrate

View File

@@ -1,248 +1,35 @@
interface StreamRankerStrategy {
rankStream(
prevIndex: number,
prevSource: any,
mediaStreams: any[],
trackOptions: any,
isSecondarySubtitle: boolean
): void;
}
import {
MediaSourceInfo,
MediaStream,
} from "@jellyfin/sdk/lib/generated-client";
class SubtitleStreamRanker implements StreamRankerStrategy {
rankStream(
abstract class StreamRankerStrategy {
abstract streamType: string;
abstract rankStream(
prevIndex: number,
prevSource: any,
mediaStreams: any[],
trackOptions: any,
isSecondarySubtitle: boolean
prevSource: MediaSourceInfo,
mediaStreams: MediaStream[],
trackOptions: any
): void;
protected rank(
prevIndex: number,
prevSource: MediaSourceInfo,
mediaStreams: MediaStream[],
trackOptions: any
): void {
if (prevIndex == -1) {
console.debug(`AutoSet Subtitle - No Stream Set`);
if (isSecondarySubtitle) {
trackOptions.DefaultSecondarySubtitleStreamIndex = -1;
} else {
trackOptions.DefaultSubtitleStreamIndex = -1;
}
trackOptions[`Default${this.streamType}StreamIndex`] = -1;
return;
}
if (!prevSource.MediaStreams || !mediaStreams) {
console.debug(`AutoSet Subtitle - No MediaStreams`);
console.debug(`AutoSet ${this.streamType} - No MediaStreams`);
return;
}
this.rank(
prevIndex,
prevSource,
mediaStreams,
trackOptions,
isSecondarySubtitle,
"Subtitle"
);
}
private rank(
prevIndex: number,
prevSource: any,
mediaStreams: any[],
trackOptions: any,
isSecondarySubtitle: boolean,
streamType: string
): void {
let bestStreamIndex = null;
let bestStreamScore = 0;
const prevStream = prevSource.MediaStreams[prevIndex];
if (!prevStream) {
console.debug(`AutoSet ${streamType} - No prevStream`);
return;
}
console.debug(
`AutoSet ${streamType} - Previous was ${prevStream.Index} - ${prevStream.DisplayTitle}`
);
let prevRelIndex = 0;
for (const stream of prevSource.MediaStreams) {
if (stream.Type != streamType) continue;
if (stream.Index == prevIndex) break;
prevRelIndex += 1;
}
let newRelIndex = 0;
for (const stream of mediaStreams) {
if (stream.Type != streamType) continue;
let score = 0;
if (prevStream.Codec == stream.Codec) score += 1;
if (prevRelIndex == newRelIndex) score += 1;
if (
prevStream.DisplayTitle &&
prevStream.DisplayTitle == stream.DisplayTitle
)
score += 2;
if (
prevStream.Language &&
prevStream.Language != "und" &&
prevStream.Language == stream.Language
)
score += 2;
console.debug(
`AutoSet ${streamType} - Score ${score} for ${stream.Index} - ${stream.DisplayTitle}`
);
if (score > bestStreamScore && score >= 3) {
bestStreamScore = score;
bestStreamIndex = stream.Index;
}
newRelIndex += 1;
}
if (bestStreamIndex != null) {
console.debug(
`AutoSet ${streamType} - Using ${bestStreamIndex} score ${bestStreamScore}.`
);
trackOptions.DefaultSubtitleStreamIndex = bestStreamIndex;
} else {
console.debug(
`AutoSet ${streamType} - Threshold not met. Using default.`
);
}
}
}
class AudioStreamRanker implements StreamRankerStrategy {
rankStream(
prevIndex: number,
prevSource: any,
mediaStreams: any[],
trackOptions: any
): void {
if (prevIndex == -1) {
console.debug(`AutoSet Audio - No Stream Set`);
return;
}
if (!prevSource.MediaStreams || !mediaStreams) {
console.debug(`AutoSet Audio - No MediaStreams`);
return;
}
this.rank(prevIndex, prevSource, mediaStreams, trackOptions, "Audio");
}
private rank(
prevIndex: number,
prevSource: any,
mediaStreams: any[],
trackOptions: any,
streamType: string
): void {
let bestStreamIndex = null;
let bestStreamScore = 0;
const prevStream = prevSource.MediaStreams[prevIndex];
if (!prevStream) {
console.debug(`AutoSet ${streamType} - No prevStream`);
return;
}
console.debug(
`AutoSet ${streamType} - Previous was ${prevStream.Index} - ${prevStream.DisplayTitle}`
);
let prevRelIndex = 0;
for (const stream of prevSource.MediaStreams) {
if (stream.Type != streamType) continue;
if (stream.Index == prevIndex) break;
prevRelIndex += 1;
}
let newRelIndex = 0;
for (const stream of mediaStreams) {
if (stream.Type != streamType) continue;
let score = 0;
if (prevStream.Codec == stream.Codec) score += 1;
if (prevRelIndex == newRelIndex) score += 1;
if (
prevStream.DisplayTitle &&
prevStream.DisplayTitle == stream.DisplayTitle
)
score += 2;
if (
prevStream.Language &&
prevStream.Language != "und" &&
prevStream.Language == stream.Language
)
score += 2;
console.debug(
`AutoSet ${streamType} - Score ${score} for ${stream.Index} - ${stream.DisplayTitle}`
);
if (score > bestStreamScore && score >= 3) {
bestStreamScore = score;
bestStreamIndex = stream.Index;
}
newRelIndex += 1;
}
if (bestStreamIndex != null) {
console.debug(
`AutoSet ${streamType} - Using ${bestStreamIndex} score ${bestStreamScore}.`
);
trackOptions.DefaultAudioStreamIndex = bestStreamIndex;
} else {
console.debug(
`AutoSet ${streamType} - Threshold not met. Using default.`
);
}
}
}
abstract class StreamRanker {
private strategy: StreamRankerStrategy;
abstract streamType: string;
constructor(strategy: StreamRankerStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: StreamRankerStrategy) {
this.strategy = strategy;
}
rankStream(
prevIndex: number,
prevSource: any,
mediaStreams: any[],
trackOptions: any,
streamType: string,
isSecondarySubtitle: boolean
) {
this.strategy.rankStream(
prevIndex,
prevSource,
mediaStreams,
trackOptions,
isSecondarySubtitle
);
}
private rank(
prevIndex: number,
prevSource: any,
mediaStreams: any[],
trackOptions: any
): void {
let bestStreamIndex = null;
let bestStreamScore = 0;
const prevStream = prevSource.MediaStreams[prevIndex];
@@ -308,3 +95,52 @@ abstract class StreamRanker {
}
}
}
class SubtitleStreamRanker extends StreamRankerStrategy {
streamType = "Subtitle";
rankStream(
prevIndex: number,
prevSource: MediaSourceInfo,
mediaStreams: MediaStream[],
trackOptions: any
): void {
super.rank(prevIndex, prevSource, mediaStreams, trackOptions);
}
}
class AudioStreamRanker extends StreamRankerStrategy {
streamType = "Audio";
rankStream(
prevIndex: number,
prevSource: MediaSourceInfo,
mediaStreams: MediaStream[],
trackOptions: any
): void {
super.rank(prevIndex, prevSource, mediaStreams, trackOptions);
}
}
class StreamRanker {
private strategy: StreamRankerStrategy;
constructor(strategy: StreamRankerStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: StreamRankerStrategy) {
this.strategy = strategy;
}
rankStream(
prevIndex: number,
prevSource: MediaSourceInfo,
mediaStreams: MediaStream[],
trackOptions: any
) {
this.strategy.rankStream(prevIndex, prevSource, mediaStreams, trackOptions);
}
}
export { StreamRanker, SubtitleStreamRanker, AudioStreamRanker };