forked from Ninjalama/streamyfin_mirror
Compare commits
2 Commits
fix/maven-
...
feat/remov
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b6c7a7603 | ||
|
|
5a07eccd9b |
@@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user