Skip to content

Commit

Permalink
feat: add support for native PGS subtitle rendering without transcodi…
Browse files Browse the repository at this point in the history
…ng (#2404)

Co-authored-by: Fernando Fernández <ferferga@hotmail.com>
  • Loading branch information
Arcus92 and ferferga authored Aug 11, 2024
1 parent 5f4249b commit bc69685
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 3 deletions.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"fast-equals": "5.0.1",
"hls.js": "1.5.14",
"jassub": "1.7.16",
"libpgs": "0.4.1",
"lodash-es": "4.17.21",
"marked": "14.0.0",
"sortablejs": "1.15.2",
Expand Down
17 changes: 14 additions & 3 deletions frontend/src/store/playback-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,23 +291,23 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
srcLang: sub.Language ?? undefined,
type: sub.DeliveryMethod ?? SubtitleDeliveryMethod.Drop,
srcIndex: sub.srcIndex,
codec: sub.Codec === null ? undefined : sub.Codec
codec: sub.Codec === null ? undefined : sub.Codec?.toLowerCase()
}));
}
}

/**
* Filters the native subtitles
*
* As our profile requires either SSA or VTT, if it's not SSA it'll be VTT.
* As our profile requires either SSA, PGS or VTT, if it's not SSA or PGS it'll be VTT.
* This is done this way as server sends as "Codec" the initial value of the track, so it can be webvtt, subrip, srt...
* This is easier to filter out the SSA subs
*/
public get currentItemVttParsedSubtitleTracks(): PlaybackExternalTrack[] {
return (
this.currentItemParsedSubtitleTracks?.filter(
(sub): sub is PlaybackExternalTrack =>
!!sub.codec && sub.codec !== 'ass' && sub.codec !== 'ssa' && !!sub.src
!!sub.codec && sub.codec !== 'ass' && sub.codec !== 'ssa' && sub.codec !== 'pgssub' && !!sub.src
) ?? []
);
}
Expand All @@ -323,6 +323,17 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
);
}

public get currentItemPgsParsedSubtitleTracks(): PlaybackExternalTrack[] {
return (
this.currentItemParsedSubtitleTracks?.filter(
(sub): sub is PlaybackExternalTrack =>
!!sub.codec
&& (sub.codec === 'pgssub')
&& !!sub.src
) ?? []
);
}

public get currentVideoTrack(): MediaStream | undefined {
if (
!isNil(this._state.currentMediaSource?.MediaStreams)
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/store/player-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import JASSUB from 'jassub';
import jassubWorker from 'jassub/dist/jassub-worker.js?url';
import jassubWasmUrl from 'jassub/dist/jassub-worker.wasm?url';
import { PgsRenderer } from 'libpgs';
import pgssubWorker from 'libpgs/dist/libpgs.worker.js?url';
import { computed, nextTick, shallowRef, watch } from 'vue';
import { playbackManager } from './playback-manager';
import { isArray, isNil, sealed } from '@/utils/validation';
Expand Down Expand Up @@ -35,6 +37,7 @@ class PlayerElementStore extends CommonStore<PlayerElementState> {
*/
private readonly _fullscreenVideoRoute = '/playback/video';
private _jassub: JASSUB | undefined;
private _pgssub: PgsRenderer | undefined;
protected _storeKey = 'playerElement';

public readonly isStretched = computed({
Expand Down Expand Up @@ -105,6 +108,30 @@ class PlayerElementStore extends CommonStore<PlayerElementState> {
);
};

private readonly _setPgsTrack = (trackSrc: string): void => {
if (
!this._pgssub
&& mediaElementRef.value
&& mediaElementRef.value instanceof HTMLVideoElement
) {
this._pgssub = new PgsRenderer({
video: mediaElementRef.value,
subUrl: trackSrc,
workerUrl: pgssubWorker
});
} else if (this._pgssub) {
this._pgssub.loadFromUrl(trackSrc);
}
};

private readonly _freePgsTrack = (): void => {
if (this._pgssub) {
this._pgssub.dispose();
}

this._pgssub = undefined;
};

/**
* Applies the current subtitle from the playbackManager store
*
Expand All @@ -128,6 +155,9 @@ class PlayerElementStore extends CommonStore<PlayerElementState> {
const ass = playbackManager.currentItemAssParsedSubtitleTracks.find(
sub => sub.srcIndex === playbackManager.currentSubtitleStreamIndex
);
const pgs = playbackManager.currentItemPgsParsedSubtitleTracks.find(
sub => sub.srcIndex === playbackManager.currentSubtitleStreamIndex
);
const attachedFonts
= playbackManager.currentMediaSource?.MediaAttachments?.filter(a =>
this._isSupportedFont(a.MimeType)
Expand Down Expand Up @@ -155,6 +185,7 @@ class PlayerElementStore extends CommonStore<PlayerElementState> {
}

this._freeSsaTrack();
this._freePgsTrack();

if (vttIdx !== -1 && mediaElementRef.value.textTracks[vttIdx]) {
/**
Expand All @@ -166,6 +197,11 @@ class PlayerElementStore extends CommonStore<PlayerElementState> {
* If SSA, using Subtitle Opctopus
*/
this._setSsaTrack(ass.src, attachedFonts);
} else if (pgs?.src) {
/**
* If PGS, using libpgs to render
*/
this._setPgsTrack(pgs.src);
}
};

Expand Down Expand Up @@ -196,6 +232,7 @@ class PlayerElementStore extends CommonStore<PlayerElementState> {
watch(videoContainerRef, () => {
if (!videoContainerRef.value) {
this._freeSsaTrack();
this._freePgsTrack();
}
}, { flush: 'sync' });

Expand Down
4 changes: 4 additions & 0 deletions frontend/src/utils/playback-profiles/subtitle-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export function getSubtitleProfiles(): SubtitleProfile[] {
{
Format: 'ssa',
Method: SubtitleDeliveryMethod.External
},
{
Format: 'pgssub',
Method: SubtitleDeliveryMethod.External
}
);

Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit bc69685

Please sign in to comment.