9 Commits

22 changed files with 303 additions and 379 deletions

View File

@@ -8,6 +8,16 @@
<div id="SeasonalsConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox"> <div id="SeasonalsConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox">
<div data-role="content"> <div data-role="content">
<div class="content-primary"> <div class="content-primary">
<div class="sectionTitleContainer">
<h2 class="sectionTitle">Seasonals</h2>
<a is="emby-linkbutton" class="raised raised-mini emby-button" style="margin-left: 2em;"
target="_blank" href="https://github.com/CodeDevMLH/Jellyfin-Seasonals">
<i class="md-icon button-icon button-icon-left secondaryText"></i>
<span>Help</span>
</a>
</div>
<hr style="max-width: 800px; margin: 1em 0;">
<br>
<form id="SeasonalsConfigForm"> <form id="SeasonalsConfigForm">
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
@@ -33,348 +43,447 @@
</select> </select>
<div class="fieldDescription">The season to display if automation is disabled.</div> <div class="fieldDescription">The season to display if automation is disabled.</div>
</div> </div>
<br>
<div is="emby-collapse" title="Advanced Configuration"> <details>
<div class="collapseContent"> <summary>Advanced Configuration</summary>
<h3>Autumn</h3> <p>Configure specific settings for each seasonal theme below.</p>
<div class="checkboxContainer"> <p>All symbol count settings add this number in addition to the standard 12 symbols (if random symbols is enabled).</p>
<details>
<summary>Autumn</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableAutumn" name="EnableAutumn" type="checkbox" is="emby-checkbox" /> <input id="EnableAutumn" name="EnableAutumn" type="checkbox" is="emby-checkbox" />
<span>Enable Autumn</span> <span>Enable Autumn</span>
</label> </label>
<div class="fieldDescription">Enable the autumn theme effects in general.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomLeaves" name="EnableRandomLeaves" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Leaves</span>
</label>
<div class="fieldDescription">Displays leaves randomly distributed across the screen</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomLeavesMobile" name="EnableRandomLeavesMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Leaves on Mobile</span>
</label>
<div class="fieldDescription">Displays leaves randomly distributed across the screen on mobile devices. Warning: High values may affect performance.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="AutumnLeafCount">Leaf Count</label> <label class="inputLabel" for="AutumnLeafCount">Leaf Count</label>
<input is="emby-input" type="number" id="AutumnLeafCount" name="AutumnLeafCount" /> <input is="emby-input" type="number" id="AutumnLeafCount" name="AutumnLeafCount" />
<div class="fieldDescription">Number of additional leaves displayed on screen (if enabled)</div>
</div> </div>
<div class="checkboxContainer"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomLeaves" name="EnableRandomLeaves" type="checkbox" is="emby-checkbox" />
<span>Enable Random Leaves</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label">
<input id="EnableRandomLeavesMobile" name="EnableRandomLeavesMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Random Leaves on Mobile (Warning: High values may affect performance)</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableDifferentDurationAutumn" name="EnableDifferentDurationAutumn" type="checkbox" is="emby-checkbox" /> <input id="EnableDifferentDurationAutumn" name="EnableDifferentDurationAutumn" type="checkbox" is="emby-checkbox" />
<span>Enable Different Duration</span> <span>Enable Different Duration</span>
</label> </label>
<div class="fieldDescription">Randomize the falling speed of each leaf.</div>
</div> </div>
<div class="checkboxContainer"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableRotation" name="EnableRotation" type="checkbox" is="emby-checkbox" /> <input id="EnableRotation" name="EnableRotation" type="checkbox" is="emby-checkbox" />
<span>Enable Rotation</span> <span>Enable Rotation</span>
</label> </label>
<div class="fieldDescription">Rotate leaves as they fall. Notice: May affect performance</div>
</div> </div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<h3>Snowflakes</h3> <details>
<div class="checkboxContainer"> <summary>Snowflakes</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableSnowflakes" name="EnableSnowflakes" type="checkbox" is="emby-checkbox" /> <input id="EnableSnowflakes" name="EnableSnowflakes" type="checkbox" is="emby-checkbox" />
<span>Enable Snowflakes</span> <span>Enable Snowflakes</span>
</label> </label>
<div class="fieldDescription">Enable the snowflakes theme in general.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomSnowflakes" name="EnableRandomSnowflakes" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Snowflakes</span>
</label>
<div class="fieldDescription">Displays snowflakes randomly distributed across the screen.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomSnowflakesMobile" name="EnableRandomSnowflakesMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Snowflakes on Mobile</span>
</label>
<div class="fieldDescription">Displays snowflakes randomly distributed across the screen on mobile devices. Warning: High values may affect performance.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SnowflakesCount">Snowflake Count</label> <label class="inputLabel" for="SnowflakesCount">Snowflake Count</label>
<input is="emby-input" type="number" id="SnowflakesCount" name="SnowflakesCount" /> <input is="emby-input" type="number" id="SnowflakesCount" name="SnowflakesCount" />
<div class="fieldDescription">Number of additional snowflakes displayed (if enabled).</div>
</div> </div>
<div class="checkboxContainer"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomSnowflakes" name="EnableRandomSnowflakes" type="checkbox" is="emby-checkbox" />
<span>Enable Random Snowflakes</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label">
<input id="EnableRandomSnowflakesMobile" name="EnableRandomSnowflakesMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Random Snowflakes on Mobile (Warning: High values may affect performance)</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableColoredSnowflakes" name="EnableColoredSnowflakes" type="checkbox" is="emby-checkbox" /> <input id="EnableColoredSnowflakes" name="EnableColoredSnowflakes" type="checkbox" is="emby-checkbox" />
<span>Enable Colored Snowflakes</span> <span>Enable Colored Snowflakes</span>
</label> </label>
<div class="fieldDescription">Display snowflakes in different colors/shapes.</div>
</div> </div>
<div class="checkboxContainer"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableDifferentDurationSnowflakes" name="EnableDifferentDurationSnowflakes" type="checkbox" is="emby-checkbox" /> <input id="EnableDifferentDurationSnowflakes" name="EnableDifferentDurationSnowflakes" type="checkbox" is="emby-checkbox" />
<span>Enable Different Duration</span> <span>Enable Different Duration</span>
</label> </label>
<div class="fieldDescription">Randomize the falling speed of snowflakes.</div>
</div> </div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<h3>Snowfall</h3> <details>
<div class="checkboxContainer"> <summary>Snowfall</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableSnowfall" name="EnableSnowfall" type="checkbox" is="emby-checkbox" /> <input id="EnableSnowfall" name="EnableSnowfall" type="checkbox" is="emby-checkbox" />
<span>Enable Snowfall</span> <span>Enable Snowfall</span>
</label> </label>
<div class="fieldDescription">Enable the snowfall effect in general.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SnowfallCount">Snowflake Count</label> <label class="inputLabel" for="SnowfallCount">Snowflake Count</label>
<input is="emby-input" type="number" id="SnowfallCount" name="SnowfallCount" /> <input is="emby-input" type="number" id="SnowfallCount" name="SnowfallCount" />
<div class="fieldDescription">Total number of snowflakes for the snowfall effect (recommended values: 300 - 600).</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SnowfallCountMobile">Snowflake Count (Mobile)</label> <label class="inputLabel" for="SnowfallCountMobile">Snowflake Count (Mobile)</label>
<input is="emby-input" type="number" id="SnowfallCountMobile" name="SnowfallCountMobile" /> <input is="emby-input" type="number" id="SnowfallCountMobile" name="SnowfallCountMobile" />
<div class="fieldDescription">Warning: High values may affect performance</div> <div class="fieldDescription">Total number of snowflakes on mobile devices. Warning: High values may affect performance.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SnowfallSpeed">Speed</label> <label class="inputLabel" for="SnowfallSpeed">Speed</label>
<input is="emby-input" type="number" id="SnowfallSpeed" name="SnowfallSpeed" step="0.1" /> <input is="emby-input" type="number" id="SnowfallSpeed" name="SnowfallSpeed" step="0.1" />
<div class="fieldDescription">The speed of the snowfall animation (recommended values: 0 - 5).</div>
</div> </div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<h3>Snowstorm</h3> <details>
<div class="checkboxContainer"> <summary>Snowstorm</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableSnowstorm" name="EnableSnowstorm" type="checkbox" is="emby-checkbox" /> <input id="EnableSnowstorm" name="EnableSnowstorm" type="checkbox" is="emby-checkbox" />
<span>Enable Snowstorm</span> <span>Enable Snowstorm</span>
</label> </label>
<div class="fieldDescription">Enable the snowstorm effect in general.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SnowstormCount">Snowflake Count</label> <label class="inputLabel" for="SnowstormCount">Snowflake Count</label>
<input is="emby-input" type="number" id="SnowstormCount" name="SnowstormCount" /> <input is="emby-input" type="number" id="SnowstormCount" name="SnowstormCount" />
<div class="fieldDescription">Total number of snowflakes in the storm (recommended values: 300 - 600).</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SnowstormCountMobile">Snowflake Count (Mobile)</label> <label class="inputLabel" for="SnowstormCountMobile">Snowflake Count (Mobile)</label>
<input is="emby-input" type="number" id="SnowstormCountMobile" name="SnowstormCountMobile" /> <input is="emby-input" type="number" id="SnowstormCountMobile" name="SnowstormCountMobile" />
<div class="fieldDescription">Warning: High values may affect performance</div> <div class="fieldDescription">Total number of snowflakes on mobile devices. Warning: High values may affect performance.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SnowstormSpeed">Speed</label> <label class="inputLabel" for="SnowstormSpeed">Speed</label>
<input is="emby-input" type="number" id="SnowstormSpeed" name="SnowstormSpeed" step="0.1" /> <input is="emby-input" type="number" id="SnowstormSpeed" name="SnowstormSpeed" step="0.1" />
<div class="fieldDescription">The speed of the snowstorm (falling).</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SnowstormHorizontalWind">Horizontal Wind</label> <label class="inputLabel" for="SnowstormHorizontalWind">Horizontal Wind</label>
<input is="emby-input" type="number" id="SnowstormHorizontalWind" name="SnowstormHorizontalWind" step="0.1" /> <input is="emby-input" type="number" id="SnowstormHorizontalWind" name="SnowstormHorizontalWind" step="0.1" />
<div class="fieldDescription">Strength of the horizontal wind effect (recommended values: 3 - 6).</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SnowstormVerticalVariation">Vertical Variation</label> <label class="inputLabel" for="SnowstormVerticalVariation">Vertical Variation</label>
<input is="emby-input" type="number" id="SnowstormVerticalVariation" name="SnowstormVerticalVariation" step="0.1" /> <input is="emby-input" type="number" id="SnowstormVerticalVariation" name="SnowstormVerticalVariation" step="0.1" />
<div class="fieldDescription">Amount of vertical movement variation (recommended values: 1 - 3).</div>
</div> </div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<h3>Fireworks</h3> <details>
<div class="checkboxContainer"> <summary>Fireworks</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableFireworks" name="EnableFireworks" type="checkbox" is="emby-checkbox" /> <input id="EnableFireworks" name="EnableFireworks" type="checkbox" is="emby-checkbox" />
<span>Enable Fireworks</span> <span>Enable Fireworks</span>
</label> </label>
<div class="fieldDescription">Enable the fireworks effect in general.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="FireworksParticles">Particle Count</label> <label class="inputLabel" for="FireworksParticles">Particle Count</label>
<input is="emby-input" type="number" id="FireworksParticles" name="FireworksParticles" /> <input is="emby-input" type="number" id="FireworksParticles" name="FireworksParticles" />
<div class="fieldDescription">Warning: High values may affect performance</div> <div class="fieldDescription">Number of particles per firework explosion. Warning: High values may affect performance.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="FireworksInterval">Launch Interval (ms)</label> <label class="inputLabel" for="FireworksInterval">Launch Interval (ms)</label>
<input is="emby-input" type="number" id="FireworksInterval" name="FireworksInterval" /> <input is="emby-input" type="number" id="FireworksInterval" name="FireworksInterval" />
<div class="fieldDescription">Time in milliseconds between firework launches.</div>
</div> </div>
<div class="checkboxContainer"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="ScrollFireworks" name="ScrollFireworks" type="checkbox" is="emby-checkbox" /> <input id="ScrollFireworks" name="ScrollFireworks" type="checkbox" is="emby-checkbox" />
<span>Scroll Fireworks</span> <span>Scroll Fireworks</span>
</label> </label>
<div class="fieldDescription">Allow fireworks to scroll with the page.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="MinFireworks">Min Fireworks</label> <label class="inputLabel" for="MinFireworks">Min Fireworks</label>
<input is="emby-input" type="number" id="MinFireworks" name="MinFireworks" /> <input is="emby-input" type="number" id="MinFireworks" name="MinFireworks" />
<div class="fieldDescription">Minimum number of concurrent fireworks.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="MaxFireworks">Max Fireworks</label> <label class="inputLabel" for="MaxFireworks">Max Fireworks</label>
<input is="emby-input" type="number" id="MaxFireworks" name="MaxFireworks" /> <input is="emby-input" type="number" id="MaxFireworks" name="MaxFireworks" />
<div class="fieldDescription">Maximum number of concurrent fireworks.</div>
</div> </div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<h3>Halloween</h3> <details>
<div class="checkboxContainer"> <summary>Halloween</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableHalloween" name="EnableHalloween" type="checkbox" is="emby-checkbox" /> <input id="EnableHalloween" name="EnableHalloween" type="checkbox" is="emby-checkbox" />
<span>Enable Halloween</span> <span>Enable Halloween</span>
</label> </label>
<div class="fieldDescription">Enable the Halloween theme in general.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomHalloween" name="EnableRandomHalloween" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Symbols</span>
</label>
<div class="fieldDescription">Displays Halloween symbols randomly distributed across the screen.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomHalloweenMobile" name="EnableRandomHalloweenMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Symbols on Mobile</span>
</label>
<div class="fieldDescription">Displays Halloween symbols randomly distributed across the screen on mobile devices. Warning: High values may affect performance.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="HalloweenCount">Symbol Count</label> <label class="inputLabel" for="HalloweenCount">Symbol Count</label>
<input is="emby-input" type="number" id="HalloweenCount" name="HalloweenCount" /> <input is="emby-input" type="number" id="HalloweenCount" name="HalloweenCount" />
<div class="fieldDescription">Number of additional Halloween symbols (pumpkins, ghosts, etc.) on screen (if enabled).</div>
</div> </div>
<div class="checkboxContainer"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomHalloween" name="EnableRandomHalloween" type="checkbox" is="emby-checkbox" />
<span>Enable Random Symbols</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label">
<input id="EnableRandomHalloweenMobile" name="EnableRandomHalloweenMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Random Symbols on Mobile (Warning: High values may affect performance)</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableDifferentDurationHalloween" name="EnableDifferentDurationHalloween" type="checkbox" is="emby-checkbox" /> <input id="EnableDifferentDurationHalloween" name="EnableDifferentDurationHalloween" type="checkbox" is="emby-checkbox" />
<span>Enable Different Duration</span> <span>Enable Different Duration</span>
</label> </label>
<div class="fieldDescription">Randomize the movement speed.</div>
</div> </div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<h3>Hearts</h3> <details>
<div class="checkboxContainer"> <summary>Hearts</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableHearts" name="EnableHearts" type="checkbox" is="emby-checkbox" /> <input id="EnableHearts" name="EnableHearts" type="checkbox" is="emby-checkbox" />
<span>Enable Hearts</span> <span>Enable Hearts</span>
</label> </label>
<div class="fieldDescription">Enable the Hearts theme in general.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomHearts" name="EnableRandomHearts" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Symbols</span>
</label>
<div class="fieldDescription">Displays hearts randomly distributed across the screen.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomHeartsMobile" name="EnableRandomHeartsMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Symbols on Mobile</span>
</label>
<div class="fieldDescription">Displays hearts randomly distributed across the screen. on mobile devices. Warning: High values may affect performance.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="HeartsCount">Symbol Count</label> <label class="inputLabel" for="HeartsCount">Symbol Count</label>
<input is="emby-input" type="number" id="HeartsCount" name="HeartsCount" /> <input is="emby-input" type="number" id="HeartsCount" name="HeartsCount" />
<div class="fieldDescription">Number of additional floating hearts.</div>
</div> </div>
<div class="checkboxContainer"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomHearts" name="EnableRandomHearts" type="checkbox" is="emby-checkbox" />
<span>Enable Random Symbols</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label">
<input id="EnableRandomHeartsMobile" name="EnableRandomHeartsMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Random Symbols on Mobile (Warning: High values may affect performance)</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableDifferentDurationHearts" name="EnableDifferentDurationHearts" type="checkbox" is="emby-checkbox" /> <input id="EnableDifferentDurationHearts" name="EnableDifferentDurationHearts" type="checkbox" is="emby-checkbox" />
<span>Enable Different Duration</span> <span>Enable Different Duration</span>
</label> </label>
<div class="fieldDescription">Randomize the floating speed.</div>
</div> </div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<h3>Christmas</h3> <details>
<div class="checkboxContainer"> <summary>Christmas</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableChristmas" name="EnableChristmas" type="checkbox" is="emby-checkbox" /> <input id="EnableChristmas" name="EnableChristmas" type="checkbox" is="emby-checkbox" />
<span>Enable Christmas</span> <span>Enable Christmas</span>
</label> </label>
<div class="fieldDescription">Enable the Christmas theme in general.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomChristmas" name="EnableRandomChristmas" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Christmas</span>
</label>
<div class="fieldDescription">Displays Christmas-themed icons randomly distributed across the screen.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomChristmasMobile" name="EnableRandomChristmasMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Christmas on Mobile</span>
</label>
<div class="fieldDescription">Displays Christmas-themed icons randomly distributed across the screen on mobile devices. Warning: High values may affect performance.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="ChristmasCount">Symbol Count</label> <label class="inputLabel" for="ChristmasCount">Symbol Count</label>
<input is="emby-input" type="number" id="ChristmasCount" name="ChristmasCount" /> <input is="emby-input" type="number" id="ChristmasCount" name="ChristmasCount" />
<div class="fieldDescription">Number of additional Christmas symbols.</div>
</div> </div>
<div class="checkboxContainer"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomChristmas" name="EnableRandomChristmas" type="checkbox" is="emby-checkbox" />
<span>Enable Random Christmas</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label">
<input id="EnableRandomChristmasMobile" name="EnableRandomChristmasMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Random Christmas on Mobile (Warning: High values may affect performance)</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableDifferentDurationChristmas" name="EnableDifferentDurationChristmas" type="checkbox" is="emby-checkbox" /> <input id="EnableDifferentDurationChristmas" name="EnableDifferentDurationChristmas" type="checkbox" is="emby-checkbox" />
<span>Enable Different Duration</span> <span>Enable Different Duration</span>
</label> </label>
<div class="fieldDescription">Randomize the movement speed.</div>
</div> </div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<h3>Santa</h3> <details>
<div class="checkboxContainer"> <summary>Santa</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableSanta" name="EnableSanta" type="checkbox" is="emby-checkbox" /> <input id="EnableSanta" name="EnableSanta" type="checkbox" is="emby-checkbox" />
<span>Enable Santa</span> <span>Enable Santa</span>
</label> </label>
<div class="fieldDescription">Enable the Santa theme in general.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SantaSnowflakes">Snowflakes Count</label> <label class="inputLabel" for="SantaSnowflakes">Snowflakes Count</label>
<input is="emby-input" type="number" id="SantaSnowflakes" name="SantaSnowflakes" /> <input is="emby-input" type="number" id="SantaSnowflakes" name="SantaSnowflakes" />
<div class="fieldDescription">Number of snowflakes accompanying Santa (recommended values: 300-600).</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SantaSnowflakesMobile">Snowflakes Count (Mobile)</label> <label class="inputLabel" for="SantaSnowflakesMobile">Snowflakes Count (Mobile)</label>
<input is="emby-input" type="number" id="SantaSnowflakesMobile" name="SantaSnowflakesMobile" /> <input is="emby-input" type="number" id="SantaSnowflakesMobile" name="SantaSnowflakesMobile" />
<div class="fieldDescription">Warning: High values may affect performance</div> <div class="fieldDescription">Number of snowflakes accompanying Santa on mobile devices. Warning: High values may affect performance.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SantaSpeed">Santa Speed (seconds)</label> <label class="inputLabel" for="SantaSpeed">Santa Speed (seconds)</label>
<input is="emby-input" type="number" id="SantaSpeed" name="SantaSpeed" step="0.1" /> <input is="emby-input" type="number" id="SantaSpeed" name="SantaSpeed" step="0.1" />
<div class="fieldDescription">Time in seconds for Santa to cross the screen (recommended values: 5 - 15).</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SantaSpeedMobile">Santa Speed Mobile (seconds)</label> <label class="inputLabel" for="SantaSpeedMobile">Santa Speed Mobile (seconds)</label>
<input is="emby-input" type="number" id="SantaSpeedMobile" name="SantaSpeedMobile" step="0.1" /> <input is="emby-input" type="number" id="SantaSpeedMobile" name="SantaSpeedMobile" step="0.1" />
<div class="fieldDescription">Time in seconds for Santa to cross the screen on mobile (recommended values: 3 - 12).</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="SantaSnowFallSpeed">Snowfall Speed</label> <label class="inputLabel" for="SantaSnowFallSpeed">Snowfall Speed</label>
<input is="emby-input" type="number" id="SantaSnowFallSpeed" name="SantaSnowFallSpeed" step="0.1" /> <input is="emby-input" type="number" id="SantaSnowFallSpeed" name="SantaSnowFallSpeed" step="0.1" />
<div class="fieldDescription">Speed of the falling snow (recommended values: 0-5).</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="MaxSantaRestTime">Max Santa Rest Time (seconds)</label> <label class="inputLabel" for="MaxSantaRestTime">Max Santa Rest Time (seconds)</label>
<input is="emby-input" type="number" id="MaxSantaRestTime" name="MaxSantaRestTime" step="0.1" /> <input is="emby-input" type="number" id="MaxSantaRestTime" name="MaxSantaRestTime" step="0.1" />
<div class="fieldDescription">Maximum time Santa waits before appearing again.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="MinSantaRestTime">Min Santa Rest Time (seconds)</label> <label class="inputLabel" for="MinSantaRestTime">Min Santa Rest Time (seconds)</label>
<input is="emby-input" type="number" id="MinSantaRestTime" name="MinSantaRestTime" step="0.1" /> <input is="emby-input" type="number" id="MinSantaRestTime" name="MinSantaRestTime" step="0.1" />
<div class="fieldDescription">Minimum time Santa waits before appearing again.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="MaxPresentFallSpeed">Max Present Fall Speed (seconds)</label> <label class="inputLabel" for="MaxPresentFallSpeed">Max Present Fall Speed (seconds)</label>
<input is="emby-input" type="number" id="MaxPresentFallSpeed" name="MaxPresentFallSpeed" step="0.1" /> <input is="emby-input" type="number" id="MaxPresentFallSpeed" name="MaxPresentFallSpeed" step="0.1" />
<div class="fieldDescription">Maximum speed of falling presents.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="MinPresentFallSpeed">Min Present Fall Speed (seconds)</label> <label class="inputLabel" for="MinPresentFallSpeed">Min Present Fall Speed (seconds)</label>
<input is="emby-input" type="number" id="MinPresentFallSpeed" name="MinPresentFallSpeed" step="0.1" /> <input is="emby-input" type="number" id="MinPresentFallSpeed" name="MinPresentFallSpeed" step="0.1" />
<div class="fieldDescription">Minimum speed of falling presents.</div>
</div> </div>
</details>
<hr style="max-width: 800px; margin: 1em 0;">
<h3>Easter</h3> <details>
<div class="checkboxContainer"> <summary>Easter</summary>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableEaster" name="EnableEaster" type="checkbox" is="emby-checkbox" /> <input id="EnableEaster" name="EnableEaster" type="checkbox" is="emby-checkbox" />
<span>Enable Easter</span> <span>Enable Easter</span>
</label> </label>
<div class="fieldDescription">Enable the Easter theme in general.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomEaster" name="EnableRandomEaster" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Easter Eggs</span>
</label>
<div class="fieldDescription">Displays easter eggs randomly distributed across the screen.</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomEasterMobile" name="EnableRandomEasterMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Additional Random Easter Eggs on Mobile</span>
</label>
<div class="fieldDescription">Displays easter eggs randomly distributed across the screen on mobile devices. Warning: High values may affect performance.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="EasterEggCount">Egg Count</label> <label class="inputLabel" for="EasterEggCount">Egg Count</label>
<input is="emby-input" type="number" id="EasterEggCount" name="EasterEggCount" /> <input is="emby-input" type="number" id="EasterEggCount" name="EasterEggCount" />
<div class="fieldDescription">Number of additional Easter eggs (if enabled).</div>
</div> </div>
<div class="checkboxContainer"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableRandomEaster" name="EnableRandomEaster" type="checkbox" is="emby-checkbox" />
<span>Enable Random Easter</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label">
<input id="EnableRandomEasterMobile" name="EnableRandomEasterMobile" type="checkbox" is="emby-checkbox" />
<span>Enable Random Easter on Mobile (Warning: High values may affect performance)</span>
</label>
</div>
<div class="checkboxContainer">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EnableDifferentDurationEaster" name="EnableDifferentDurationEaster" type="checkbox" is="emby-checkbox" /> <input id="EnableDifferentDurationEaster" name="EnableDifferentDurationEaster" type="checkbox" is="emby-checkbox" />
<span>Enable Different Duration</span> <span>Enable Different Duration</span>
</label> </label>
<div class="fieldDescription">Randomize the movement speed.</div>
</div> </div>
<div class="checkboxContainer"> <div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label"> <label class="emby-checkbox-label">
<input id="EasterBunny" name="EasterBunny" type="checkbox" is="emby-checkbox" /> <input id="EasterBunny" name="EasterBunny" type="checkbox" is="emby-checkbox" />
<span>Enable Bunny</span> <span>Enable Bunny</span>
</label> </label>
<div class="fieldDescription">Show the Easter Bunny hopping across the screen.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="BunnyDuration">Bunny Duration (ms)</label> <label class="inputLabel" for="BunnyDuration">Bunny Duration (ms)</label>
<input is="emby-input" type="number" id="BunnyDuration" name="BunnyDuration" /> <input is="emby-input" type="number" id="BunnyDuration" name="BunnyDuration" />
<div class="fieldDescription">Time in milliseconds for one hop cycle.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="HopHeight">Hop Height (px)</label> <label class="inputLabel" for="HopHeight">Hop Height (px)</label>
<input is="emby-input" type="number" id="HopHeight" name="HopHeight" /> <input is="emby-input" type="number" id="HopHeight" name="HopHeight" />
<div class="fieldDescription">Height of the bunny's hop in pixels.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="MinBunnyRestTime">Min Bunny Rest Time (ms)</label> <label class="inputLabel" for="MinBunnyRestTime">Min Bunny Rest Time (ms)</label>
<input is="emby-input" type="number" id="MinBunnyRestTime" name="MinBunnyRestTime" /> <input is="emby-input" type="number" id="MinBunnyRestTime" name="MinBunnyRestTime" />
<div class="fieldDescription">Minimum time the bunny waits before appearing again.</div>
</div> </div>
<div class="inputContainer"> <div class="inputContainer">
<label class="inputLabel" for="MaxBunnyRestTime">Max Bunny Rest Time (ms)</label> <label class="inputLabel" for="MaxBunnyRestTime">Max Bunny Rest Time (ms)</label>
<input is="emby-input" type="number" id="MaxBunnyRestTime" name="MaxBunnyRestTime" /> <input is="emby-input" type="number" id="MaxBunnyRestTime" name="MaxBunnyRestTime" />
<div class="fieldDescription">Maximum time the bunny waits before appearing again.</div>
</div> </div>
</details>
</details>
<div style="background-color: rgba(255, 255, 255, 0.05); border-left: 4px solid #00a4dc; border-radius: 4px; padding: 1em 1.5em; margin: 1.5em 0; display: flex; align-items: center; gap: 1em;">
<i class="material-icons" style="color: #00a4dc; font-size: 24px;">info</i>
<div>
All changes require a page refresh (ctrl + r or F5) after saving for changes to take effect. <br/>
If old settings persist, please force clear browser cache.
</div> </div>
</div> </div>
@@ -385,7 +494,6 @@
<button is="emby-button" type="button" class="raised button-cancel block btnCancel" onclick="history.back();"> <button is="emby-button" type="button" class="raised button-cancel block btnCancel" onclick="history.back();">
<span>${ButtonCancel}</span> <span>${ButtonCancel}</span>
</button> </button>
<div class="fieldDescription" style="margin-top: 1em;">Please reload the page (F5) after saving for changes to take effect.</div>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -1,7 +1,7 @@
.autumn-container { .autumn-container {
display: block; display: block;
pointer-events: none; pointer-events: none;
z-index: 0; z-index: 10;
overflow: hidden; overflow: hidden;
} }

View File

@@ -1,7 +1,7 @@
.christmas-container { .christmas-container {
display: block; display: block;
pointer-events: none; pointer-events: none;
z-index: 0; z-index: 10;
overflow: hidden; overflow: hidden;
} }

View File

@@ -1,7 +1,7 @@
.easter-container { .easter-container {
display: block; display: block;
pointer-events: none; pointer-events: none;
z-index: 0; z-index: 10;
overflow: hidden; overflow: hidden;
} }

View File

@@ -1,7 +1,7 @@
.halloween-container { .halloween-container {
display: block; display: block;
pointer-events: none; pointer-events: none;
z-index: 0; z-index: 10;
overflow: hidden; overflow: hidden;
} }

View File

@@ -1,7 +1,7 @@
.hearts-container { .hearts-container {
display: block; display: block;
pointer-events: none; pointer-events: none;
z-index: 0; z-index: 10;
overflow: hidden; overflow: hidden;
} }

View File

@@ -5,6 +5,7 @@
background: transparent; background: transparent;
overflow: hidden; overflow: hidden;
pointer-events: none; pointer-events: none;
z-index: 10;
} }
#snowfallCanvas { #snowfallCanvas {

View File

@@ -4,7 +4,7 @@ const santaIsFlying = config.enableSanta !== undefined ? config.enableSanta : tr
let snowflakesCount = config.snowflakesCount || 500; // count of snowflakes (recommended values: 300-600) let snowflakesCount = config.snowflakesCount || 500; // count of snowflakes (recommended values: 300-600)
const snowflakesCountMobile = config.snowflakesCountMobile || 250; // count of snowflakes on mobile devices (Warning: High values may affect performance) const snowflakesCountMobile = config.snowflakesCountMobile || 250; // count of snowflakes on mobile devices (Warning: High values may affect performance)
const snowFallSpeed = config.snowFallSpeed || 3; // speed of snowfall (recommended values: 0-5) const snowFallSpeed = config.snowFallSpeed || 3; // speed of snowfall (recommended values: 0-5)
const santaSpeed = config.santaSpeed || 10; // speed of santa in seconds (recommended values: 5000-15000) const santaSpeed = config.santaSpeed || 10; // speed of santa in seconds (recommended values: 5-15)
const santaSpeedMobile = config.santaSpeedMobile || 8; // speed of santa on mobile devices in seconds const santaSpeedMobile = config.santaSpeedMobile || 8; // speed of santa on mobile devices in seconds
const maxSantaRestTime = config.maxSantaRestTime || 8; // maximum time santa rests in seconds const maxSantaRestTime = config.maxSantaRestTime || 8; // maximum time santa rests in seconds
const minSantaRestTime = config.minSantaRestTime || 3; // minimum time santa rests in seconds const minSantaRestTime = config.minSantaRestTime || 3; // minimum time santa rests in seconds

View File

@@ -5,6 +5,7 @@
background: transparent; background: transparent;
overflow: hidden; overflow: hidden;
pointer-events: none; pointer-events: none;
z-index: 10;
} }
#snowfallCanvas { #snowfallCanvas {

View File

@@ -1,7 +1,7 @@
.snowflakes { .snowflakes {
display: block; display: block;
pointer-events: none; pointer-events: none;
z-index: 0; z-index: 10;
overflow: hidden; overflow: hidden;
} }

View File

@@ -4,4 +4,5 @@
border-radius: 50%; border-radius: 50%;
pointer-events: none; pointer-events: none;
opacity: 0.7; opacity: 0.7;
z-index: 10;
} }

View File

@@ -14,13 +14,17 @@ This plugin is based on my manual mod (see the [legacy branch](https://github.co
- [Features](#features) - [Features](#features)
- [Overview](#overview) - [Overview](#overview)
- [Installation](#installation) - [Installation](#installation)
- [Usage](#usage) - [Configuration](#configuration)
- [Automatic Theme Selection](#automatic-theme-selection) - [Automatic Theme Selection](#automatic-theme-selection)
- [Theme Settings](#theme-settings)
- [Build Process](#build-process) - [Build Process](#build-process)
- [Troubleshooting](#troubleshooting)
- [Effects Not Showing](#effects-not-showing)
- [Docker Permission Issues](#docker-permission-issues)
- [Contributing](#contributing) - [Contributing](#contributing)
- [Legacy Manual Installation](#legacy-manual-installation) - [Legacy Manual Installation](#legacy-manual-installation)
- [Installation](#installation-1) - [Installation](#installation-1)
- [Usage](#usage-1) - [Usage](#usage)
- [Additional Directory: Separate Single Seasonals](#additional-directory-separate-single-seasonals) - [Additional Directory: Separate Single Seasonals](#additional-directory-separate-single-seasonals)
--- ---
@@ -83,14 +87,14 @@ To install this plugin, you will first need to add the repository in Jellyfin.
9. **Restart your Jellyfin server.** 9. **Restart your Jellyfin server.**
10. **You may need to refresh your browser page** (F5 or Ctrl+R) to see the changes. 10. **You may need to refresh your browser page** (F5 or Ctrl+R) to see the changes.
## Usage ## Configuration
After installation and restart: After installation and restart:
1. Go to **Dashboard** > **Plugins** > **Seasonals**. 1. Go to **Dashboard** > **Plugins** > **Seasonals**.
2. **Enable Seasonals**: Toggle the plugin on or off. 2. **Enable Seasonals**: Toggle the plugin on or off.
3. **Automatic Selection**: 3. **Automatic Selection**:
* If enabled, the plugin selects the theme based on the current date (e.g., Snow in Winter, Hearts in February). * If enabled, the plugin selects the theme based on the current date (e.g., Snow in Winter, Hearts in February). See the table below for details.
* If disabled, you can manually select a theme from the dropdown list. * If disabled, you can manually select a theme from the dropdown list.
4. **Save** your settings. 4. **Save** your settings.
5. **Reload your browser page** (F5 or Ctrl+R) to see the changes. 5. **Reload your browser page** (F5 or Ctrl+R) to see the changes.
@@ -112,6 +116,9 @@ If automatic selection is enabled, the following themes are applied based on the
> **Note:** Holiday themes (like `santa` or `fireworks`) override monthly seasonal themes (like `snowflakes`). > **Note:** Holiday themes (like `santa` or `fireworks`) override monthly seasonal themes (like `snowflakes`).
## Theme Settings
Each theme contains additional settings to customize its behavior. Expand the advanced configuration section to configure each theme, adjust parameters like particle count, animation speed etc.
## Build Process ## Build Process
If you want to build the plugin yourself: If you want to build the plugin yourself:
@@ -124,6 +131,58 @@ If you want to build the plugin yourself:
``` ```
4. The compiled DLL and resources will be in bin/Publish. 4. The compiled DLL and resources will be in bin/Publish.
## Troubleshooting
### Effects Not Showing
1. **Verify plugin installation**:
- Check that the plugin appears in the jellyfin admin panel
- Ensure that the plugin is enabled and active
2. **Clear browser cache**:
- Force refresh browser (Ctrl+F5)
- Clear jellyfin web client cache (--> mostly you have to clear the whole browser cache)
### Docker Permission Issues
If you encounter the message `Access was denied when attempting to inject script into index.html. Automatic direct injection failed. Automatic direct insertion failed. The system will now attempt to use the File Transformation plugin.` in the log or similar permission errors in Docker:
**Option 1: Use File Transformation Plugin (Recommended)**
Seasonals now automatically detects and uses the [File Transformation](https://github.com/IAmParadox27/jellyfin-plugin-file-transformation) plugin (v2.5.0.0+) if it's installed. This eliminates permission issues by transforming content at runtime without modifying files on disk.
**Installation Steps:**
1. Install the File Transformation plugin from the Jellyfin plugin catalog
2. Restart Jellyfin
3. Seasonals will automatically detect and use it (no configuration needed)
4. Check logs to confirm: Look for "Successfully registered transformation with File Transformation plugin"
**Benefits:**
- No file permission issues in Docker environments
- Works with read-only web directories
- Survives Jellyfin updates without re-injection
- No manual file modifications required
**Option 2: Fix File Permissions**
```bash
# Find the actual index.html location
docker exec -it jellyfin find / -name index.html
# Fix ownership (replace 'jellyfin' with your container name and adjust user:group if needed)
docker exec -it --user root jellyfin chown jellyfin:jellyfin /jellyfin/jellyfin-web/index.html
# Restart container
docker restart jellyfin
```
**Option 3: Manual Volume Mapping**
```bash
# Extract index.html from container
docker cp jellyfin:/jellyfin/jellyfin-web/index.html /path/to/jellyfin/config/index.html
# Add to docker-compose.yml volumes section:
volumes:
- /path/to/jellyfin/config/index.html:/jellyfin/jellyfin-web/index.html
```
## Contributing ## Contributing
Feel free to contribute to this project by creating pull requests or reporting issues. Feel free to contribute to this project by creating pull requests or reporting issues.

View File

@@ -1,34 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<JellyfinVersion>10.11.0</JellyfinVersion>
<TargetFramework Condition="$(JellyfinVersion.StartsWith('10.11'))">net9.0</TargetFramework>
<TargetFramework Condition="!$(JellyfinVersion.StartsWith('10.11'))">net8.0</TargetFramework>
<RootNamespace>Jellyfin.Plugin.Seasonals</RootNamespace>
<Nullable>enable</Nullable>
<!-- <AnalysisMode>AllEnabledByDefault</AnalysisMode> -->
<!-- <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> -->
<!-- <GenerateDocumentationFile>true</GenerateDocumentationFile> -->
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
<Title>Jellyfin Seasonals Plugin</Title>
<Authors>CodeDevMLH</Authors>
<Version>1.1.0.0</Version>
<RepositoryUrl>https://github.com/CodeDevMLH/jellyfin-plugin-seasonals</RepositoryUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Jellyfin.Controller" Version="$(JellyfinVersion)" />
<PackageReference Include="Jellyfin.Model" Version="$(JellyfinVersion)" />
</ItemGroup>
<ItemGroup>
<None Remove="Configuration\configPage.html" />
<EmbeddedResource Include="Configuration\configPage.html" />
<None Remove="Web\**" />
<EmbeddedResource Include="Web\**" />
<None Include="..\README.md" />
<None Include="..\logo.png" CopyToOutputDirectory="Always" />
</ItemGroup>
</Project>

View File

@@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
using Jellyfin.Plugin.Seasonals.Configuration;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
namespace Jellyfin.Plugin.Seasonals;
/// <summary>
/// The main plugin.
/// </summary>
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
{
/// <summary>
/// Initializes a new instance of the <see cref="Plugin"/> class.
/// </summary>
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
: base(applicationPaths, xmlSerializer)
{
Instance = this;
}
/// <inheritdoc />
public override string Name => "Seasonals";
/// <inheritdoc />
public override Guid Id => Guid.Parse("ef1e863f-cbb0-4e47-9f23-f0cbb1826ad4");
/// <summary>
/// Gets the current plugin instance.
/// </summary>
public static Plugin? Instance { get; private set; }
/// <inheritdoc />
public IEnumerable<PluginPageInfo> GetPages()
{
return
[
new PluginPageInfo
{
Name = "seasonals",
EmbeddedResourcePath = GetType().Namespace + ".Configuration.configPage.html"
}
];
}
}

View File

@@ -1,18 +0,0 @@
using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Jellyfin.Plugin.Seasonals;
/// <summary>
/// Registers plugin services.
/// </summary>
public class PluginServiceRegistrator : IPluginServiceRegistrator
{
/// <inheritdoc />
public void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost)
{
serviceCollection.AddTransient<IStartupFilter, ScriptInjectionStartupFilter>();
}
}

View File

@@ -1,102 +0,0 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Seasonals;
/// <summary>
/// Middleware to inject the Seasonals script into the Jellyfin web interface.
/// </summary>
public class ScriptInjectionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ScriptInjectionMiddleware> _logger;
private const string ScriptTag = "<script src=\"Seasonals/Resources/seasonals.js\"></script>";
private const string Marker = "</body>";
/// <summary>
/// Initializes a new instance of the <see cref="ScriptInjectionMiddleware"/> class.
/// </summary>
/// <param name="next">The next delegate in the pipeline.</param>
/// <param name="logger">The logger.</param>
public ScriptInjectionMiddleware(RequestDelegate next, ILogger<ScriptInjectionMiddleware> logger)
{
_next = next;
_logger = logger;
}
/// <summary>
/// Invokes the middleware.
/// </summary>
/// <param name="context">The HTTP context.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task InvokeAsync(HttpContext context)
{
if (IsIndexRequest(context.Request.Path))
{
var originalBodyStream = context.Response.Body;
using var responseBody = new MemoryStream();
context.Response.Body = responseBody;
try
{
await _next(context);
context.Response.Body = originalBodyStream;
responseBody.Seek(0, SeekOrigin.Begin);
if (context.Response.StatusCode == 200 &&
context.Response.ContentType != null &&
context.Response.ContentType.Contains("text/html", StringComparison.OrdinalIgnoreCase))
{
using var reader = new StreamReader(responseBody);
var content = await reader.ReadToEndAsync();
if (!content.Contains(ScriptTag, StringComparison.Ordinal) && content.Contains(Marker, StringComparison.Ordinal))
{
var newContent = content.Replace(Marker, $"{ScriptTag}\n{Marker}", StringComparison.Ordinal);
var bytes = Encoding.UTF8.GetBytes(newContent);
context.Response.ContentLength = bytes.Length;
await context.Response.Body.WriteAsync(bytes, 0, bytes.Length);
return;
}
}
responseBody.Seek(0, SeekOrigin.Begin);
await responseBody.CopyToAsync(originalBodyStream);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error injecting Seasonals script via middleware.");
// Ensure we try to write back the original response if something failed
if (responseBody.Length > 0 && context.Response.Body == originalBodyStream)
{
responseBody.Seek(0, SeekOrigin.Begin);
await responseBody.CopyToAsync(originalBodyStream);
}
}
}
else
{
await _next(context);
}
}
private static bool IsIndexRequest(PathString path)
{
if (!path.HasValue)
{
return false;
}
var p = path.Value;
// Check for root, index.html, or web/index.html
return p.Equals("/", StringComparison.OrdinalIgnoreCase) ||
p.Equals("/index.html", StringComparison.OrdinalIgnoreCase) ||
p.EndsWith("/web/index.html", StringComparison.OrdinalIgnoreCase);
}
}

View File

@@ -1,21 +0,0 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
namespace Jellyfin.Plugin.Seasonals;
/// <summary>
/// Startup filter to register the ScriptInjectionMiddleware.
/// </summary>
public class ScriptInjectionStartupFilter : IStartupFilter
{
/// <inheritdoc />
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return builder =>
{
builder.UseMiddleware<ScriptInjectionMiddleware>();
next(builder);
};
}
}

View File

@@ -1,21 +0,0 @@
[
{
"guid": "ef1e863f-cbb0-4e47-9f23-f0cbb1826ad4",
"name": "Seasonals",
"description": "Adds seasonal effects like snow, leaves, etc. to the Jellyfin web interface.",
"overview": "Seasonal effects for Jellyfin",
"owner": "CodeDevMLH",
"category": "General",
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png",
"versions": [
{
"version": "1.0.0.0",
"changelog": "Initial release",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.0.0.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "be6d06a959b3e18e5058a6d8fb6d800c",
"timestamp": "2025-12-15T15:33:15Z"
}
]
}
]

View File

@@ -9,28 +9,28 @@
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png", "imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png",
"versions": [ "versions": [
{ {
"version": "1.2.0.0", "version": "1.3.0.0",
"changelog": "Bug fixing: Fix path, fix injection issue, added File Transformator as fallback if direct injection is blocked due to permissions.", "changelog": "Advanced settings added: Users can now customize the intensity and speed of seasonal effects through the settings panel.",
"targetAbi": "10.11.0.0", "targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.2.0.0/Jellyfin.Plugin.Seasonals.zip", "sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.3.0.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "be2e93364396b6e0e02368d5a7db53bc", "checksum": "3c469c92a5c10a08a4d1cb8e6f387df3",
"timestamp": "2025-12-17T15:32:08Z" "timestamp": "2025-12-17T21:23:20Z"
}, },
{ {
"version": "1.1.1.0", "version": "1.2.0.0",
"changelog": "Bug fixing: Added Advanced Configuration UI for customizing individual seasonal effects.", "changelog": "Advanced settings added: Users can now customize the intensity and speed of seasonal effects through the settings panel.",
"targetAbi": "10.11.0.0", "targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.1.1.0/Jellyfin.Plugin.Seasonals.zip", "sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.2.0.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "f1d7e2b24ad474904fa0eb1749e855b0", "checksum": "5e18022c914c06072035dc0b4429e0fe",
"timestamp": "2025-12-16T00:56:08Z" "timestamp": "2025-12-17T20:59:37Z"
}, },
{ {
"version": "1.1.0.0", "version": "1.1.0.0",
"changelog": "Added Advanced Configuration UI for customizing individual seasonal effects.", "changelog": "Bug fixing: Fix path, fix injection issue, added File Transformator as fallback if direct injection is blocked due to permissions.",
"targetAbi": "10.11.0.0", "targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.1.0.0/Jellyfin.Plugin.Seasonals.zip", "sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.1.0.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "efc26cf0d09b313c52c089fc43df12ab", "checksum": "be2e93364396b6e0e02368d5a7db53bc",
"timestamp": "2025-12-16T00:22:10Z" "timestamp": "2025-12-17T15:32:08Z"
}, },
{ {
"version": "1.0.0.0", "version": "1.0.0.0",