Compare commits

..

6 Commits

Author SHA1 Message Date
felixfoertsch b5cd185b57 use gitea token for release workflow
TubeArchivistMetadata / Build (push) Successful in 44s
TubeArchivistMetadata / Release (push) Has been skipped
2026-07-01 10:27:22 +02:00
felixfoertsch 945f1ed6dd remove unsupported artifact upload from gitea workflow
TubeArchivistMetadata / Build (push) Successful in 44s
TubeArchivistMetadata / Release (push) Has been skipped
2026-07-01 10:21:07 +02:00
felixfoertsch 9433aff28a adapt release workflow for gitea actions
TubeArchivistMetadata / Build (push) Failing after 1m2s
TubeArchivistMetadata / Release (push) Has been skipped
2026-07-01 10:18:45 +02:00
felixfoertsch 754d4abd45 release tubearchivistmetadata 1.4.5.0
TubeArchivistMetadata Release / Build & Release (push) Failing after 1m34s
2026-07-01 09:58:49 +02:00
felixfoertsch 5f4afcc28e scope tubearchivist providers, fix sync intervals 2026-07-01 08:12:39 +02:00
DarkFighterLuke 42c02b33eb Update manifest.json
Signed-off-by: DarkFighterLuke <DarkFighterLuke@users.noreply.github.com>
2025-12-02 13:40:50 +00:00
16 changed files with 327 additions and 120 deletions
+152
View File
@@ -0,0 +1,152 @@
name: TubeArchivistMetadata
permissions:
code: read
releases: write
on:
push:
branches:
- main
tags:
- v*
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
name: Build
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Restore
run: dotnet restore TubeArchivistMetadata.sln
- name: Build
run: dotnet build TubeArchivistMetadata.sln -c Release --no-restore
- name: Package plugin
run: |
set -euo pipefail
plugin_version="$(awk -F'\"' '/^version:/ { print $2 }' build.yaml)"
artifact="artifacts/tubearchivistmetadata_${plugin_version}.zip"
checksum="$(./scripts/package-release.sh)"
echo "PLUGIN_VERSION=$plugin_version" >> "$GITHUB_ENV"
echo "PLUGIN_ARTIFACT=$artifact" >> "$GITHUB_ENV"
echo "PLUGIN_CHECKSUM=$checksum" >> "$GITHUB_ENV"
- name: Validate release metadata
if: startsWith(github.ref, 'refs/tags/v')
run: |
set -euo pipefail
tag_version="${GITHUB_REF_NAME#v}"
if [ "$PLUGIN_VERSION" != "$tag_version" ] && [ "$PLUGIN_VERSION" != "${tag_version}.0" ]; then
echo "Tag $GITHUB_REF_NAME does not match build.yaml version $PLUGIN_VERSION" >&2
exit 1
fi
grep -F "\"version\": \"$PLUGIN_VERSION\"" manifest.json >/dev/null
grep -F "\"checksum\": \"$PLUGIN_CHECKSUM\"" manifest.json >/dev/null
grep -F "/releases/download/$GITHUB_REF_NAME/$(basename "$PLUGIN_ARTIFACT")" manifest.json >/dev/null
release:
runs-on: ubuntu-latest
name: Release
needs: build
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Setup Go for tea
uses: actions/setup-go@v5
with:
go-version: 1.26.x
- name: Install tea
run: go install code.gitea.io/tea@v0.14.0
- name: Restore
run: dotnet restore TubeArchivistMetadata.sln
- name: Build
run: dotnet build TubeArchivistMetadata.sln -c Release --no-restore
- name: Package plugin
run: |
set -euo pipefail
plugin_version="$(awk -F'\"' '/^version:/ { print $2 }' build.yaml)"
artifact="artifacts/tubearchivistmetadata_${plugin_version}.zip"
checksum="$(./scripts/package-release.sh)"
echo "PLUGIN_VERSION=$plugin_version" >> "$GITHUB_ENV"
echo "PLUGIN_ARTIFACT=$artifact" >> "$GITHUB_ENV"
echo "PLUGIN_CHECKSUM=$checksum" >> "$GITHUB_ENV"
- name: Validate release metadata
run: |
set -euo pipefail
tag_version="${GITHUB_REF_NAME#v}"
if [ "$PLUGIN_VERSION" != "$tag_version" ] && [ "$PLUGIN_VERSION" != "${tag_version}.0" ]; then
echo "Tag $GITHUB_REF_NAME does not match build.yaml version $PLUGIN_VERSION" >&2
exit 1
fi
grep -F "\"version\": \"$PLUGIN_VERSION\"" manifest.json >/dev/null
grep -F "\"checksum\": \"$PLUGIN_CHECKSUM\"" manifest.json >/dev/null
grep -F "/releases/download/$GITHUB_REF_NAME/$(basename "$PLUGIN_ARTIFACT")" manifest.json >/dev/null
- name: Generate release notes
run: |
set -euo pipefail
changelog="$(awk '/changelog:/ {flag=1; next} /^[^ ]/ {flag=0} flag' build.yaml | awk '{$1=$1};1')"
{
echo "Changelog:"
echo "* $changelog"
} > release-changelog.md
- name: Create Gitea release
env:
GITEA_SERVER_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
if [ -z "${GITEA_SERVER_TOKEN:-}" ]; then
echo "Missing built-in GITEA_TOKEN" >&2
exit 1
fi
tea logins add \
--name gitea \
--url "$GITHUB_SERVER_URL" \
--token "$GITEA_SERVER_TOKEN" \
--no-version-check
tea releases create "$GITHUB_REF_NAME" \
--repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" \
--title "TubeArchivist Metadata $PLUGIN_VERSION" \
--note-file release-changelog.md \
--asset "$PLUGIN_ARTIFACT" \
--login gitea
-86
View File
@@ -1,86 +0,0 @@
name: TubeArchivistMetadata Release
on:
push:
tags:
- v*
jobs:
build:
runs-on: ubuntu-latest
name: Build & Release
permissions:
contents: write
packages: read
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: master
fetch-depth: 0
- name: Get release version
id: newtag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
- name: Setup .Net
uses: actions/setup-dotnet@v1
with:
dotnet-version: 9.0.x
- name: Add Jellyfin GitHub Nuget registry to sources
run: |
dotnet nuget add source \
--username DarkFighterLuke \
--password ${{ secrets.GITHUB_TOKEN }} \
--store-password-in-clear-text \
--name github https://nuget.pkg.github.com/jellyfin/index.json
- name: Get version number only
run: |
TAG=${{ steps.newtag.outputs.tag }}
echo "VERSION=${TAG#v}" >> $GITHUB_ENV
- name: Generate release message
run: |
# Read the YAML file and extract the value of the 'changelog' field
changelog=$(awk '/changelog:/ {flag=1; next} /^[^ ]/ {flag=0} flag' build.yaml | awk '{$1=$1};1')
# Check if changelog contains multiple lines
if [[ $(echo "$changelog" | wc -l) -gt 1 ]]; then
changelog=$(echo "$changelog" | sed 's/^/* /')
else
changelog="* $changelog"
fi
# Create release-changelog.md
echo "Changelog:" > release-changelog.md
echo "$changelog" >> release-changelog.md
- name: Build Jellyfin Plugin
uses: DarkFighterLuke/jellyfin-plugin-repository-manager@7a96768accc155ac7b597351b5033532f3c48173
id: jprm
with:
dotnet-target: net9.0
version: ${VERSION}
update-manifest: "true"
- name: Create release
uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: ${{ steps.jprm.outputs.artifact }}
makeLatest: latest
bodyFile: release-changelog.md
tag: ${{ steps.newtag.outputs.tag }}
allowUpdates: true
- name: Commit manifest.json
uses: EndBug/add-and-commit@v9
with:
add: manifest.json
commit: --signoff
message: "Update manifest.json"
pull: "--no-rebase -X ours origin master"
+4 -3
View File
@@ -1,7 +1,8 @@
<Project>
<PropertyGroup>
<Version>1.4.4.0</Version>
<AssemblyVersion>1.4.4.0</AssemblyVersion>
<FileVersion>1.4.4.0</FileVersion>
<Version>1.4.5.0</Version>
<AssemblyVersion>1.4.5.0</AssemblyVersion>
<FileVersion>1.4.5.0</FileVersion>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
</PropertyGroup>
</Project>
@@ -47,7 +47,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
public string Name => "TubeArchivist";
/// <inheritdoc />
public bool Supports(BaseItem item) => item is Episode;
public bool Supports(BaseItem item) => item is Episode && Utils.IsTubeArchivistItem(item);
/// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
@@ -65,7 +65,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
_logger.LogDebug("{Message}", string.Format(CultureInfo.CurrentCulture, "Getting images for video: {0} ({1})", video?.Title, videoTAId));
_logger.LogDebug("{Message}", "Thumb URI: " + video?.VidThumbUrl);
if (video != null)
if (video != null && Utils.HasImageUrl(video.VidThumbUrl))
{
list.Add(new RemoteImageInfo
{
@@ -87,6 +87,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
}
else
{
if (!Utils.HasImageUrl(url))
{
throw new HttpRequestException("TubeArchivist returned an empty image URL.");
}
return await Plugin.Instance.HttpClient.GetAsync(new Uri(Utils.SanitizeUrl(Plugin.Instance.Configuration.TubeArchivistUrl + url).TrimEnd('/')), cancellationToken).ConfigureAwait(false);
}
}
@@ -50,6 +50,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<Episode>();
if (string.IsNullOrWhiteSpace(info.Path))
{
return result;
}
var taApi = TubeArchivistApi.GetInstance();
var videoTAId = Utils.GetVideoNameFromPath(info.Path);
var video = await taApi.GetVideo(videoTAId).ConfigureAwait(true);
@@ -79,6 +84,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
{
var results = new List<RemoteSearchResult>();
if (string.IsNullOrWhiteSpace(searchInfo.Path))
{
return results;
}
var taApi = TubeArchivistApi.GetInstance();
var videoTAId = Utils.GetVideoNameFromPath(searchInfo.Path);
@@ -45,12 +45,12 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
public string Name => "TubeArchivist";
/// <inheritdoc />
public bool Supports(BaseItem item) => item is Series;
public bool Supports(BaseItem item) => item is Series && Utils.IsTubeArchivistItem(item);
/// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return new[] { ImageType.Primary };
return new[] { ImageType.Primary, ImageType.Art, ImageType.Banner, ImageType.Backdrop };
}
/// <inheritdoc />
@@ -62,10 +62,8 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
var channel = await taApi.GetChannel(channelTAId).ConfigureAwait(true);
_logger.LogDebug("{Message}", string.Format(CultureInfo.CurrentCulture, "Getting images for channel: {0} ({1})", channel?.Name, channelTAId));
_logger.LogDebug("{Message}", "Thumb URI: " + channel?.ThumbUrl);
_logger.LogDebug("{Message}", "TVArt URI: " + channel?.TvartUrl);
_logger.LogDebug("{Message}", "Banner URI: " + channel?.BannerUrl);
if (channel != null)
if (channel != null && Utils.HasImageUrl(channel.ThumbUrl))
{
list.Add(new RemoteImageInfo
{
@@ -73,18 +71,17 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
Type = ImageType.Primary,
Url = channel.ThumbUrl
});
}
if (channel != null && Utils.HasImageUrl(channel.TvartUrl))
{
list.Add(new RemoteImageInfo
{
ProviderName = Name,
Type = ImageType.Art,
Url = channel.TvartUrl
});
list.Add(new RemoteImageInfo
{
ProviderName = Name,
Type = ImageType.Banner,
Url = channel.BannerUrl
});
list.Add(new RemoteImageInfo
{
ProviderName = Name,
@@ -93,6 +90,16 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
});
}
if (channel != null && Utils.HasImageUrl(channel.BannerUrl))
{
list.Add(new RemoteImageInfo
{
ProviderName = Name,
Type = ImageType.Banner,
Url = channel.BannerUrl
});
}
return list;
}
@@ -105,6 +112,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
}
else
{
if (!Utils.HasImageUrl(url))
{
throw new HttpRequestException("TubeArchivist returned an empty image URL.");
}
return await Plugin.Instance.HttpClient.GetAsync(new Uri(Utils.SanitizeUrl(Plugin.Instance.Configuration.TubeArchivistUrl + url).TrimEnd('/')), cancellationToken).ConfigureAwait(false);
}
}
@@ -49,6 +49,11 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
{
var result = new MetadataResult<Series>();
if (string.IsNullOrWhiteSpace(info.Path))
{
return result;
}
var taApi = TubeArchivistApi.GetInstance();
var channelTAId = Utils.GetChannelNameFromPath(info.Path);
var channel = await taApi.GetChannel(channelTAId).ConfigureAwait(true);
@@ -77,6 +82,10 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Providers
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
{
var results = new List<RemoteSearchResult>();
if (string.IsNullOrWhiteSpace(searchInfo.Path))
{
return results;
}
var taApi = TubeArchivistApi.GetInstance();
var channelTAId = Utils.GetChannelNameFromPath(searchInfo.Path);
@@ -379,7 +379,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
new TaskTriggerInfo
{
Type = TaskTriggerInfoType.IntervalTrigger,
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.TAJFProgressTaskInterval).Ticks
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.JFTAPlaylistsSyncTaskInterval).Ticks
},
];
}
@@ -201,7 +201,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Tasks
new TaskTriggerInfo
{
Type = TaskTriggerInfoType.IntervalTrigger,
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.TAJFProgressTaskInterval).Ticks
IntervalTicks = TimeSpan.FromSeconds(Plugin.Instance!.Configuration.TAJFPlaylistsSyncTaskInterval).Ticks
},
];
}
@@ -96,7 +96,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
{
Name = Name,
SearchProviderName = Constants.ProviderName,
ImageUrl = ThumbUrl,
ImageUrl = Utils.HasImageUrl(ThumbUrl) ? ThumbUrl : null,
ProviderIds = new Dictionary<string, string>() { { Constants.ProviderName, Id } }
};
}
@@ -118,14 +118,16 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
Constants.ProviderName, Id
}
},
ImageInfos = new[]
{
ImageInfos = Utils.HasImageUrl(ThumbUrl) ?
[
new ItemImageInfo
{
Path = ThumbUrl,
Type = ImageType.Primary
}
},
] :
[],
Tags = this.Tags != null ?
this.Tags.ToArray<string>() :
[]
@@ -109,7 +109,7 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
Name = Title,
SearchProviderName = Constants.ProviderName,
ProductionYear = Published.Year,
ImageUrl = VidThumbUrl,
ImageUrl = Utils.HasImageUrl(VidThumbUrl) ? VidThumbUrl : null,
PremiereDate = Published,
ProviderIds = new Dictionary<string, string>() { { Constants.ProviderName, YoutubeId } }
};
@@ -142,14 +142,16 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.TubeArchivist
Constants.ProviderName, YoutubeId
}
},
ImageInfos = new[]
{
ImageInfos = Utils.HasImageUrl(VidThumbUrl) ?
[
new ItemImageInfo
{
Path = VidThumbUrl,
Type = ImageType.Primary
}
},
] :
[],
Tags = this.Tags.ToArray<string>()
};
}
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using MediaBrowser.Controller.Entities;
namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities
{
@@ -125,6 +126,32 @@ namespace Jellyfin.Plugin.TubeArchivistMetadata.Utilities
return path.Split(DetectDirectorySeparator(path)).Last();
}
/// <summary>
/// Checks whether the Jellyfin item belongs to the configured TubeArchivist collection.
/// </summary>
/// <param name="item">Jellyfin item.</param>
/// <returns>True when the item belongs to the configured TubeArchivist collection.</returns>
public static bool IsTubeArchivistItem(BaseItem item)
{
if (Plugin.Instance == null)
{
return false;
}
var topParent = item.GetTopParent();
return string.Equals(topParent?.Name, Plugin.Instance.Configuration.CollectionTitle, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Checks whether a TubeArchivist image URL can be fetched safely.
/// </summary>
/// <param name="url">Image URL returned by TubeArchivist.</param>
/// <returns>True when the image URL is non-empty.</returns>
public static bool HasImageUrl(string? url)
{
return !string.IsNullOrWhiteSpace(url);
}
private static char DetectDirectorySeparator(string path)
{
int backslashCount = path.Count(c => c == '\\');
+2 -2
View File
@@ -1,7 +1,7 @@
<h1 align="center">Jellyfin TubeArchivist Plugin</h1>
<p align="center">
<img alt="Plugin Banner" src="https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png"/>
<img alt="Plugin Banner" src="https://git.felixfoertsch.de/felixfoertsch/tubearchivist-jf-plugin/raw/branch/main/images/logo.png"/>
<br/>
</p>
@@ -38,7 +38,7 @@ The plugin interacts with TubeArchivist APIs to fetch videos and channels metada
1. Go to `Dashboard -> Plugins` and click on the `Manage Repositories` button
2. Add a new repository with the following details:
- Repository name: `TubeArchivistMetadata`
- Repository URL: `https://github.com/tubearchivist/tubearchivist-jf-plugin/raw/master/manifest.json`
- Repository URL: `https://git.felixfoertsch.de/felixfoertsch/tubearchivist-jf-plugin/raw/branch/main/manifest.json`
![Add repository](https://github.com/user-attachments/assets/337ba921-bc97-47ea-815c-c664cf7661a2)
3. Go back to the catalog
+3 -4
View File
@@ -1,8 +1,8 @@
---
name: "TubeArchivistMetadata"
guid: "dc97d0c6-28b0-4242-afb4-5833ae1b3715"
imageUrl: https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png
version: "1.4.4.0"
imageUrl: https://git.felixfoertsch.de/felixfoertsch/tubearchivist-jf-plugin/raw/branch/main/images/logo.png
version: "1.4.5.0"
targetAbi: "10.11.0.0"
framework: "net9.0"
overview: "Metadata for your TubeArchivist library on Jellyfin"
@@ -14,5 +14,4 @@ owner: "DarkFighterLuke"
artifacts:
- "Jellyfin.Plugin.TubeArchivistMetadata.dll"
changelog: >
Handle TubeArchivist playlists pagination
Handle prematurely ended response errors
Limit providers to TubeArchivist paths, skip empty image URLs, and fix playlist sync intervals
+18 -2
View File
@@ -6,8 +6,24 @@
"description": "TubeArchivistMetadata is a plugin that automatically manages metadata for videos downloaded with TubeArchivist from YouTube.\n",
"owner": "DarkFighterLuke",
"overview": "Metadata for your TubeArchivist library on Jellyfin",
"imageUrl": "https://raw.githubusercontent.com/tubearchivist/tubearchivist-jf-plugin/master/images/logo.png",
"imageUrl": "https://git.felixfoertsch.de/felixfoertsch/tubearchivist-jf-plugin/raw/branch/main/images/logo.png",
"versions": [
{
"version": "1.4.5.0",
"changelog": "Limit providers to TubeArchivist paths, skip empty image URLs, and fix playlist sync intervals\n",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.felixfoertsch.de/felixfoertsch/tubearchivist-jf-plugin/releases/download/v1.4.5/tubearchivistmetadata_1.4.5.0.zip",
"checksum": "2b3c47c6468fac2c6e42a005f66630c6",
"timestamp": "2026-07-01T06:20:00Z"
},
{
"version": "1.4.4.0",
"changelog": "Handle TubeArchivist playlists pagination\n",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://github.com/tubearchivist/tubearchivist-jf-plugin/releases/download/v1.4.4/tubearchivistmetadata_1.4.4.0.zip",
"checksum": "90f1d388f7363537df8287e996a96bbd",
"timestamp": "2025-12-02T13:40:47Z"
},
{
"version": "1.4.3.0",
"changelog": "Introduce episode numbering alternative schema\n",
@@ -178,4 +194,4 @@
}
]
}
]
]
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
# Description: Build a deterministic Jellyfin plugin release zip.
# Usage: ./scripts/package-release.sh
main() {
local root
root="$(git rev-parse --show-toplevel)"
cd "$root"
local version
version="$(awk -F'\"' '/^version:/ { print $2 }' build.yaml)"
local dll_path
dll_path="Jellyfin.Plugin.TubeArchivistMetadata/bin/Release/net9.0/Jellyfin.Plugin.TubeArchivistMetadata.dll"
if [[ ! -f "$dll_path" ]]; then
echo "Missing release DLL: $dll_path" >&2
exit 1
fi
local output_dir
output_dir="artifacts"
mkdir -p "$output_dir"
local zip_path
zip_path="$output_dir/tubearchivistmetadata_${version}.zip"
rm -f "$zip_path"
local package_dir
package_dir="$(mktemp -d)"
trap 'rm -rf "'"$package_dir"'"' EXIT
cp "$dll_path" "$package_dir/Jellyfin.Plugin.TubeArchivistMetadata.dll"
TZ=UTC touch -t 198001010000 "$package_dir/Jellyfin.Plugin.TubeArchivistMetadata.dll"
(
cd "$package_dir"
TZ=UTC zip -X -q "$root/$zip_path" Jellyfin.Plugin.TubeArchivistMetadata.dll
)
local entry_count
entry_count="$(zipinfo -1 "$zip_path" | wc -l | tr -d ' ')"
if [[ "$entry_count" != "1" ]]; then
echo "Unexpected zip entry count: $entry_count" >&2
zipinfo -1 "$zip_path" >&2
exit 1
fi
local entry_name
entry_name="$(zipinfo -1 "$zip_path")"
if [[ "$entry_name" != "Jellyfin.Plugin.TubeArchivistMetadata.dll" ]]; then
echo "Unexpected zip entry: $entry_name" >&2
exit 1
fi
md5sum "$zip_path" | cut -d' ' -f1
}
main "$@"