Compare commits

...

2 Commits

Author SHA1 Message Date
Fredrik Burmester
8b6c7a7603 fix: incorrect matrix 2024-12-09 15:53:53 +01:00
Fredrik Burmester
5a07eccd9b fix: remove black background in some logo images 2024-12-09 15:39:23 +01:00
5 changed files with 158 additions and 109 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -231,7 +231,7 @@ export const DownloadItems: React.FC<DownloadProps> = ({
if (usingOptimizedServer) {
await startBackgroundDownload(url, item, source);
} else {
await startRemuxing(item, url, source);
await startRemuxing(item, url);
}
}
},

View File

@@ -32,6 +32,16 @@ import { Chromecast } from "./Chromecast";
import { ItemHeader } from "./ItemHeader";
import { MediaSourceSelector } from "./MediaSourceSelector";
import { MoreMoviesWithActor } from "./MoreMoviesWithActor";
import {
brightness,
ColorMatrix,
colorTone,
concatColorMatrices,
contrast,
saturate,
sepia,
tint,
} from "react-native-color-matrix-image-filters";
export type SelectedOptions = {
bitrate: Bitrate;
@@ -49,7 +59,7 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
const insets = useSafeAreaInsets();
useImageColors({ item });
const [loadingLogo, setLoadingLogo] = useState(true);
const [loadingLogo, setLoadingLogo] = useState(false);
const [headerHeight, setHeaderHeight] = useState(350);
const [selectedOptions, setSelectedOptions] = useState<
@@ -139,6 +149,34 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
logo={
<>
{logoUrl ? (
<ColorMatrix
matrix={[
1,
0,
0,
0,
0, // Red channel remains unchanged
0,
1,
0,
0,
0, // Green channel remains unchanged
0,
0,
1,
0,
0, // Blue channel remains unchanged
1,
1,
1,
1,
-1, // Make black (R=0, G=0, B=0) transparent
]}
style={{
height: 130,
width: "100%",
}}
>
<Image
source={{
uri: logoUrl,
@@ -148,9 +186,8 @@ export const ItemContent: React.FC<{ item: BaseItemDto }> = React.memo(
width: "100%",
resizeMode: "contain",
}}
onLoad={() => setLoadingLogo(false)}
onError={() => setLoadingLogo(false)}
/>
</ColorMatrix>
) : null}
</>
}

View File

@@ -35,8 +35,8 @@ const createFFmpegCommand = (url: string, output: string) => [
"-c copy", // streamcopy, preventing transcoding
"-bufsize 25M", // amount of data processed before calculating current bitrate
"-max_muxing_queue_size 4096", // sets the size of stream buffer in packets for output
output
]
output,
];
/**
* Custom hook for remuxing HLS to MP4 using FFmpeg.
@@ -66,9 +66,10 @@ export const useRemuxHlsToMp4 = () => {
});
await saveImage(item.Id, itemImage?.uri);
}
};
const completeCallback = useCallback(async (session: FFmpegSession, item: BaseItemDto) => {
const completeCallback = useCallback(
async (session: FFmpegSession, item: BaseItemDto) => {
try {
let endTime;
const returnCode = await session.getReturnCode();
@@ -77,15 +78,20 @@ export const useRemuxHlsToMp4 = () => {
if (returnCode.isValueSuccess()) {
endTime = new Date();
const stat = await session.getLastReceivedStatistics();
await queryClient.invalidateQueries({queryKey: ["downloadedItems"]});
await queryClient.invalidateQueries({
queryKey: ["downloadedItems"],
});
saveDownloadedItemInfo(item, stat.getSize());
writeInfoLog(
`useRemuxHlsToMp4 ~ remuxing completed successfully for item: ${item.Name},
`useRemuxHlsToMp4 ~ remuxing completed successfully for item: ${
item.Name
},
start time: ${startTime.toISOString()}, end time: ${endTime.toISOString()},
duration: ${(endTime.getTime() - startTime.getTime()) / 1000}s`
.replace(/^ +/g, '')
)
duration: ${
(endTime.getTime() - startTime.getTime()) / 1000
}s`.replace(/^ +/g, "")
);
toast.success("Download completed");
} else if (returnCode.isValueError()) {
endTime = new Date();
@@ -93,17 +99,19 @@ export const useRemuxHlsToMp4 = () => {
writeErrorLog(
`useRemuxHlsToMp4 ~ remuxing failed for item: ${item.Name},
start time: ${startTime.toISOString()}, end time: ${endTime.toISOString()},
duration: ${(endTime.getTime() - startTime.getTime()) / 1000}s. All logs: ${allLogs}`
.replace(/^ +/g, '')
)
duration: ${
(endTime.getTime() - startTime.getTime()) / 1000
}s. All logs: ${allLogs}`.replace(/^ +/g, "")
);
} else if (returnCode.isValueCancel()) {
endTime = new Date();
writeInfoLog(
`useRemuxHlsToMp4 ~ remuxing was canceled for item: ${item.Name},
start time: ${startTime.toISOString()}, end time: ${endTime.toISOString()},
duration: ${(endTime.getTime() - startTime.getTime()) / 1000}s`
.replace(/^ +/g, '')
)
duration: ${
(endTime.getTime() - startTime.getTime()) / 1000
}s`.replace(/^ +/g, "")
);
}
setProcesses((prev) => {
@@ -113,23 +121,24 @@ export const useRemuxHlsToMp4 = () => {
const error = e as Error;
writeErrorLog(
`useRemuxHlsToMp4 ~ Exception during remuxing for item: ${item.Name},
Error: ${error.message}, Stack: ${error.stack}`
.replace(/^ +/g, '')
Error: ${error.message}, Stack: ${error.stack}`.replace(/^ +/g, "")
);
}
}, [processes, setProcesses]);
},
[processes, setProcesses]
);
const statisticsCallback = useCallback((statistics: Statistics, item: BaseItemDto) => {
const videoLength = (item.MediaSources?.[0]?.RunTimeTicks || 0) / 10000000; // In seconds
const statisticsCallback = useCallback(
(statistics: Statistics, item: BaseItemDto) => {
const videoLength =
(item.MediaSources?.[0]?.RunTimeTicks || 0) / 10000000; // In seconds
const fps = item.MediaStreams?.[0]?.RealFrameRate || 25;
const totalFrames = videoLength * fps;
const processedFrames = statistics.getVideoFrameNumber();
const speed = statistics.getSpeed();
const percentage =
totalFrames > 0
? Math.floor((processedFrames / totalFrames) * 100)
: 0;
totalFrames > 0 ? Math.floor((processedFrames / totalFrames) * 100) : 0;
if (!item.Id) throw new Error("Item is undefined");
setProcesses((prev) => {
@@ -145,10 +154,12 @@ export const useRemuxHlsToMp4 = () => {
return process;
});
});
}, [setProcesses, completeCallback]);
},
[setProcesses, completeCallback]
);
const startRemuxing = useCallback(
async (item: BaseItemDto, url: string, mediaSource: MediaSourceInfo) => {
async (item: BaseItemDto, url: string) => {
const output = `${FileSystem.documentDirectory}${item.Id}.mp4`;
if (!api) throw new Error("API is not defined");
if (!item.Id) throw new Error("Item must have an Id");
@@ -177,17 +188,17 @@ export const useRemuxHlsToMp4 = () => {
progress: 0,
status: "downloading",
timestamp: new Date(),
}
};
writeInfoLog(`useRemuxHlsToMp4 ~ startRemuxing for item ${item.Name}`);
setProcesses((prev) => [...prev, job]);
await FFmpegKit.executeAsync(
createFFmpegCommand(url, output).join(" "),
session => completeCallback(session, item),
(session) => completeCallback(session, item),
undefined,
s => statisticsCallback(s, item)
)
(s) => statisticsCallback(s, item)
);
} catch (e) {
const error = e as Error;
console.error("Failed to remux:", error);

View File

@@ -73,6 +73,7 @@
"react-native-awesome-slider": "^2.5.6",
"react-native-bottom-tabs": "^0.7.3",
"react-native-circular-progress": "^1.4.1",
"react-native-color-matrix-image-filters": "^7.0.1",
"react-native-compressor": "^1.9.0",
"react-native-device-info": "^14.0.1",
"react-native-edge-to-edge": "^1.1.1",