Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d88f910b58 | |||
| 512dabc611 | |||
| 78e100c99e | |||
| b2885aa163 | |||
| 73f4bb488a | |||
| 9042643383 | |||
| 739c96c376 | |||
| 14831c6cd5 | |||
| 5cdde631e1 | |||
| 83f3c2975a |
@@ -136,8 +136,76 @@ jobs:
|
||||
except Exception as e:
|
||||
print(f"Asset upload error: {e}")
|
||||
|
||||
# 3. Update manifest.json on master branch
|
||||
clean_ver = tag.lstrip("v").split("-")[0]
|
||||
# 3. Build manifest.json dynamically from Gitea Releases API
|
||||
rel_list_req = urllib.request.Request(f"{api_url}/repos/{repo}/releases?per_page=100", headers=headers)
|
||||
all_releases = []
|
||||
try:
|
||||
res_list = urllib.request.urlopen(rel_list_req)
|
||||
all_releases = json.loads(res_list.read().decode("utf-8"))
|
||||
except Exception as e:
|
||||
print(f"Notice fetching releases list: {e}")
|
||||
|
||||
all_versions = []
|
||||
for r in all_releases:
|
||||
rel_tag = r.get("tag_name")
|
||||
if not rel_tag:
|
||||
continue
|
||||
clean_v = rel_tag.lstrip("v").split("-")[0]
|
||||
ts = r.get("published_at") or r.get("created_at") or "2026-09-17T20:00:00Z"
|
||||
assets = r.get("assets", [])
|
||||
|
||||
if rel_tag == tag:
|
||||
all_versions.append({
|
||||
"version": clean_v,
|
||||
"changelog": f"Release {tag} (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{tag}/BlackBarRemover_{tag}_jf12.0.zip",
|
||||
"checksum": md5_10,
|
||||
"timestamp": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
})
|
||||
all_versions.append({
|
||||
"version": clean_v,
|
||||
"changelog": f"Release {tag} (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{tag}/BlackBarRemover_{tag}_jf10.11.zip",
|
||||
"checksum": md5_9,
|
||||
"timestamp": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
})
|
||||
else:
|
||||
a12 = next((a for a in assets if "jf12.0" in a.get("name", "")), None)
|
||||
if a12:
|
||||
a12_name = a12.get("name")
|
||||
all_versions.append({
|
||||
"version": clean_v,
|
||||
"changelog": f"Release {rel_tag} (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{rel_tag}/{a12_name}",
|
||||
"checksum": a12.get("md5") or "",
|
||||
"timestamp": ts
|
||||
})
|
||||
a10 = next((a for a in assets if "jf10.11" in a.get("name", "")), None)
|
||||
if a10:
|
||||
a10_name = a10.get("name")
|
||||
all_versions.append({
|
||||
"version": clean_v,
|
||||
"changelog": f"Release {rel_tag} (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{rel_tag}/{a10_name}",
|
||||
"checksum": a10.get("md5") or "",
|
||||
"timestamp": ts
|
||||
})
|
||||
if not a12 and not a10 and assets:
|
||||
alegacy = assets[0]
|
||||
leg_name = alegacy.get("name")
|
||||
all_versions.append({
|
||||
"version": clean_v,
|
||||
"changelog": f"Release {rel_tag}",
|
||||
"targetAbi": "10.9.0.0",
|
||||
"sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{rel_tag}/{leg_name}",
|
||||
"checksum": alegacy.get("md5") or "",
|
||||
"timestamp": ts
|
||||
})
|
||||
|
||||
manifest_data = [
|
||||
{
|
||||
"guid": "12345678-1234-1234-1234-1234567890ab",
|
||||
@@ -147,49 +215,36 @@ jobs:
|
||||
"owner": "karol",
|
||||
"category": "Playback",
|
||||
"imageUrl": "",
|
||||
"versions": [
|
||||
{
|
||||
"version": clean_ver,
|
||||
"changelog": f"Automated Release {tag} (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{tag}/BlackBarRemover_{tag}_jf12.0.zip",
|
||||
"checksum": md5_10,
|
||||
"timestamp": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
},
|
||||
{
|
||||
"version": clean_ver,
|
||||
"changelog": f"Automated Release {tag} (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": f"https://gitea.labus.uk/karol/blackBarRemover/releases/download/{tag}/BlackBarRemover_{tag}_jf10.11.zip",
|
||||
"checksum": md5_9,
|
||||
"timestamp": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
}
|
||||
]
|
||||
"versions": all_versions
|
||||
}
|
||||
]
|
||||
|
||||
b64_manifest = base64.b64encode(json.dumps(manifest_data, indent=4).encode("utf-8")).decode("utf-8")
|
||||
|
||||
# Update on master branch
|
||||
get_mf_url = f"{api_url}/repos/{repo}/contents/manifest.json?ref=master"
|
||||
req_mf = urllib.request.Request(get_mf_url, headers=headers)
|
||||
file_sha = None
|
||||
try:
|
||||
res_mf = urllib.request.urlopen(req_mf)
|
||||
file_sha = json.loads(res_mf.read().decode("utf-8")).get("sha")
|
||||
except Exception:
|
||||
pass
|
||||
# Update on master and manifest branches
|
||||
for target_branch in ["master", "manifest"]:
|
||||
get_mf_url = f"{api_url}/repos/{repo}/contents/manifest.json?ref={target_branch}"
|
||||
req_mf = urllib.request.Request(get_mf_url, headers=headers)
|
||||
file_sha = None
|
||||
try:
|
||||
res_mf = urllib.request.urlopen(req_mf)
|
||||
file_sha = json.loads(res_mf.read().decode("utf-8")).get("sha")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
put_payload = {
|
||||
"content": b64_manifest,
|
||||
"message": f"Update manifest.json for {tag}",
|
||||
"branch": "master"
|
||||
}
|
||||
if file_sha:
|
||||
put_payload["sha"] = file_sha
|
||||
put_payload = {
|
||||
"content": b64_manifest,
|
||||
"message": f"Update manifest.json for {tag}",
|
||||
"branch": target_branch
|
||||
}
|
||||
if file_sha:
|
||||
put_payload["sha"] = file_sha
|
||||
|
||||
put_url = f"{api_url}/repos/{repo}/contents/manifest.json"
|
||||
put_req = urllib.request.Request(put_url, data=json.dumps(put_payload).encode("utf-8"), headers=headers, method="PUT")
|
||||
urllib.request.urlopen(put_req)
|
||||
print("manifest.json updated successfully via API.")
|
||||
put_url = f"{api_url}/repos/{repo}/contents/manifest.json"
|
||||
put_req = urllib.request.Request(put_url, data=json.dumps(put_payload).encode("utf-8"), headers=headers, method="PUT")
|
||||
try:
|
||||
urllib.request.urlopen(put_req)
|
||||
print(f"manifest.json updated successfully on branch {target_branch}.")
|
||||
except Exception as e:
|
||||
print(f"Notice updating branch {target_branch}: {e}")
|
||||
'
|
||||
|
||||
@@ -70,10 +70,118 @@ namespace JellyfinPlugin.Api
|
||||
console.log('Black Bar Remover: Script loaded');
|
||||
let currentItemId = null;
|
||||
let interceptedItemId = null;
|
||||
let currentCropData = null;
|
||||
window._currentPlayMethod = null;
|
||||
window._isVideoDirect = null;
|
||||
|
||||
function isTranscoding() {
|
||||
if (window._isVideoDirect === false) {
|
||||
return true;
|
||||
}
|
||||
if (window._isVideoDirect === true) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function ensureBoundingBoxHook(video) {
|
||||
if (!video._origGetBoundingClientRect) {
|
||||
video._origGetBoundingClientRect = video.getBoundingClientRect;
|
||||
video.getBoundingClientRect = function() {
|
||||
if (video._isCroppedByPlugin && video.parentElement) {
|
||||
const rect = video.parentElement.getBoundingClientRect();
|
||||
return {
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
x: rect.x !== undefined ? rect.x : rect.left,
|
||||
y: rect.y !== undefined ? rect.y : rect.top,
|
||||
toJSON: function() { return this; }
|
||||
};
|
||||
}
|
||||
return video._origGetBoundingClientRect.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let lastBottomPercent = 0;
|
||||
|
||||
function updateSubtitleStyles(bottomPercent) {
|
||||
lastBottomPercent = bottomPercent;
|
||||
let styleEl = document.getElementById('black-bar-remover-sub-style');
|
||||
if (!styleEl) {
|
||||
styleEl = document.createElement('style');
|
||||
styleEl.id = 'black-bar-remover-sub-style';
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
|
||||
if (bottomPercent > 0) {
|
||||
const offsetVh = (bottomPercent * 0.4 + 2.5).toFixed(2);
|
||||
styleEl.textContent = `
|
||||
.videoSubtitles {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
justify-content: flex-end !important;
|
||||
transform: translateY(-${offsetVh}vh) !important;
|
||||
}
|
||||
.track-text-output,
|
||||
.custom-subtitle-overlay,
|
||||
.subtitleSyncContainer,
|
||||
.videoSubtitlesContainer,
|
||||
.libassjs-canvas-parent,
|
||||
canvas.libassjs-canvas,
|
||||
.subtitles-octopus-canvas,
|
||||
.htmlvideoplayer::-webkit-media-text-track-container,
|
||||
.htmlvideoplayer::-webkit-media-text-track-display {
|
||||
transform: translateY(-${offsetVh}vh) !important;
|
||||
}
|
||||
`;
|
||||
console.log('Black Bar Remover: Subtitle CSS style updated translateY(-' + offsetVh + 'vh)');
|
||||
} else {
|
||||
styleEl.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
function adjustVttCues(bottomPercent) {
|
||||
const video = document.querySelector('video');
|
||||
if (!video || !video.textTracks) return;
|
||||
const targetLine = Math.max(10, 92 - Math.round(bottomPercent * 0.4 + 2.5));
|
||||
for (let i = 0; i < video.textTracks.length; i++) {
|
||||
const track = video.textTracks[i];
|
||||
if (track && track.cues) {
|
||||
for (let j = 0; j < track.cues.length; j++) {
|
||||
const cue = track.cues[j];
|
||||
if (cue && !cue._pluginAdjusted) {
|
||||
cue.snapToLines = false;
|
||||
cue.line = targetLine;
|
||||
cue._pluginAdjusted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyCrop(cropData) {
|
||||
const video = document.querySelector('video');
|
||||
if (!video) return;
|
||||
|
||||
if (isTranscoding()) {
|
||||
console.log('Black Bar Remover: Video is transcoded by server (IsVideoDirect: false), skipping CSS crop.');
|
||||
video.style.position = '';
|
||||
video.style.objectFit = '';
|
||||
video.style.width = '';
|
||||
video.style.height = '';
|
||||
video.style.top = '';
|
||||
video.style.left = '';
|
||||
video.style.clipPath = '';
|
||||
video.style.transform = '';
|
||||
updateSubtitleStyles(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = cropData.split(':');
|
||||
if (parts.length === 4) {
|
||||
const w = parseInt(parts[0]);
|
||||
@@ -81,21 +189,28 @@ namespace JellyfinPlugin.Api
|
||||
const x = parseInt(parts[2]);
|
||||
const y = parseInt(parts[3]);
|
||||
|
||||
const topPercent = (y / 1080) * 100;
|
||||
const bottomPercent = ((1080 - (y + h)) / 1080) * 100;
|
||||
const leftPercent = (x / 1920) * 100;
|
||||
const rightPercent = ((1920 - (x + w)) / 1920) * 100;
|
||||
|
||||
video.style.clipPath = `inset(${topPercent}% ${rightPercent}% ${bottomPercent}% ${leftPercent}%)`;
|
||||
|
||||
const scaleY = 1 / (1 - (topPercent + bottomPercent)/100);
|
||||
const scaleX = 1 / (1 - (leftPercent + rightPercent)/100);
|
||||
const scale = Math.max(scaleY, scaleX);
|
||||
|
||||
if (scale > 1.01) {
|
||||
video.style.transform = `scale(${scale})`;
|
||||
if (h > 0 && h < 1080) {
|
||||
const heightPercent = (1080 / h) * 100;
|
||||
const topPercent = -(y / h) * 100;
|
||||
const bottomPercent = ((1080 - (y + h)) / 1080) * 100;
|
||||
|
||||
video.style.position = 'absolute';
|
||||
video.style.objectFit = 'fill';
|
||||
video.style.width = '100%';
|
||||
video.style.height = heightPercent.toFixed(2) + '%';
|
||||
video.style.top = topPercent.toFixed(2) + '%';
|
||||
video.style.left = '0%';
|
||||
video.style.transform = 'none';
|
||||
video.style.clipPath = 'none';
|
||||
|
||||
if (video.parentElement) {
|
||||
video.parentElement.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
updateSubtitleStyles(bottomPercent);
|
||||
adjustVttCues(bottomPercent);
|
||||
console.log('Black Bar Remover: Applied non-scaling crop height:', heightPercent.toFixed(2) + '% top:', topPercent.toFixed(2) + '% subOffset:', (bottomPercent * 0.4 + 2.5).toFixed(2) + 'vh');
|
||||
}
|
||||
console.log('Black Bar Remover: Applied crop', cropData, 'scale', scale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +221,7 @@ namespace JellyfinPlugin.Api
|
||||
.then(crop => {
|
||||
console.log('Black Bar Remover: Received crop data:', crop);
|
||||
if (crop) {
|
||||
currentCropData = crop;
|
||||
setTimeout(() => applyCrop(crop), 500);
|
||||
setTimeout(() => applyCrop(crop), 2000);
|
||||
}
|
||||
@@ -117,17 +233,39 @@ namespace JellyfinPlugin.Api
|
||||
window._originalFetch = window.fetch;
|
||||
window.fetch = async function(...args) {
|
||||
const url = typeof args[0] === 'string' ? args[0] : (args[0] && args[0].url);
|
||||
if (url) {
|
||||
if (url && typeof url === 'string') {
|
||||
const match = url.match(/\/Items\/([a-f0-9]{32}|[a-f0-9\-]{36})/i);
|
||||
if (match) {
|
||||
const id = match[1];
|
||||
if (id && id !== interceptedItemId) {
|
||||
interceptedItemId = id;
|
||||
window._currentPlayMethod = null;
|
||||
window._isVideoDirect = null;
|
||||
console.log('Black Bar Remover: Intercepted itemId from fetch:', id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return window._originalFetch.apply(this, args);
|
||||
|
||||
const res = await window._originalFetch.apply(this, args);
|
||||
|
||||
if (url && typeof url === 'string' && url.includes('/PlaybackInfo')) {
|
||||
try {
|
||||
const clone = res.clone();
|
||||
clone.json().then(data => {
|
||||
if (data && data.MediaSources && data.MediaSources[0]) {
|
||||
const ms = data.MediaSources[0];
|
||||
window._currentPlayMethod = ms.PlayMethod;
|
||||
window._isVideoDirect = ms.IsVideoDirect !== false;
|
||||
console.log('Black Bar Remover: PlaybackInfo detected -> PlayMethod:', window._currentPlayMethod, 'IsVideoDirect:', window._isVideoDirect);
|
||||
if (currentCropData) {
|
||||
applyCrop(currentCropData);
|
||||
}
|
||||
}
|
||||
}).catch(e => {});
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -143,9 +281,27 @@ namespace JellyfinPlugin.Api
|
||||
const id = match[1];
|
||||
if (id && id !== interceptedItemId) {
|
||||
interceptedItemId = id;
|
||||
window._currentPlayMethod = null;
|
||||
window._isVideoDirect = null;
|
||||
console.log('Black Bar Remover: Intercepted itemId from XHR:', id);
|
||||
}
|
||||
}
|
||||
if (url.includes('/PlaybackInfo')) {
|
||||
xhr.addEventListener('load', function() {
|
||||
try {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
if (data && data.MediaSources && data.MediaSources[0]) {
|
||||
const ms = data.MediaSources[0];
|
||||
window._currentPlayMethod = ms.PlayMethod;
|
||||
window._isVideoDirect = ms.IsVideoDirect !== false;
|
||||
console.log('Black Bar Remover: XHR PlaybackInfo detected -> PlayMethod:', window._currentPlayMethod, 'IsVideoDirect:', window._isVideoDirect);
|
||||
if (currentCropData) {
|
||||
applyCrop(currentCropData);
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
return originalOpen.apply(this, [method, url, ...rest]);
|
||||
};
|
||||
@@ -226,8 +382,12 @@ namespace JellyfinPlugin.Api
|
||||
console.log('Black Bar Remover: Video element found for intercepted item:', currentItemId);
|
||||
fetchCrop(currentItemId);
|
||||
checkServerCrop(currentItemId);
|
||||
} else if (video && lastBottomPercent > 0) {
|
||||
updateSubtitleStyles(lastBottomPercent);
|
||||
adjustVttCues(lastBottomPercent);
|
||||
} else if (!video) {
|
||||
currentItemId = null;
|
||||
lastBottomPercent = 0;
|
||||
window._isServerCropping = false;
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace JellyfinPlugin.Configuration
|
||||
// Czy wtyczka jest aktywna
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
// Tryb odtwarzania: "Server" (FFmpeg wrapper), "Client" (CSS injection w Web UI), "Both" (Obydwa naraz)
|
||||
public string PlaybackMode { get; set; } = "Both";
|
||||
// Tryb odtwarzania: "Server" (FFmpeg wrapper), "Client" (CSS injection w Web UI), "Auto" (Automatyczny: CSS dla Web, FFmpeg dla aplikacji natywnych)
|
||||
public string PlaybackMode { get; set; } = "Auto";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
<div class="selectContainer">
|
||||
<label class="selectLabel" for="selectPlaybackMode">Black Bar Removal Method:</label>
|
||||
<select is="emby-select" id="selectPlaybackMode" class="emby-select-withcolor">
|
||||
<option value="Auto">Auto (CSS clip in Web, Transcode on Native Apps / Transcoded streams)</option>
|
||||
<option value="Server">Server Only (FFmpeg Transcoding)</option>
|
||||
<option value="Client">Client Only (CSS Clip in Jellyfin Web)</option>
|
||||
<option value="Both">Both (Transcoding or CSS, depending on client)</option>
|
||||
</select>
|
||||
<div class="fieldDescription">Choose how the plugin should remove black bars. Server option forces transcoding (CPU/GPU load), Client option is lightweight but only works in web browsers.</div>
|
||||
<div class="fieldDescription">Choose how the plugin should remove black bars. Auto mode uses lightweight CSS clipping for Web browser DirectPlay, and falls back to FFmpeg transcoding for native apps or unsupported video formats.</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -53,7 +53,9 @@
|
||||
ApiClient.getPluginConfiguration(BlackBarRemoverConfig.pluginUniqueId).then(function (config) {
|
||||
$('#chkEnablePlugin', page).prop('checked', config.IsEnabled !== false);
|
||||
$('#txtCropLimit', page).val(config.CropDetectLimit || 0.094);
|
||||
$('#selectPlaybackMode', page).val(config.PlaybackMode || "Both");
|
||||
var mode = config.PlaybackMode || "Auto";
|
||||
if (mode === "Both") mode = "Auto";
|
||||
$('#selectPlaybackMode', page).val(mode);
|
||||
Dashboard.hideLoadingMsg();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,6 +60,14 @@ namespace JellyfinPlugin
|
||||
return;
|
||||
}
|
||||
|
||||
var isAutoMode = config.PlaybackMode == "Auto" || config.PlaybackMode == "Both";
|
||||
if (isAutoMode && IsJellyfinWebClient(context))
|
||||
{
|
||||
// W trybie Auto dla przeglądarki Web pozwalamy na Direct Play (przycięcie wykona skrypt CSS w przeglądarce).
|
||||
await nextMw().ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- HLS STREAM QUERY REWRITE ---
|
||||
// If the client requests the m3u8 playlist, we simply tell Jellyfin NOT to allow video stream copy.
|
||||
// This elegantly forces transcoding for the BEST supported codec without breaking FFmpeg tags/filters!
|
||||
@@ -147,5 +155,43 @@ namespace JellyfinPlugin
|
||||
|
||||
await nextMw().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static bool IsJellyfinWebClient(HttpContext context)
|
||||
{
|
||||
if (context.Request.Headers.TryGetValue("X-Emby-Authorization", out var authHeader) ||
|
||||
context.Request.Headers.TryGetValue("Authorization", out authHeader))
|
||||
{
|
||||
var authStr = authHeader.ToString();
|
||||
if (authStr.Contains("Client=\"Jellyfin Web\"", StringComparison.OrdinalIgnoreCase) ||
|
||||
authStr.Contains("Client=\"Jellyfin%20Web\"", StringComparison.OrdinalIgnoreCase) ||
|
||||
authStr.Contains("Client=Jellyfin Web", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.Request.Headers.TryGetValue("User-Agent", out var uaHeader))
|
||||
{
|
||||
var ua = uaHeader.ToString();
|
||||
if (ua.Contains("JellyfinMediaPlayer", StringComparison.OrdinalIgnoreCase) ||
|
||||
ua.Contains("Jellyfin-Android", StringComparison.OrdinalIgnoreCase) ||
|
||||
ua.Contains("ExoPlayer", StringComparison.OrdinalIgnoreCase) ||
|
||||
ua.Contains("okhttp", StringComparison.OrdinalIgnoreCase) ||
|
||||
ua.Contains("Kodi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ua.Contains("Mozilla", StringComparison.OrdinalIgnoreCase) ||
|
||||
ua.Contains("Chrome", StringComparison.OrdinalIgnoreCase) ||
|
||||
ua.Contains("Safari", StringComparison.OrdinalIgnoreCase) ||
|
||||
ua.Contains("Firefox", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ if [ -n ""$INPUT_FILE"" ]; then
|
||||
if [ $next_idx -le $# ]; then
|
||||
next_arg=""${{!next_idx}}""
|
||||
fi
|
||||
NEW_ARGS+=(""$next_arg,crop=$CROP_DATA"")
|
||||
NEW_ARGS+=(""crop=$CROP_DATA,$next_arg"")
|
||||
let i++
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -13,9 +13,15 @@ A Jellyfin plugin that automatically detects and removes black bars (letterboxin
|
||||
|
||||
## Requirements
|
||||
|
||||
- Jellyfin Server **12.0** or newer (.NET 10).
|
||||
- Jellyfin Server **10.11** or **12.0+** (.NET 9 / .NET 10).
|
||||
- FFmpeg installed on the server.
|
||||
|
||||
## Tested Versions
|
||||
|
||||
This plugin has been verified and tested on:
|
||||
- **Jellyfin 10.11.x** (.NET 9.0)
|
||||
- **Jellyfin 12.0 / 13.0 Unstable** (.NET 10.0)
|
||||
|
||||
## Installation
|
||||
|
||||
### Method 1: Jellyfin Repository (Recommended & Easiest)
|
||||
@@ -23,7 +29,7 @@ A Jellyfin plugin that automatically detects and removes black bars (letterboxin
|
||||
1. Open your Jellyfin Dashboard and navigate to **Plugins** -> **Repositories**.
|
||||
2. Click the `+` icon to add a new repository.
|
||||
3. Enter `Black Bar Remover` as the name.
|
||||
4. Enter `https://gitea.labus.uk/karol/blackBarRemover/raw/branch/manifest/manifest.json` as the Repository URL and save.
|
||||
4. Enter `https://gitea.labus.uk/karol/blackBarRemover/raw/branch/master/manifest.json` as the Repository URL and save.
|
||||
5. Go back to the **Catalog** tab, find "Black Bar Remover" under the Playback category, and click **Install**.
|
||||
6. Restart the Jellyfin server.
|
||||
|
||||
@@ -37,17 +43,26 @@ A Jellyfin plugin that automatically detects and removes black bars (letterboxin
|
||||
### Method 3: Building from Source
|
||||
|
||||
1. Clone this repository.
|
||||
2. Build the plugin using the .NET 9 SDK:
|
||||
2. Ensure you have the **.NET 9.0 SDK** and/or **.NET 10.0 SDK** installed (the project multi-targets both `net9.0` and `net10.0`).
|
||||
3. Build the plugin for all targets:
|
||||
```bash
|
||||
dotnet build JellyfinPlugin/JellyfinPlugin.csproj -c Release
|
||||
```
|
||||
3. Copy the compiled DLLs from `JellyfinPlugin/bin/Release/net9.0/` into a new folder named `BlackBarRemover` inside your Jellyfin plugins directory.
|
||||
4. Restart the Jellyfin server.
|
||||
Or build for a specific target framework:
|
||||
```bash
|
||||
# For Jellyfin 10.11 (.NET 9.0)
|
||||
dotnet build JellyfinPlugin/JellyfinPlugin.csproj -c Release -f net9.0
|
||||
|
||||
# For Jellyfin 12.0 / 13.0 Unstable (.NET 10.0)
|
||||
dotnet build JellyfinPlugin/JellyfinPlugin.csproj -c Release -f net10.0
|
||||
```
|
||||
4. Copy the compiled DLLs from `JellyfinPlugin/bin/Release/net9.0/` (for Jellyfin 10.11) or `JellyfinPlugin/bin/Release/net10.0/` (for Jellyfin 12.0+) into a new folder named `BlackBarRemover` inside your Jellyfin plugins directory (e.g., `/var/lib/jellyfin/plugins/BlackBarRemover/`).
|
||||
5. Restart the Jellyfin server.
|
||||
|
||||
### Configuration & Initial Scan
|
||||
|
||||
1. Go to **Dashboard** -> **Plugins** -> **Black Bar Remover**.
|
||||
2. Set your **Crop Detect Limit** (Default is 0.15). If your web-dl rips have noisy black bars that are not being cropped, increase this value to 0.20 or 0.25.
|
||||
2. Set your **Crop Detect Limit** (Default is `0.094`). If your web-dl rips have noisy black bars that are not being cropped, increase this value to `0.15` or `0.20`.
|
||||
3. Select your preferred **Playback Mode**:
|
||||
- `Client`: Uses CSS injection in the web player.
|
||||
- `Server`: Requires setting up the FFmpeg wrapper.
|
||||
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
[
|
||||
{
|
||||
"guid": "12345678-1234-1234-1234-1234567890ab",
|
||||
"name": "Black Bar Remover",
|
||||
"description": "Automatically detects and removes black bars from video files.",
|
||||
"overview": "Automatically detects and removes black bars from video files via CSS or FFmpeg.",
|
||||
"owner": "karol",
|
||||
"category": "Playback",
|
||||
"imageUrl": "",
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.1.19",
|
||||
"changelog": "Release v0.1.19-beta (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.19-beta/BlackBarRemover_v0.1.19-beta_jf12.0.zip",
|
||||
"checksum": "baed36aef30d4cece36d1b36cd67775f",
|
||||
"timestamp": "2026-09-17T19:57:09Z"
|
||||
},
|
||||
{
|
||||
"version": "0.1.19",
|
||||
"changelog": "Release v0.1.19-beta (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.19-beta/BlackBarRemover_v0.1.19-beta_jf10.11.zip",
|
||||
"checksum": "9bb29cf4ba5e351059a92a9a7ac5b85f",
|
||||
"timestamp": "2026-09-17T19:57:09Z"
|
||||
},
|
||||
{
|
||||
"version": "0.1.18",
|
||||
"changelog": "Release v0.1.18-beta (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.18-beta/BlackBarRemover_v0.1.18-beta_jf12.0.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T21:51:42+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.18",
|
||||
"changelog": "Release v0.1.18-beta (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.18-beta/BlackBarRemover_v0.1.18-beta_jf10.11.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T21:51:42+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.17",
|
||||
"changelog": "Release v0.1.17-beta (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.17-beta/BlackBarRemover_v0.1.17-beta_jf12.0.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T21:32:32+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.17",
|
||||
"changelog": "Release v0.1.17-beta (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.17-beta/BlackBarRemover_v0.1.17-beta_jf10.11.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T21:32:32+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.11",
|
||||
"changelog": "Release v0.1.11-beta (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.11-beta/BlackBarRemover_v0.1.11-beta_jf12.0.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T21:05:29+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.11",
|
||||
"changelog": "Release v0.1.11-beta (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.11-beta/BlackBarRemover_v0.1.11-beta_jf10.11.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T21:05:29+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.10",
|
||||
"changelog": "Release v0.1.10-beta (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.10-beta/BlackBarRemover_v0.1.10-beta_jf12.0.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T19:54:01+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.10",
|
||||
"changelog": "Release v0.1.10-beta (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.10-beta/BlackBarRemover_v0.1.10-beta_jf10.11.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T19:54:01+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.9",
|
||||
"changelog": "Release v0.1.9-beta (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.9-beta/BlackBarRemover_v0.1.9-beta_jf12.0.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T19:39:22+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.9",
|
||||
"changelog": "Release v0.1.9-beta (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.9-beta/BlackBarRemover_v0.1.9-beta_jf10.11.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T19:39:22+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.8",
|
||||
"changelog": "Release v0.1.8-beta (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.8-beta/BlackBarRemover_v0.1.8-beta_jf12.0.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T18:29:04+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.8",
|
||||
"changelog": "Release v0.1.8-beta (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.8-beta/BlackBarRemover_v0.1.8-beta_jf10.11.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T18:29:04+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.7",
|
||||
"changelog": "Release v0.1.7-beta (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.7-beta/BlackBarRemover_v0.1.7-beta_jf12.0.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T18:23:27+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.7",
|
||||
"changelog": "Release v0.1.7-beta (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.7-beta/BlackBarRemover_v0.1.7-beta_jf10.11.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T18:23:27+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.6",
|
||||
"changelog": "Release v0.1.6-beta (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.6-beta/BlackBarRemover_v0.1.6-beta_jf12.0.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T18:11:37+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.6",
|
||||
"changelog": "Release v0.1.6-beta (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.6-beta/BlackBarRemover_v0.1.6-beta_jf10.11.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T18:11:37+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.2",
|
||||
"changelog": "Release v0.1.2",
|
||||
"targetAbi": "10.9.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.2/BlackBarRemover_v0.1.2.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T14:52:19+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1.1",
|
||||
"changelog": "Release v0.1.1",
|
||||
"targetAbi": "10.9.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1.1/BlackBarRemover_v0.1.1.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T14:45:08+02:00"
|
||||
},
|
||||
{
|
||||
"version": "0.1",
|
||||
"changelog": "Release v0.1-beta",
|
||||
"targetAbi": "10.9.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/v0.1-beta/BlackBarRemover_v0.1-beta.zip",
|
||||
"checksum": "",
|
||||
"timestamp": "2026-09-17T14:37:16+02:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user