-Thank you for your interest in contributing to Jellyfin. We are a community project made up of volunteers doing our best to keep a free software version of Emby alive and healthy.
-
-This document describes how to contribute to Jellyfin via GitHub, and is applicable to anyone who might wish to open up an issue or do a pull request. Please give it a read-through first.
-
-## Issues
-
-Issues should be one of three things:
-
-1. A software bug with Jellyfin.
-2. A feature request or suggestion.
-3. A task to be done with Jellyfin.
-
-All other discussions should be directed towards the [Jellyfin Riot channel](https://matrix.to/#/#jellyfin:matrix.org), the [Jellyfish development Riot channel](https://matrix.to/#/#jellyfin-dev:matrix.org), or [our subreddit](https://reddit.com/r/jellyfin).
-
-When writing an issue, please ensure you capture as much relevant detail as possible - having to request additional information just leads to wasted time. Please include any log output, configuration options, and system details.
-
-## Pull Requests
-
-We adhere to a fairly standard fork-and-PR model of development:
-
-1. Fork a copy of the Jellyfin repository to your own user.
-
-2. Make your changes on a local feature branch of your copy of the repository.
-
-3. Submit a pull request from your feature branch back to the upstream develop branch. If your PR includes *only* documentation or text file changes you should submit it against master.
-
-All PRs require review before merging. Once that is done, if the PR is not tagged with the WIP label, it should be immediately merged by the reviewer assuming that they have write access. If you do not have write access, please feel free to leave reviews as well and suggest changes as they will be taken into account as well.
-
-## Developer Access
-
-We are open with granting developer (merge/write) access to those who show interest in contributing and reviewing PRs. Such access can be granted by a current developer. Unruly or abusive developers can be removed at any time by the core team, so please take your responsibility seriously. We're all in this together trying to make the best free software media platform we can.
-
-## How can I contribute?
-
-If you're a .NET C# or Javascript developer, please feel free to jump right in - we can use all the help we can get!
-
- If you're not a developer, please consider helping out by contributing documentation, testing the latest releases and reporting bugs, or by telling your friends about the project! Any little bit helps.
+ var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
+ var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength;
+
+ if (currentTranscodingIndex == null)
+ {
+ Logger.Debug("Starting transcoding because currentTranscodingIndex=null");
+ startTranscoding = true;
+ }
+ else if (requestedIndex < currentTranscodingIndex.Value)
+ {
+ Logger.Debug("Starting transcoding because requestedIndex={0} and currentTranscodingIndex={1}", requestedIndex, currentTranscodingIndex);
+ startTranscoding = true;
+ }
+ else if (requestedIndex - currentTranscodingIndex.Value > segmentGapRequiringTranscodingChange)
+ {
+ Logger.Debug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", requestedIndex - currentTranscodingIndex.Value, segmentGapRequiringTranscodingChange, requestedIndex);
+ startTranscoding = true;
+ }
+ if (startTranscoding)
+ {
+ // If the playlist doesn't already exist, startup ffmpeg
+ try
+ {
+ ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
+ [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
+ [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string MediaSourceId { get; set; }
+
+ [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string AudioCodec { get; set; }
+
+ [ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string DeviceProfileId { get; set; }
+
+ public string Params { get; set; }
+ public string PlaySessionId { get; set; }
+ public string Tag { get; set; }
+ public string SegmentContainer { get; set; }
+
+ public int? SegmentLength { get; set; }
+ public int? MinSegments { get; set; }
+ }
+
+ public class VideoStreamRequest : StreamRequest
+ {
+ /// <summary>
+ /// Gets a value indicating whether this instance has fixed resolution.
+ /// </summary>
+ /// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
+ public bool HasFixedResolution
+ {
+ get
+ {
+ return Width.HasValue || Height.HasValue;
+ }
+ }
+
+ public bool EnableSubtitlesInManifest { get; set; }
+ [ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+ public string MediaSourceId { get; set; }
+
+ [ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string DeviceId { get; set; }
+
+ public Guid UserId { get; set; }
+ public string AudioCodec { get; set; }
+ public string Container { get; set; }
+
+ public int? MaxAudioChannels { get; set; }
+ public int? TranscodingAudioChannels { get; set; }
+ // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600.
+ // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar
+ var vf = "scale=600:trunc(600/dar/2)*2";
+
+ if (threedFormat.HasValue)
+ {
+ switch (threedFormat.Value)
+ {
+ case Video3DFormat.HalfSideBySide:
+ vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
+ // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
+ break;
+ case Video3DFormat.FullSideBySide:
+ vf = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
+ //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600.
+ break;
+ case Video3DFormat.HalfTopAndBottom:
+ vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
+ //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600
+ break;
+ case Video3DFormat.FullTopAndBottom:
+ vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
+ // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600
+ // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
+ var thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty;
+ <Warning Text="Packages containing MSBuild targets and props files cannot be fully installed in projects targeting multiple frameworks. The MSBuild targets and props files have been ignored." />
+ private const int MaxSubtitleDescriptionExtractionLength = 100; // When extracting subtitles, the maximum length to consider (to avoid invalid filenames)
+ .Select(i => new BaseItemPerson { Name = i.Trim(), Type = PersonType.Actor })
+ .ToArray();
+ }
+
+ var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");
+ if (!string.IsNullOrWhiteSpace(year))
+ {
+ int val;
+
+ if (int.TryParse(year, NumberStyles.Integer, _usCulture, out val))
+ {
+ video.ProductionYear = val;
+ }
+ }
+
+ var premiereDateString = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaOriginalBroadcastDateTime");
+ if (!string.IsNullOrWhiteSpace(premiereDateString))
+ {
+ DateTime val;
+
+ // Credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
+ // DateTime is reported along with timezone info (typically Z i.e. UTC hence assume None)
+ if (DateTime.TryParse(year, null, DateTimeStyles.None, out val))
+ {
+ video.PremiereDate = val.ToUniversalTime();
+ }
+ }
+
+ var description = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");
+
+ var subTitle = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitle");
+
+ // For below code, credit to MCEBuddy: https://mcebuddy2x.codeplex.com/
+
+ // Sometimes for TV Shows the Subtitle field is empty and the subtitle description contains the subtitle, extract if possible. See ticket https://mcebuddy2x.codeplex.com/workitem/1910
+ // The format is -> EPISODE/TOTAL_EPISODES_IN_SEASON. SUBTITLE: DESCRIPTION
+ // OR -> COMMENT. SUBTITLE: DESCRIPTION
+ // e.g. -> 4/13. The Doctor's Wife: Science fiction drama. When he follows a Time Lord distress signal, the Doctor puts Amy, Rory and his beloved TARDIS in grave danger. Also in HD. [AD,S]
+ // e.g. -> CBeebies Bedtime Hour. The Mystery: Animated adventures of two friends who live on an island in the middle of the big city. Some of Abney and Teal's favourite objects are missing. [S]
+ if (String.IsNullOrWhiteSpace(subTitle) && !String.IsNullOrWhiteSpace(description) && description.Substring(0, Math.Min(description.Length, MaxSubtitleDescriptionExtractionLength)).Contains(":")) // Check within the Subtitle size limit, otherwise from description it can get too long creating an invalid filename
+ {
+ string[] parts = description.Split(':');
+ if (parts.Length > 0)
+ {
+ string subtitle = parts[0];
+ try
+ {
+ if (subtitle.Contains("/")) // It contains a episode number and season number
+ /// Credit to https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs
+ /// </summary>
+ public class SsaParser : ISubtitleParser
+ {
+ public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
+ {
+ var trackInfo = new SubtitleTrackInfo();
+ List<SubtitleTrackEvent> trackEvents = new List<SubtitleTrackEvent>();
@@ -15,7 +15,7 @@ While our first priority is a stable build, we will eventually add features that
## Contributing to Jellyfin
## Contributing to Jellyfin
-If you're interested in contributing, please see [CONTRIBUTING.md](https://github.com/jellyfin/jellyfin/blob/master/CONTRIBUTING.md).
+If you're interested in contributing, please see [our wiki for guidelines](https://github.com/jellyfin/jellyfin/wiki/Contributing-to-Jellyfin).
## Prebuilt Jellyfin packages
## Prebuilt Jellyfin packages
@@ -37,6 +37,8 @@ An Unraid Docker template is available. See [this documentation page](https://gi
A package repository is available at https://repo.jellyfin.org.
A package repository is available at https://repo.jellyfin.org.
+NOTE: Ubuntu users may find that the `ffmpeg` dependency package is not present in their release or is simply a rebranded `libav` which is not directly compatible. Please [obtain the ffmpeg package directly from the FFMPEG site](https://ffmpeg.org/download.html#build-linux) to use Jellyfin on Ubuntu.
+
#### Clean install
#### Clean install
0. Install the `dotnet-runtime-2.2` package via [Microsoft's repositories](https://dotnet.microsoft.com/download/dotnet-core/2.2).
0. Install the `dotnet-runtime-2.2` package via [Microsoft's repositories](https://dotnet.microsoft.com/download/dotnet-core/2.2).
It is built on top of other popular open source technologies such as Service Stack, jQuery, jQuery mobile, and Mono. It features a REST-based api with built-in documentation to facilitate client development. We also have client libraries for our api to enable rapid development.
It is built on top of other popular open source technologies such as Service Stack, jQuery, jQuery mobile, and Mono. It features a REST-based api with built-in documentation to facilitate client development. We also have client libraries for our api to enable rapid development.