#nullable disable
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
{
///
/// Class Video.
///
public class Video : BaseItem,
IHasAspectRatio,
ISupportsPlaceHolders,
IHasMediaSources
{
public Video()
{
AdditionalParts = [];
LocalAlternateVersions = [];
SubtitleFiles = [];
AudioFiles = [];
LinkedAlternateVersions = [];
}
[JsonIgnore]
public Guid? PrimaryVersionId { get; set; }
public string[] AdditionalParts { get; set; }
public string[] LocalAlternateVersions { get; set; }
public LinkedChild[] LinkedAlternateVersions { get; set; }
[JsonIgnore]
public override bool SupportsPlayedStatus => true;
[JsonIgnore]
public override bool SupportsPeople => true;
[JsonIgnore]
public override bool SupportsInheritedParentImages => true;
[JsonIgnore]
public override bool SupportsPositionTicksResume
{
get
{
var extraType = ExtraType;
if (extraType.HasValue)
{
if (extraType.Value == Model.Entities.ExtraType.Sample)
{
return false;
}
if (extraType.Value == Model.Entities.ExtraType.ThemeVideo)
{
return false;
}
if (extraType.Value == Model.Entities.ExtraType.Trailer)
{
return false;
}
}
return true;
}
}
[JsonIgnore]
public override bool SupportsThemeMedia => true;
///
/// Gets or sets the timestamp.
///
/// The timestamp.
public TransportStreamTimestamp? Timestamp { get; set; }
///
/// Gets or sets the subtitle paths.
///
/// The subtitle paths.
public string[] SubtitleFiles { get; set; }
///
/// Gets or sets the audio paths.
///
/// The audio paths.
public string[] AudioFiles { get; set; }
///
/// Gets or sets a value indicating whether this instance has subtitles.
///
/// true if this instance has subtitles; otherwise, false.
public bool HasSubtitles { get; set; }
public bool IsPlaceHolder { get; set; }
///
/// Gets or sets the default index of the video stream.
///
/// The default index of the video stream.
public int? DefaultVideoStreamIndex { get; set; }
///
/// Gets or sets the type of the video.
///
/// The type of the video.
public VideoType VideoType { get; set; }
///
/// Gets or sets the type of the iso.
///
/// The type of the iso.
public IsoType? IsoType { get; set; }
///
/// Gets or sets the video3 D format.
///
/// The video3 D format.
public Video3DFormat? Video3DFormat { get; set; }
///
/// Gets or sets the aspect ratio.
///
/// The aspect ratio.
public string AspectRatio { get; set; }
[JsonIgnore]
public override bool SupportsAddingToPlaylist => true;
[JsonIgnore]
public int MediaSourceCount
{
get
{
return GetMediaSourceCount();
}
}
[JsonIgnore]
public bool IsStacked => AdditionalParts.Length > 0;
[JsonIgnore]
public override bool HasLocalAlternateVersions => LibraryManager.GetLocalAlternateVersionIds(this).Any();
public static IRecordingsManager RecordingsManager { get; set; }
[JsonIgnore]
public override SourceType SourceType
{
get
{
if (IsActiveRecording())
{
return SourceType.LiveTV;
}
return base.SourceType;
}
}
[JsonIgnore]
public bool IsCompleteMedia
{
get
{
if (SourceType == SourceType.Channel)
{
return !Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase);
}
return !IsActiveRecording();
}
}
[JsonIgnore]
protected virtual bool EnableDefaultVideoUserDataKeys => true;
[JsonIgnore]
public override string ContainingFolderPath
{
get
{
if (IsStacked)
{
return System.IO.Path.GetDirectoryName(Path);
}
if (!IsPlaceHolder)
{
if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
{
return Path;
}
}
return base.ContainingFolderPath;
}
}
[JsonIgnore]
public override string FileNameWithoutExtension
{
get
{
if (IsFileProtocol)
{
if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
{
return System.IO.Path.GetFileName(Path);
}
return System.IO.Path.GetFileNameWithoutExtension(Path);
}
return null;
}
}
///
/// Gets a value indicating whether [is3 D].
///
/// true if [is3 D]; otherwise, false.
[JsonIgnore]
public bool Is3D => Video3DFormat.HasValue;
///
/// Gets the type of the media.
///
/// The type of the media.
[JsonIgnore]
public override MediaType MediaType => MediaType.Video;
private int GetMediaSourceCount(HashSet callstack = null)
{
callstack ??= [];
if (PrimaryVersionId.HasValue)
{
var item = LibraryManager.GetItemById(PrimaryVersionId.Value);
if (item is Video video)
{
if (callstack.Contains(video.Id))
{
// Count alternate versions using LibraryManager
var linkedCount = LibraryManager.GetLinkedAlternateVersions(video).Count();
var localCount = LibraryManager.GetLocalAlternateVersionIds(video).Count();
return linkedCount + localCount + 1;
}
callstack.Add(video.Id);
return video.GetMediaSourceCount(callstack);
}
}
// Count alternate versions using LibraryManager
var linkedVersionCount = LibraryManager.GetLinkedAlternateVersions(this).Count();
var localVersionCount = LibraryManager.GetLocalAlternateVersionIds(this).Count();
return linkedVersionCount + localVersionCount + 1;
}
///
public override string GetInheritedOriginalLanguage()
{
if (ExtraType.GetValueOrDefault() == Model.Entities.ExtraType.Trailer)
{
return GetOwner()?.GetInheritedOriginalLanguage();
}
return OriginalLanguage ?? GetOwner()?.GetInheritedOriginalLanguage();
}
public override List GetUserDataKeys()
{
var list = base.GetUserDataKeys();
if (EnableDefaultVideoUserDataKeys)
{
if (ExtraType.HasValue)
{
var key = this.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, GetUserDataKey(key));
}
key = this.GetProviderId(MetadataProvider.Imdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, GetUserDataKey(key));
}
}
else
{
var key = this.GetProviderId(MetadataProvider.Imdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, key);
}
key = this.GetProviderId(MetadataProvider.Tmdb);
if (!string.IsNullOrEmpty(key))
{
list.Insert(0, key);
}
}
}
return list;
}
public void SetPrimaryVersionId(Guid? id)
{
PrimaryVersionId = id;
PresentationUniqueKey = CreatePresentationUniqueKey();
}
///
/// Marks the played status of this video and propagates it to its alternate versions.
///
/// The user.
/// The date played.
/// if set to true [reset position].
public override void MarkPlayed(User user, DateTime? datePlayed, bool resetPosition)
{
base.MarkPlayed(user, datePlayed, resetPosition);
PropagatePlayedState(user, true, resetPosition);
}
///
/// Marks this video unplayed and propagates the change to its alternate versions.
///
/// The user.
public override void MarkUnplayed(User user)
{
base.MarkUnplayed(user);
// MarkUnplayed always clears the position on this video, so reset the versions too.
PropagatePlayedState(user, false, true);
}
///
/// Propagates the played status to every alternate version of this video.
///
/// The user.
/// The played status to apply to the alternate versions.
/// When marking played, controls whether each version's resume point
/// is also reset (true) or left untouched (false). Ignored when marking unplayed,
/// which always fully resets every version.
public void PropagatePlayedState(User user, bool played, bool resetPosition = true)
{
ArgumentNullException.ThrowIfNull(user);
if (!PrimaryVersionId.HasValue && LinkedAlternateVersions.Length == 0 && !HasLocalAlternateVersions)
{
return;
}
foreach (var (item, _) in GetAllItemsForMediaSources())
{
if (item.Id.Equals(Id) || item is not Video)
{
continue;
}
if (played)
{
var dto = new UpdateUserItemDataDto { Played = true };
if (resetPosition)
{
dto.PlaybackPositionTicks = 0;
}
// SaveUserData only writes the fields set on the DTO, so play count and other state are preserved.
UserDataManager.SaveUserData(user, item, dto, UserDataSaveReason.TogglePlayed);
}
else
{
var data = UserDataManager.GetUserData(user, item);
if (data is null)
{
continue;
}
ResetPlayedState(data);
UserDataManager.SaveUserData(user, item, data, UserDataSaveReason.TogglePlayed, CancellationToken.None);
}
}
}
///
/// Gets this video together with all of its alternate versions (local and linked and, when this
/// is itself an alternate, the primary and the primary's other versions), deduplicated.
///
/// This video and every alternate version of it.
public IReadOnlyList