Skip to content

Commit

Permalink
Feat: ass subtitle support
Browse files Browse the repository at this point in the history
  • Loading branch information
LazyCreeper committed May 9, 2024
1 parent c25efae commit 4b689b4
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 17 deletions.
2 changes: 2 additions & 0 deletions env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/// <reference types="vite/client" />

declare module "libass-wasm";
6 changes: 6 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"hls.js": "^1.5.7",
"less": "^4.1.3",
"less-loader": "^11.1.3",
"libass-wasm": "^4.1.0",
"lodash": "^4.17.21",
"mpegts.js": "^1.7.3",
"nprogress": "^0.2.0",
Expand Down
26 changes: 21 additions & 5 deletions src/hooks/useMovie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,28 @@ export const useMovieApi = (roomToken: string) => {
headers: { Authorization: roomToken }
});

if (currentMovie.value) {
console.log(currentMovie.value);
room.currentMovie = currentMovie.value.movie;
room.currentStatus = currentMovie.value.status;
room.currentExpireId = currentMovie.value.expireId;
if (!currentMovie.value) return;

room.currentMovie = currentMovie.value.movie;
room.currentStatus = currentMovie.value.status;
room.currentExpireId = currentMovie.value.expireId;

const url = currentMovie.value.movie.base.url;
// when cross origin, add token to headers and query
if (url.startsWith(window.location.origin) || url.startsWith("/api/movie")) {
room.currentMovie.base.url = url.includes("?")
? `${url}&token=${roomToken}`
: `${url}?token=${roomToken}`;
}

const defaultSubtitle = currentMovie.value.movie.base.subtitles;
for (let key in defaultSubtitle) {
if (defaultSubtitle[key].url.includes("token=")) continue;
defaultSubtitle[key].url.includes("?")
? (defaultSubtitle[key].url = `${defaultSubtitle[key].url}&token=${roomToken}`)
: (defaultSubtitle[key].url = `${defaultSubtitle[key].url}?token=${roomToken}`);
}
room.currentMovie.base.subtitles = defaultSubtitle;
} catch (err: any) {
console.log(err);
ElNotification({
Expand Down
Binary file not shown.
Binary file not shown.
12 changes: 12 additions & 0 deletions src/plugins/artplayer-plugin-ass/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type Artplayer from "artplayer"
import type SubtitlesOctopus from "libass-wasm"
import { type Options } from "libass-wasm"

export = artplayerPluginAss
export as namespace artplayerPluginAss
type Ass = {
name: "artplayerPluginAss"
instance: SubtitlesOctopus
}

declare const artplayerPluginAss: (options: Options) => (art: Artplayer) => Ass
110 changes: 110 additions & 0 deletions src/plugins/artplayer-plugin-ass/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/**
* ref: https://github.com/alist-org/alist-web/blob/main/src/components/artplayer-plugin-ass/index.js
*/
import SubtitlesOctopus from "libass-wasm"
import workerUrl from "libass-wasm/dist/js/subtitles-octopus-worker.js?url"
import wasmUrl from "libass-wasm/dist/js/subtitles-octopus-worker.wasm?url"

import TimesNewRomanFont from "./fonts/TimesNewRoman.ttf?url"
import fallbackFont from "./fonts/SourceHanSansCN-Bold.woff2?url"

let instance = null

function isAbsoluteUrl(url) {
return /^https?:\/\//.test(url)
}

function toAbsoluteUrl(url) {
if (isAbsoluteUrl(url)) return url

// handle absolute URL when the `Worker` of `BLOB` type loading network resources
return new URL(url, document.baseURI).toString()
}

function loadWorker({ workerUrl, wasmUrl }) {
return new Promise((resolve) => {
fetch(workerUrl)
.then((res) => res.text())
.then((text) => {
let workerScriptContent = text

workerScriptContent = workerScriptContent.replace(
/wasmBinaryFile\s*=\s*"(subtitles-octopus-worker\.wasm)"/g,
(_match, wasm) => {
if (!wasmUrl) {
wasmUrl = new URL(wasm, toAbsoluteUrl(workerUrl)).toString()
} else {
wasmUrl = toAbsoluteUrl(wasmUrl)
}

return `wasmBinaryFile = "${wasmUrl}"`
},
)

const workerBlob = new Blob([workerScriptContent], {
type: "text/javascript",
})
resolve(URL.createObjectURL(workerBlob))
})
})
}

function setVisible(visible) {
if (instance.canvasParent)
instance.canvasParent.style.display = visible ? "block" : "none"
}

function artplayerPluginAss(options) {
return async (art) => {
instance = new SubtitlesOctopus({
// TODO: load available fonts from manage panel
availableFonts: {
"times new roman": toAbsoluteUrl(TimesNewRomanFont),
},
workerUrl: await loadWorker({ workerUrl, wasmUrl }),
fallbackFont: toAbsoluteUrl(fallbackFont),
video: art.template.$video,
...options,
})

instance.canvasParent.className = "artplayer-plugin-ass"
instance.canvasParent.style.cssText = `
position: absolute;
width: 100%;
height: 100%;
user-select: none;
pointer-events: none;
z-index: 20;
`
// switch subtitle track
art.on("artplayer-plugin-ass:switch", (subtitle) => {
let newSubAddr
if (subtitle.startsWith('/api/movie/proxy/')) {
newSubAddr = window.location.origin + subtitle
} else {
newSubAddr = subtitle
}
instance.freeTrack()
instance.setTrackByUrl(newSubAddr)
console.log("plugin->切换字幕:", newSubAddr);
setVisible(true)
})

// set subtitle visible
art.on("subtitle", (visible) => setVisible(visible))
art.on("artplayer-plugin-ass:visible", (visible) => setVisible(visible))

// set subtitle offset
art.on("subtitleOffset", (offset) => (instance.timeOffset = offset))

// when player destory
art.on("destroy", () => instance.dispose())

return {
name: "artplayerPluginAss",
instance: instance,
}
}
}

export default artplayerPluginAss
13 changes: 12 additions & 1 deletion src/plugins/subtitle.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type Artplayer from "artplayer";
import type { ComponentOption } from "artplayer/types/component";
import type { Events } from "artplayer/types/events";
import { ElMessage } from "element-plus";

const newSubtitleHtml = (name: string): HTMLElement => {
const SubtitleHtml = document.createElement("span");
Expand Down Expand Up @@ -39,10 +41,19 @@ export const newSubtitleControl = (
};
}),
onSelect(this: Artplayer, selector: any) {
console.log("切换字幕:", selector);
if (selector.html === "关闭") {
this.subtitle.show = false;
this.emit("artplayer-plugin-ass:visible" as keyof Events, false);
} else if (selector.type.toLowerCase() === "ass") {
let newUrl;
if (selector.url.startsWith("/api/movie/proxy/")) {
newUrl + window.location.origin + selector.url;
} else if (!selector.url.startsWith("http")) return ElMessage.error("无效的字幕地址");
newUrl = selector.url;
this.subtitle.show = false;
this.emit("artplayer-plugin-ass:switch" as keyof Events, newUrl);
} else {
this.emit("artplayer-plugin-ass:visible" as keyof Events, false);
this.subtitle.switch(selector.url, { type: selector.type });
this.subtitle.show = true;
}
Expand Down
53 changes: 43 additions & 10 deletions src/views/Cinema.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import MovieList from "@/components/cinema/MovieList.vue";
import MoviePush from "@/components/cinema/MoviePush.vue";
import type { Subtitles } from "@/types/Movie";
import { RoomMemberPermission } from "@/types/Room";
import artplayerPluginAss from "@/plugins/artplayer-plugin-ass";
const Player = defineAsyncComponent(() => import("@/components/Player.vue"));
Expand Down Expand Up @@ -139,18 +140,50 @@ const playerOption = computed<options>(() => {
newLazyInitSyncPlugin(room.currentExpireId)
]
};
// when cross origin, add token to headers and query
if (option.url.startsWith(window.location.origin) || option.url.startsWith("/api/movie")) {
// option.headers = {
// ...option.headers,
// Authorization: roomToken.value
// };
option.url = option.url.includes("?")
? `${option.url}&token=${roomToken.value}`
: `${option.url}?token=${roomToken.value}`;
}
if (room.currentMovie.base!.subtitles) {
let defaultUrl;
let useAssPlugin = false;
const defaultSubtitle = room.currentMovie.base!.subtitles;
for (let key in defaultSubtitle) {
if (defaultSubtitle[key].hasOwnProperty("url")) {
if (defaultSubtitle[key].type === "ass") {
if (defaultSubtitle[key].url.startsWith("/api/movie/proxy/")) {
defaultUrl = window.location.origin + room.currentMovie.base!.subtitles[key].url;
useAssPlugin = true;
break;
}
if (defaultSubtitle[key].url.startsWith("http")) {
defaultUrl = room.currentMovie.base!.subtitles[key].url;
useAssPlugin = true;
break;
}
ElNotification.error({
title: "错误",
message: "字幕文件地址错误!ASS字幕解析将失效!"
});
useAssPlugin = false;
break;
} else {
useAssPlugin = false;
break;
}
} else break;
}
option.plugins!.push(newLazyInitSubtitlePlugin(room.currentMovie.base!.subtitles));
// return;
useAssPlugin &&
option.plugins!.push(
artplayerPluginAss({
// debug: true,
subUrl: defaultUrl
})
);
}
return option;
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"compilerOptions": {
"target": "ES2016",
"noImplicitAny": true,
"moduleResolution": "bundler"
"moduleResolution": "bundler",
"allowJs": true,
}
}

0 comments on commit 4b689b4

Please sign in to comment.