Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d0af2aed0 | |||
| 8b8b9a6d43 | |||
| 0cfaa50805 | |||
| fea812d3a3 | |||
| fe6e20a68a | |||
| b7693b606e | |||
| 13f9fd642c | |||
| 322cfd9acd | |||
| 648930aa21 | |||
| cb16bf0074 |
+127
-55
@@ -46,48 +46,99 @@ jobs:
|
||||
echo "md5_9=$MD5_SUM_9" >> $GITHUB_OUTPUT
|
||||
echo "md5_10=$MD5_SUM_10" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Gitea Release
|
||||
- name: Publish Release and Update Manifest
|
||||
env:
|
||||
API_URL: ${{ github.api_url }}
|
||||
REPO: ${{ github.repository }}
|
||||
TOKEN: ${{ github.token }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
MD5_9: ${{ steps.checksum.outputs.md5_9 }}
|
||||
MD5_10: ${{ steps.checksum.outputs.md5_10 }}
|
||||
run: |
|
||||
API_URL="${{ github.api_url }}"
|
||||
REPO="${{ github.repository }}"
|
||||
TOKEN="${{ github.token }}"
|
||||
TAG="${{ github.ref_name }}"
|
||||
|
||||
IS_PRERELEASE="false"
|
||||
if [[ "$TAG" == *"beta"* ]] || [[ "$TAG" == *"alpha"* ]] || [[ "$TAG" == v0.* ]]; then
|
||||
IS_PRERELEASE="true"
|
||||
fi
|
||||
|
||||
# Create Release
|
||||
RELEASE_RES=$(curl -s -X POST "$API_URL/repos/$REPO/releases" \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\", \"name\":\"Release $TAG\", \"body\":\"Automated release\", \"draft\":false, \"prerelease\":$IS_PRERELEASE}")
|
||||
|
||||
REL_ID=$(echo "$RELEASE_RES" | grep -o '"id":[0-9]*' | head -1 | cut -d':' -f2)
|
||||
|
||||
# Upload Assets
|
||||
curl -s -X POST "$API_URL/repos/$REPO/releases/$REL_ID/assets" \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-F "attachment=@BlackBarRemover_${TAG}_jf10.11.zip"
|
||||
|
||||
curl -s -X POST "$API_URL/repos/$REPO/releases/$REL_ID/assets" \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-F "attachment=@BlackBarRemover_${TAG}_jf12.0.zip"
|
||||
python3 -c '
|
||||
import os, json, urllib.request, base64, datetime
|
||||
|
||||
- name: Update manifest.json
|
||||
run: |
|
||||
git config user.name "Gitea Actions"
|
||||
git config user.email "actions@gitea.local"
|
||||
|
||||
git checkout -B manifest
|
||||
|
||||
VERSION="${{ github.ref_name }}"
|
||||
VERSION="${VERSION#v}"
|
||||
CLEAN_VERSION="${VERSION%%-*}"
|
||||
|
||||
cat > manifest.json <<EOF
|
||||
[
|
||||
api_url = os.environ["API_URL"]
|
||||
repo = os.environ["REPO"]
|
||||
token = os.environ["TOKEN"]
|
||||
tag = os.environ["TAG"]
|
||||
md5_9 = os.environ["MD5_9"]
|
||||
md5_10 = os.environ["MD5_10"]
|
||||
|
||||
headers = {
|
||||
"Authorization": f"token {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
# 1. Get or Create Release
|
||||
rel_url = f"{api_url}/repos/{repo}/releases/tags/{tag}"
|
||||
req = urllib.request.Request(rel_url, headers=headers)
|
||||
rel_data = None
|
||||
try:
|
||||
res = urllib.request.urlopen(req)
|
||||
rel_data = json.loads(res.read().decode("utf-8"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not rel_data or "id" not in rel_data:
|
||||
is_pre = "beta" in tag or "alpha" in tag or tag.startswith("v0.")
|
||||
create_url = f"{api_url}/repos/{repo}/releases"
|
||||
body = json.dumps({
|
||||
"tag_name": tag,
|
||||
"name": f"Release {tag}",
|
||||
"body": "Automated release",
|
||||
"draft": False,
|
||||
"prerelease": is_pre
|
||||
}).encode("utf-8")
|
||||
create_req = urllib.request.Request(create_url, data=body, headers=headers)
|
||||
res = urllib.request.urlopen(create_req)
|
||||
rel_data = json.loads(res.read().decode("utf-8"))
|
||||
|
||||
rel_id = rel_data["id"]
|
||||
|
||||
# 2. Upload Assets
|
||||
for filename in [f"BlackBarRemover_{tag}_jf10.11.zip", f"BlackBarRemover_{tag}_jf12.0.zip"]:
|
||||
if not os.path.exists(filename):
|
||||
print(f"File {filename} not found, skipping.")
|
||||
continue
|
||||
|
||||
# Delete existing asset if any
|
||||
for existing_asset in rel_data.get("assets", []) or []:
|
||||
if existing_asset.get("name") == filename:
|
||||
del_url = f"{api_url}/repos/{repo}/releases/{rel_id}/assets/{existing_asset['id']}"
|
||||
del_req = urllib.request.Request(del_url, headers=headers, method="DELETE")
|
||||
try:
|
||||
urllib.request.urlopen(del_req)
|
||||
print(f"Deleted old asset {existing_asset['id']}")
|
||||
except Exception as e:
|
||||
print(f"Delete asset notice: {e}")
|
||||
|
||||
# Upload asset
|
||||
with open(filename, "rb") as f:
|
||||
file_bytes = f.read()
|
||||
|
||||
boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
|
||||
body_data = (
|
||||
f"--{boundary}\r\n"
|
||||
f"Content-Disposition: form-data; name=\"attachment\"; filename=\"{filename}\"\r\n"
|
||||
f"Content-Type: application/zip\r\n\r\n"
|
||||
).encode("utf-8") + file_bytes + f"\r\n--{boundary}--\r\n".encode("utf-8")
|
||||
|
||||
upload_headers = {
|
||||
"Authorization": f"token {token}",
|
||||
"Content-Type": f"multipart/form-data; boundary={boundary}"
|
||||
}
|
||||
upload_url = f"{api_url}/repos/{repo}/releases/{rel_id}/assets"
|
||||
upload_req = urllib.request.Request(upload_url, data=body_data, headers=upload_headers)
|
||||
try:
|
||||
urllib.request.urlopen(upload_req)
|
||||
print(f"Uploaded {filename} successfully.")
|
||||
except Exception as e:
|
||||
print(f"Asset upload error: {e}")
|
||||
|
||||
# 3. Update manifest.json on master branch
|
||||
clean_ver = tag.lstrip("v").split("-")[0]
|
||||
manifest_data = [
|
||||
{
|
||||
"guid": "12345678-1234-1234-1234-1234567890ab",
|
||||
"name": "Black Bar Remover",
|
||||
@@ -98,26 +149,47 @@ jobs:
|
||||
"imageUrl": "",
|
||||
"versions": [
|
||||
{
|
||||
"version": "$CLEAN_VERSION",
|
||||
"changelog": "Automated Release ${{ github.ref_name }} (Jellyfin 12.0)",
|
||||
"version": clean_ver,
|
||||
"changelog": f"Automated Release {tag} (Jellyfin 12.0)",
|
||||
"targetAbi": "12.0.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/${{ github.ref_name }}/BlackBarRemover_${{ github.ref_name }}_jf12.0.zip",
|
||||
"checksum": "${{ steps.checksum.outputs.md5_10 }}",
|
||||
"timestamp": "$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
||||
"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_VERSION",
|
||||
"changelog": "Automated Release ${{ github.ref_name }} (Jellyfin 10.11)",
|
||||
"version": clean_ver,
|
||||
"changelog": f"Automated Release {tag} (Jellyfin 10.11)",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://gitea.labus.uk/karol/blackBarRemover/releases/download/${{ github.ref_name }}/BlackBarRemover_${{ github.ref_name }}_jf10.11.zip",
|
||||
"checksum": "${{ steps.checksum.outputs.md5_9 }}",
|
||||
"timestamp": "$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
||||
"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")
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
EOF
|
||||
|
||||
git add manifest.json
|
||||
git commit -m "Update manifest for ${{ github.ref_name }}"
|
||||
git push -f origin manifest
|
||||
|
||||
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
|
||||
|
||||
put_payload = {
|
||||
"content": b64_manifest,
|
||||
"message": f"Update manifest.json for {tag}",
|
||||
"branch": "master"
|
||||
}
|
||||
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.")
|
||||
'
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace JellyfinPlugin
|
||||
private readonly ILogger<LibraryMonitor> _logger;
|
||||
private readonly SemaphoreSlim _ffmpegSemaphore = new SemaphoreSlim(2, 2);
|
||||
|
||||
private readonly ConcurrentDictionary<Guid, byte> _processedItems = new ConcurrentDictionary<Guid, byte>();
|
||||
|
||||
public LibraryMonitor(ILibraryManager libraryManager, ILogger<LibraryMonitor> logger)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
@@ -48,6 +50,16 @@ namespace JellyfinPlugin
|
||||
{
|
||||
if (!Plugin.Instance!.Configuration.IsEnabled) return;
|
||||
|
||||
if (_processedItems.ContainsKey(video.Id)) return;
|
||||
|
||||
if (video.ProviderIds != null &&
|
||||
video.ProviderIds.TryGetValue("BlackBarCrop", out string? existingCrop) &&
|
||||
!string.IsNullOrEmpty(existingCrop))
|
||||
{
|
||||
_processedItems.TryAdd(video.Id, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire and forget, don't block library scan
|
||||
_ = Task.Run(() => ProcessVideoAsync(video));
|
||||
}
|
||||
@@ -57,6 +69,8 @@ namespace JellyfinPlugin
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_processedItems.TryAdd(video.Id, 0)) return;
|
||||
|
||||
if (video.ProviderIds != null &&
|
||||
video.ProviderIds.TryGetValue("BlackBarCrop", out string? existingCrop) &&
|
||||
!string.IsNullOrEmpty(existingCrop))
|
||||
|
||||
@@ -63,8 +63,8 @@ namespace JellyfinPlugin
|
||||
// --- 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!
|
||||
if (path.EndsWith("master.m3u8", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("main.m3u8", StringComparison.OrdinalIgnoreCase))
|
||||
if (path.Contains("/Videos/", StringComparison.OrdinalIgnoreCase) &&
|
||||
(path.Contains(".m3u8", StringComparison.OrdinalIgnoreCase) || path.Contains("/stream", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
var matchId = Regex.Match(path, @"/Videos/([^/]+)/", RegexOptions.IgnoreCase);
|
||||
if (matchId.Success)
|
||||
@@ -112,113 +112,40 @@ namespace JellyfinPlugin
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"[Black Bar Remover DEBUG] Intercepting PlaybackInfo for {itemId}. Found crop in DB: {cropData}. Forcing transcode.");
|
||||
_logger.LogInformation($"[Black Bar Remover DEBUG] Intercepting PlaybackInfo request for {itemId}. Found crop in DB: {cropData}. Disabling DirectPlayProfiles in incoming request.");
|
||||
|
||||
// Kestrel middleware do nadpisania odpowiedzi
|
||||
context.Request.Headers.Remove("Accept-Encoding");
|
||||
var originalBody = context.Response.Body;
|
||||
using var buffer = new MemoryStream();
|
||||
context.Response.Body = buffer;
|
||||
// Modify incoming request body to clear DirectPlayProfiles
|
||||
context.Request.EnableBuffering();
|
||||
string reqJson = string.Empty;
|
||||
using (var reader = new StreamReader(context.Request.Body, Encoding.UTF8, leaveOpen: true))
|
||||
{
|
||||
reqJson = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
JsonNode rootNode;
|
||||
try
|
||||
{
|
||||
await nextMw().ConfigureAwait(false);
|
||||
rootNode = string.IsNullOrWhiteSpace(reqJson) ? new JsonObject() : (JsonNode.Parse(reqJson) ?? new JsonObject());
|
||||
}
|
||||
catch
|
||||
{
|
||||
context.Response.Body = originalBody;
|
||||
throw;
|
||||
rootNode = new JsonObject();
|
||||
}
|
||||
|
||||
context.Response.Body = originalBody;
|
||||
buffer.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var isJson = context.Response.ContentType?.Contains("application/json", StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
|
||||
if (!isJson || context.Response.StatusCode != 200)
|
||||
if (rootNode["DeviceProfile"] is not JsonObject devProfile)
|
||||
{
|
||||
await buffer.CopyToAsync(originalBody).ConfigureAwait(false);
|
||||
return;
|
||||
devProfile = new JsonObject();
|
||||
rootNode["DeviceProfile"] = devProfile;
|
||||
}
|
||||
|
||||
string json;
|
||||
using (var reader = new StreamReader(buffer, Encoding.UTF8, true, 1024, leaveOpen: true))
|
||||
{
|
||||
json = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||
}
|
||||
devProfile["DirectPlayProfiles"] = new JsonArray();
|
||||
|
||||
try
|
||||
{
|
||||
var root = JsonNode.Parse(json);
|
||||
var mediaSources = root?["MediaSources"]?.AsArray();
|
||||
if (mediaSources != null)
|
||||
{
|
||||
bool modified = false;
|
||||
foreach (var source in mediaSources)
|
||||
{
|
||||
if (source == null) continue;
|
||||
|
||||
var obj = source.AsObject();
|
||||
|
||||
// Pobieramy flagi niezależnie od wielkości liter (Jellyfin 10.9+ może używać camelCase)
|
||||
var dpNode = obj.FirstOrDefault(x => x.Key.Equals("SupportsDirectPlay", StringComparison.OrdinalIgnoreCase)).Value;
|
||||
var dsNode = obj.FirstOrDefault(x => x.Key.Equals("SupportsDirectStream", StringComparison.OrdinalIgnoreCase)).Value;
|
||||
|
||||
var dp = dpNode?.GetValue<bool>() ?? false;
|
||||
var ds = dsNode?.GetValue<bool>() ?? false;
|
||||
var newReqBytes = Encoding.UTF8.GetBytes(rootNode.ToJsonString());
|
||||
var reqMs = new MemoryStream(newReqBytes);
|
||||
context.Request.Body = reqMs;
|
||||
context.Request.ContentLength = newReqBytes.Length;
|
||||
|
||||
if (dp || ds)
|
||||
{
|
||||
// Szukamy oryginalnych kluczy, żeby ich nie zdublować w innym casing-u
|
||||
var dpKey = obj.FirstOrDefault(x => x.Key.Equals("SupportsDirectPlay", StringComparison.OrdinalIgnoreCase)).Key ?? "SupportsDirectPlay";
|
||||
var dsKey = obj.FirstOrDefault(x => x.Key.Equals("SupportsDirectStream", StringComparison.OrdinalIgnoreCase)).Key ?? "SupportsDirectStream";
|
||||
var trKey = obj.FirstOrDefault(x => x.Key.Equals("TranscodingReasons", StringComparison.OrdinalIgnoreCase)).Key ?? "TranscodingReasons";
|
||||
|
||||
obj[dpKey] = false;
|
||||
obj[dsKey] = false;
|
||||
|
||||
var transcodingReasons = obj[trKey]?.GetValue<string>();
|
||||
string customReason = "VideoCodecNotSupported";
|
||||
|
||||
if (transcodingReasons == null)
|
||||
{
|
||||
obj[trKey] = customReason;
|
||||
}
|
||||
else if (!transcodingReasons.Contains(customReason))
|
||||
{
|
||||
obj[trKey] = transcodingReasons + "," + customReason;
|
||||
}
|
||||
|
||||
// Dodatkowo obcinamy wsparcie dla Direct Stream w pod-strumieniach (Jellyfin czasami wymusza z poziomu klienta)
|
||||
var reqKey = obj.FirstOrDefault(x => x.Key.Equals("RequiresOpening", StringComparison.OrdinalIgnoreCase)).Key ?? "RequiresOpening";
|
||||
obj[reqKey] = true;
|
||||
|
||||
var reqCloseKey = obj.FirstOrDefault(x => x.Key.Equals("RequiresClosing", StringComparison.OrdinalIgnoreCase)).Key ?? "RequiresClosing";
|
||||
obj[reqCloseKey] = true;
|
||||
|
||||
modified = true;
|
||||
_logger.LogInformation($"[Black Bar Remover DEBUG] Modified PlaybackInfo JSON: forced {dpKey}/{dsKey} to false for MediaSource ID {obj.FirstOrDefault(x => x.Key.Equals("Id", StringComparison.OrdinalIgnoreCase)).Value}");
|
||||
}
|
||||
}
|
||||
|
||||
if (modified)
|
||||
{
|
||||
json = root!.ToJsonString(new JsonSerializerOptions { WriteIndented = false });
|
||||
_logger.LogInformation($"[Black Bar Remover DEBUG] Successfully rewrote PlaybackInfo JSON to enforce transcoding.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning($"[Black Bar Remover DEBUG] Error modifying PlaybackInfo JSON: {ex.Message}");
|
||||
}
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
context.Response.ContentType = "application/json; charset=utf-8";
|
||||
context.Response.ContentLength = bytes.Length;
|
||||
context.Response.Headers.Remove("ETag");
|
||||
context.Response.Headers.Remove("Last-Modified");
|
||||
await originalBody.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||
await nextMw().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,18 +58,16 @@ namespace JellyfinPlugin.Tasks
|
||||
{
|
||||
string cropData = await CropHelper.DetectCropAsync(path, config.CropDetectLimit);
|
||||
|
||||
if (item.ProviderIds == null)
|
||||
{
|
||||
item.ProviderIds = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
// Zawsze dodajemy flagę, nawet jak puste, żeby nie skanować drugi raz tego samego pliku
|
||||
// (puste oznacza że nie ma czarnych pasów do ucięcia)
|
||||
item.ProviderIds["BlackBarCrop"] = cropData ?? string.Empty;
|
||||
await _libraryManager.UpdateItemAsync(item, item, ItemUpdateType.MetadataEdit, cancellationToken);
|
||||
|
||||
if (!string.IsNullOrEmpty(cropData))
|
||||
{
|
||||
if (item.ProviderIds == null)
|
||||
{
|
||||
item.ProviderIds = new Dictionary<string, string>();
|
||||
}
|
||||
item.ProviderIds["BlackBarCrop"] = cropData;
|
||||
await _libraryManager.UpdateItemAsync(item, item, ItemUpdateType.MetadataEdit, cancellationToken);
|
||||
_logger.LogInformation("Found black bars {Crop} in {Path}", cropData, path);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -44,37 +44,34 @@ namespace JellyfinPlugin
|
||||
return;
|
||||
}
|
||||
|
||||
// Pobieramy obecną konfigurację kodowania
|
||||
var encodingConfig = _configManager.GetConfiguration("encoding");
|
||||
if (encodingConfig == null) return;
|
||||
// Przygotowujemy zamianę pliku ffmpeg bezpośrednio w /usr/lib/jellyfin-ffmpeg/ffmpeg
|
||||
string defaultFfmpeg = "/usr/lib/jellyfin-ffmpeg/ffmpeg";
|
||||
string origFfmpeg = "/usr/lib/jellyfin-ffmpeg/ffmpeg.original";
|
||||
string realFfmpegPath = origFfmpeg;
|
||||
|
||||
// Używamy dynamic, aby ominąć potrzebę rzutowania na konkretny, wewnętrzny typ Jellyfin (EncodingOptions)
|
||||
dynamic dynConfig = encodingConfig;
|
||||
string currentFfmpegPath = dynConfig.EncoderAppPath ?? "/usr/lib/jellyfin-ffmpeg/ffmpeg";
|
||||
if (!File.Exists(origFfmpeg))
|
||||
{
|
||||
if (File.Exists(defaultFfmpeg))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Move(defaultFfmpeg, origFfmpeg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Nie udało się przenieść {DefaultFfmpeg} do {OrigFfmpeg}", defaultFfmpeg, origFfmpeg);
|
||||
realFfmpegPath = defaultFfmpeg;
|
||||
}
|
||||
}
|
||||
else if (File.Exists("/usr/bin/ffmpeg"))
|
||||
{
|
||||
realFfmpegPath = "/usr/bin/ffmpeg";
|
||||
}
|
||||
}
|
||||
|
||||
string wrapperDir = Path.Combine(_appPaths.PluginsPath, "BlackBarRemover");
|
||||
Directory.CreateDirectory(wrapperDir);
|
||||
string wrapperPath = Path.Combine(wrapperDir, "ffmpeg-wrapper.sh");
|
||||
|
||||
// Jeśli obecna ścieżka to już nasz wrapper, nie robimy pętli w nieskończoność.
|
||||
// Znajdujemy poprzednio zapisaną prawdziwą ścieżkę z pliku.
|
||||
string realFfmpegPath = currentFfmpegPath;
|
||||
if (currentFfmpegPath == wrapperPath)
|
||||
{
|
||||
if (File.Exists(wrapperPath))
|
||||
{
|
||||
var lines = File.ReadAllLines(wrapperPath);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.StartsWith("REAL_FFMPEG="))
|
||||
{
|
||||
realFfmpegPath = line.Split('=')[1].Trim('"');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (realFfmpegPath == wrapperPath) realFfmpegPath = "/usr/lib/jellyfin-ffmpeg/ffmpeg";
|
||||
}
|
||||
string wrapperPath = Path.Combine(wrapperDir, "ffmpeg");
|
||||
|
||||
string script = $@"#!/bin/bash
|
||||
REAL_FFMPEG=""{realFfmpegPath}""
|
||||
@@ -124,7 +121,7 @@ if [ -n ""$INPUT_FILE"" ]; then
|
||||
if [ $next_idx -le $# ]; then
|
||||
next_arg=""${{!next_idx}}""
|
||||
fi
|
||||
NEW_ARGS+=(""crop=$CROP_DATA,$next_arg"")
|
||||
NEW_ARGS+=(""$next_arg,crop=$CROP_DATA"")
|
||||
let i++
|
||||
continue
|
||||
fi
|
||||
@@ -151,21 +148,37 @@ fi
|
||||
exec ""$REAL_FFMPEG"" ""$@""
|
||||
";
|
||||
File.WriteAllText(wrapperPath, script);
|
||||
|
||||
if (Environment.OSVersion.Platform == PlatformID.Unix)
|
||||
if (File.Exists(origFfmpeg) || defaultFfmpeg != origFfmpeg)
|
||||
{
|
||||
System.Diagnostics.Process.Start("chmod", $"+x \"{wrapperPath}\"");
|
||||
try
|
||||
{
|
||||
File.WriteAllText(defaultFfmpeg, script);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Nie udało się zapisać skryptu wrappera do {DefaultFfmpeg}", defaultFfmpeg);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Wygenerowano skrypt FFmpeg Wrapper w lokalizacji: {Path}", wrapperPath);
|
||||
|
||||
// Teraz automagicznie podmieniamy ścieżkę w Jellyfin!
|
||||
if (dynConfig.EncoderAppPath != wrapperPath)
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
dynConfig.EncoderAppPath = wrapperPath;
|
||||
_configManager.SaveConfiguration("encoding", encodingConfig);
|
||||
_logger.LogInformation("Automatycznie zmieniono ścieżkę FFmpeg w konfiguracji Jellyfin na wrapper wtyczki.");
|
||||
string[] pathsToChmod = new[] { wrapperPath, defaultFfmpeg };
|
||||
foreach (var path in pathsToChmod)
|
||||
{
|
||||
if (!File.Exists(path)) continue;
|
||||
try
|
||||
{
|
||||
File.SetUnixFileMode(path, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherExecute);
|
||||
}
|
||||
catch
|
||||
{
|
||||
var proc = System.Diagnostics.Process.Start("chmod", $"+x \"{path}\"");
|
||||
proc?.WaitForExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Wygenerowano skrypt FFmpeg Wrapper w lokalizacji: {Path} oraz {DefaultFfmpeg}", wrapperPath, defaultFfmpeg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user