1
0
Fork 0
mirror of https://github.com/sussy-code/smov.git synced 2024-12-20 14:37:43 +01:00

Merge branch 'dev' into dev

This commit is contained in:
mrjvs 2023-03-10 19:49:51 +01:00 committed by GitHub
commit 02d94ba411
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 179 additions and 21 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
* text=auto eol=lf

View file

@ -18,12 +18,13 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'yarn'
- name: Install Yarn packages
run: yarn install
- name: Build project
run: npm run build
run: yarn build
- name: Upload production-ready build files
uses: actions/upload-artifact@v3

48
.github/workflows/linting_annotate.yml vendored Normal file
View file

@ -0,0 +1,48 @@
name: Annotate linting
permissions:
actions: read # download artifact
checks: write # annotate
# this is done as a seperate workflow so
# the annotater has access to write to checks (to annotate)
on:
workflow_run:
workflows: ["Linting and Testing"]
types:
- completed
jobs:
annotate:
name: Annotate linting
runs-on: ubuntu-latest
steps:
- name: Download linting report
uses: actions/github-script@v6
with:
script: |
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
const matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "eslint_report.json"
})[0];
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
const fs = require('fs');
fs.writeFileSync('${{github.workspace}}/eslint_report.zip', Buffer.from(download.data));
- run: unzip eslint_report.zip
- name: Annotate linting
uses: ataylorme/eslint-annotate-action@v2
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
report-json: "eslint_report.json"

View file

@ -5,8 +5,7 @@ on:
branches:
- master
- dev
pull_request_target:
types: [opened, reopened, synchronize]
pull_request:
jobs:
linting:
@ -21,6 +20,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'yarn'
- name: Install Yarn packages
run: yarn install
@ -30,11 +30,27 @@ jobs:
# continue on error, so it still reports it in the next step
continue-on-error: true
- name: Annotate Code Linting Results
uses: ataylorme/eslint-annotate-action@v2
- uses: actions/upload-artifact@v3
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
report-json: "eslint_report.json"
name: eslint_report.json
path: eslint_report.json
building:
name: Build project
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
cache: 'yarn'
- name: Install Yarn packages
run: yarn install
- name: Build Project
run: npm run build
run: yarn build

View file

@ -2,6 +2,7 @@ import { mwFetch, proxiedFetch } from "@/backend/helpers/fetch";
import { MWCaption, MWCaptionType } from "@/backend/helpers/streams";
import toWebVTT from "srt-webvtt";
export const CUSTOM_CAPTION_ID = "customCaption";
export async function getCaptionUrl(caption: MWCaption): Promise<string> {
if (caption.type === MWCaptionType.SRT) {
let captionBlob: Blob;
@ -32,3 +33,18 @@ export async function getCaptionUrl(caption: MWCaption): Promise<string> {
throw new Error("invalid type");
}
export async function convertCustomCaptionFileToWebVTT(file: File) {
const header = await file.slice(0, 6).text();
const isWebVTT = header === "WEBVTT";
if (!isWebVTT) {
return toWebVTT(file);
}
return URL.createObjectURL(file);
}
export function revokeCaptionBlob(url: string | undefined) {
if (url && url.startsWith("blob:")) {
URL.revokeObjectURL(url);
}
}

View file

@ -1,6 +1,10 @@
import { proxiedFetch } from "../helpers/fetch";
import { registerProvider } from "../helpers/register";
import { MWStreamQuality, MWStreamType } from "../helpers/streams";
import {
MWCaptionType,
MWStreamQuality,
MWStreamType,
} from "../helpers/streams";
import { MWMediaType } from "../metadata/types";
const netfilmBase = "https://net-film.vercel.app";
@ -18,7 +22,6 @@ registerProvider({
displayName: "NetFilm",
rank: 15,
type: [MWMediaType.MOVIE, MWMediaType.SERIES],
disabled: true, // https://github.com/lamhoang1256/netfilm/issues/25
async scrape({ media, episode, progress }) {
// search for relevant item
@ -48,20 +51,29 @@ registerProvider({
}
);
const { qualities } = watchInfo.data;
const data = watchInfo.data;
// get best quality source
const source = qualities.reduce((p: any, c: any) =>
const source = data.qualities.reduce((p: any, c: any) =>
c.quality > p.quality ? c : p
);
const mappedCaptions = data.subtitles.map((sub: Record<string, any>) => ({
needsProxy: false,
url: sub.url.replace("https://convert-srt-to-vtt.vercel.app/?url=", ""),
type: MWCaptionType.SRT,
langIso: sub.language,
}));
return {
embeds: [],
stream: {
streamUrl: source.url,
streamUrl: source.url
.replace("akm-cdn", "aws-cdn")
.replace("gg-cdn", "aws-cdn"),
quality: qualityMap[source.quality as QualityInMap],
type: MWStreamType.HLS,
captions: [],
captions: mappedCaptions,
},
};
}
@ -109,20 +121,29 @@ registerProvider({
}
);
const { qualities } = episodeStream.data;
const data = episodeStream.data;
// get best quality source
const source = qualities.reduce((p: any, c: any) =>
const source = data.qualities.reduce((p: any, c: any) =>
c.quality > p.quality ? c : p
);
const mappedCaptions = data.subtitles.map((sub: Record<string, any>) => ({
needsProxy: false,
url: sub.url.replace("https://convert-srt-to-vtt.vercel.app/?url=", ""),
type: MWCaptionType.SRT,
langIso: sub.language,
}));
return {
embeds: [],
stream: {
streamUrl: source.url,
streamUrl: source.url
.replace("akm-cdn", "aws-cdn")
.replace("gg-cdn", "aws-cdn"),
quality: qualityMap[source.quality as QualityInMap],
type: MWStreamType.HLS,
captions: [],
captions: mappedCaptions,
},
};
},

View file

@ -71,6 +71,8 @@
"episode": "E{{index}} - {{title}}",
"noCaptions": "No captions",
"linkedCaptions": "Linked captions",
"customCaption": "Custom caption",
"uploadCustomCaption": "Upload caption (SRT, VTT)",
"noEmbeds": "No embeds were found for this source",
"errors": {
"loadingWentWong": "Something went wrong loading the episodes for {{seasonTitle}}",

View file

@ -1,4 +1,8 @@
import { getCaptionUrl } from "@/backend/helpers/captions";
import {
getCaptionUrl,
convertCustomCaptionFileToWebVTT,
CUSTOM_CAPTION_ID,
} from "@/backend/helpers/captions";
import { MWCaption } from "@/backend/helpers/streams";
import { Icon, Icons } from "@/components/Icon";
import { useLoading } from "@/hooks/useLoading";
@ -6,7 +10,7 @@ import { useVideoPlayerDescriptor } from "@/video/state/hooks";
import { useControls } from "@/video/state/logic/controls";
import { useMeta } from "@/video/state/logic/meta";
import { useSource } from "@/video/state/logic/source";
import { useMemo, useRef } from "react";
import { ChangeEvent, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import { PopoutListEntry, PopoutSection } from "./PopoutUtils";
@ -37,6 +41,29 @@ export function CaptionSelectionPopout() {
);
const currentCaption = source.source?.caption?.id;
const customCaptionUploadElement = useRef<HTMLInputElement>(null);
const [setCustomCaption, loadingCustomCaption, errorCustomCaption] =
useLoading(async (captionFile: File) => {
if (
!captionFile.name.endsWith(".srt") &&
!captionFile.name.endsWith(".vtt")
) {
throw new Error("Only SRT or VTT files are allowed");
}
controls.setCaption(
CUSTOM_CAPTION_ID,
await convertCustomCaptionFileToWebVTT(captionFile)
);
controls.closePopout();
});
async function handleUploadCaption(e: ChangeEvent<HTMLInputElement>) {
if (!e.target.files) {
return;
}
const captionFile = e.target.files[0];
setCustomCaption(captionFile);
}
return (
<>
@ -54,6 +81,26 @@ export function CaptionSelectionPopout() {
>
{t("videoPlayer.popouts.noCaptions")}
</PopoutListEntry>
<PopoutListEntry
key={CUSTOM_CAPTION_ID}
active={currentCaption === CUSTOM_CAPTION_ID}
loading={loadingCustomCaption}
errored={!!errorCustomCaption}
onClick={() => {
customCaptionUploadElement.current?.click();
}}
>
{currentCaption === CUSTOM_CAPTION_ID
? t("videoPlayer.popouts.customCaption")
: t("videoPlayer.popouts.uploadCustomCaption")}
<input
ref={customCaptionUploadElement}
type="file"
onChange={handleUploadCaption}
className="hidden"
accept=".vtt, .srt"
/>
</PopoutListEntry>
</PopoutSection>
<p className="sticky top-0 z-10 flex items-center space-x-1 bg-ash-200 px-5 py-3 text-sm font-bold uppercase">

View file

@ -96,7 +96,7 @@ export function PopoutListEntry(props: PopoutListEntryTypes) {
return (
<div
className={[
"group -mx-2 flex cursor-pointer items-center justify-between space-x-1 rounded p-2 font-semibold transition-[background-color,color] duration-150",
"group my-2 -mx-2 flex cursor-pointer items-center justify-between space-x-1 rounded p-2 font-semibold transition-[background-color,color] duration-150",
hover,
props.active
? `${bg} active text-white outline-denim-700`

View file

@ -12,6 +12,7 @@ import {
} from "@/video/components/hooks/volumeStore";
import { resetStateForSource } from "@/video/state/providers/helpers";
import { updateInterface } from "@/video/state/logic/interface";
import { revokeCaptionBlob } from "@/backend/helpers/captions";
import { getPlayerState } from "../cache";
import { updateMediaPlaying } from "../logic/mediaplaying";
import { VideoPlayerStateProvider } from "./providerTypes";
@ -138,6 +139,7 @@ export function createCastingStateProvider(
},
setCaption(id, url) {
if (state.source) {
revokeCaptionBlob(state.source.caption?.url);
state.source.caption = {
id,
url,
@ -147,6 +149,7 @@ export function createCastingStateProvider(
},
clearCaption() {
if (state.source) {
revokeCaptionBlob(state.source.caption?.url);
state.source.caption = null;
updateSource(descriptor, state);
}

View file

@ -18,6 +18,7 @@ import {
import { updateError } from "@/video/state/logic/error";
import { updateMisc } from "@/video/state/logic/misc";
import { resetStateForSource } from "@/video/state/providers/helpers";
import { revokeCaptionBlob } from "@/backend/helpers/captions";
import { getPlayerState } from "../cache";
import { updateMediaPlaying } from "../logic/mediaplaying";
import { VideoPlayerStateProvider } from "./providerTypes";
@ -193,6 +194,7 @@ export function createVideoStateProvider(
},
setCaption(id, url) {
if (state.source) {
revokeCaptionBlob(state.source.caption?.url);
state.source.caption = {
id,
url,
@ -202,6 +204,7 @@ export function createVideoStateProvider(
},
clearCaption() {
if (state.source) {
revokeCaptionBlob(state.source.caption?.url);
state.source.caption = null;
updateSource(descriptor, state);
}