Repository: jellyfin/jellyfin

Issue:
Movie Version Name Changes Based on Title of Movie
### This issue respects the following points:

- [X] This is a **bug**, not a question or a configuration issue; Please visit our forum or chat rooms first to troubleshoot with volunteers, before creating a report. The links can be found [here](https://jellyfin.org/contact/).
- [X] This issue is **not** already reported on [GitHub](https://github.com/jellyfin/jellyfin/issues?q=is%3Aopen+is%3Aissue) _(I've searched it)_.
- [X] I'm using an up to date version of Jellyfin Server stable, unstable or master; We generally do not support previous older versions. If possible, please update to the latest version before opening an issue.
- [X] I agree to follow Jellyfin's [Code of Conduct](https://jellyfin.org/docs/general/community-standards.html#code-of-conduct).
- [X] This report addresses only a single issue; If you encounter multiple issues, kindly create separate reports for each one.

### Description of the bug

When a movie has multiple versions available, version names will be modified in a way where if the title of the movie appears in the version name, the title will be removed. This causes issues with certain version names.

### Reproduction steps

1. Prepare two video files, name one "[movie_name] - [version 1]" and the other "[movie_name] - [version 2]"
2. Create a folder in your movies folder named "[movie_name]"
3. Move the two video files to the newly created folder

### What is the current _bug_ behavior?

If [movie_name] appears anywhere within the text of [version 1] and/or [version 2], then [movie_name] will be removed from the version name.

Hypothetical Example: file names "Rap - 1. Holy crap dude!"; "Rap - 2. Wrapping paper" results in version names display on Jellyfin "1. Holy c dude!"; "2. Wping paper"

Real Example: file names "Ted - 1. Theatrical Version"; "Ted - 2. Unrated Version" results in version names display on Jellyfin "1. Theatrical Version"; "2. Unra Version"

### What is the expected _correct_ behavior?

If [movie_name] appears anywhere within the text of [version 1] and/or [version 2], the version name does not change.

Hypothetical Example: file names "Rap - 1. Holy crap dude!"; "Rap - 2. Wrapping paper" results in version names display on Jellyfin "1. Holy crap dude!"; "2. Wrapping paper"

Real Example: file names "Ted - 1. Theatrical Version"; "Ted - 2. Unrated Version" results in version names display on Jellyfin "1. Theatrical Version"; "2. Unrated Version"

### Jellyfin Server version

Older*

### Specify commit id

_No response_

### Specify unstable release number

_No response_

### Specify version number

10.9.7

### Specify the build version

Release

### Environment

```markdown
- OS: Windows 10
- Linux Kernel: none
- Virtualization: Docker? (using package for Synology available on synocommunity.com)
- Clients: Browser, Windows, Mac OS, Android, iPhone
- Browser: Chrome 127.0.6533.88
- FFmpeg Version: 6.0.1
- Playback Method: N/A
- Hardware Acceleration: none
- GPU Model: N/A
- Plugins: none
- Reverse Proxy: none
- Base URL: none
- Networking: Host
- Storage: local & remote NAS
```


### Jellyfin logs

```shell
N/A
```


### FFmpeg logs

_No response_

### Client / Browser logs

_No response_

### Relevant screenshots or videos

![ted_unra1](https://github.com/user-attachments/assets/4e6f4c0e-4b46-40db-8935-43cebd18c86a)
![ted_unra2](https://github.com/user-attachments/assets/9edd696c-ec00-4966-b8c9-1c7d97f2da84)


### Additional information

_No response_


Hints:

Patch:
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 8bd4fb4f382..f16558d1e7c 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1180,28 +1180,29 @@ private MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, BaseItem ite
             return info;
         }
 
-        private string GetMediaSourceName(BaseItem item)
+        internal string GetMediaSourceName(BaseItem item)
         {
             var terms = new List<string>();
 
             var path = item.Path;
             if (item.IsFileProtocol && !string.IsNullOrEmpty(path))
             {
+                var displayName = System.IO.Path.GetFileNameWithoutExtension(path);
                 if (HasLocalAlternateVersions)
                 {
-                    var displayName = System.IO.Path.GetFileNameWithoutExtension(path)
-                        .Replace(System.IO.Path.GetFileName(ContainingFolderPath), string.Empty, StringComparison.OrdinalIgnoreCase)
-                        .TrimStart(new char[] { ' ', '-' });
-
-                    if (!string.IsNullOrEmpty(displayName))
+                    var containingFolderName = System.IO.Path.GetFileName(ContainingFolderPath);
+                    if (displayName.Length > containingFolderName.Length && displayName.StartsWith(containingFolderName, StringComparison.OrdinalIgnoreCase))
                     {
-                        terms.Add(displayName);
+                        var name = displayName.AsSpan(containingFolderName.Length).TrimStart([' ', '-']);
+                        if (!name.IsWhiteSpace())
+                        {
+                            terms.Add(name.ToString());
+                        }
                     }
                 }
 
                 if (terms.Count == 0)
                 {
-                    var displayName = System.IO.Path.GetFileNameWithoutExtension(path);
                     terms.Add(displayName);
                 }
             }


Test patch:
diff --git a/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs b/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs
index f3ada59dbcd..6171f12e472 100644
--- a/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs
+++ b/tests/Jellyfin.Controller.Tests/Entities/BaseItemTests.cs
@@ -1,4 +1,7 @@
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.MediaInfo;
+using Moq;
 using Xunit;
 
 namespace Jellyfin.Controller.Tests.Entities;
@@ -14,4 +17,30 @@ public class BaseItemTests
     [InlineData("1test 2", "0000000001test 0000000002")]
     public void BaseItem_ModifySortChunks_Valid(string input, string expected)
         => Assert.Equal(expected, BaseItem.ModifySortChunks(input));
+
+    [Theory]
+    [InlineData("/Movies/Ted/Ted.mp4", "/Movies/Ted/Ted - Unrated Edition.mp4", "Ted", "Unrated Edition")]
+    [InlineData("/Movies/Deadpool 2 (2018)/Deadpool 2 (2018).mkv", "/Movies/Deadpool 2 (2018)/Deadpool 2 (2018) - Super Duper Cut.mkv", "Deadpool 2 (2018)", "Super Duper Cut")]
+    public void GetMediaSourceName_Valid(string primaryPath, string altPath, string name, string altName)
+    {
+        var mediaSourceManager = new Mock<IMediaSourceManager>();
+        mediaSourceManager.Setup(x => x.GetPathProtocol(It.IsAny<string>()))
+                .Returns((string x) => MediaProtocol.File);
+        BaseItem.MediaSourceManager = mediaSourceManager.Object;
+
+        var video = new Video()
+        {
+            Path = primaryPath
+        };
+
+        var videoAlt = new Video()
+        {
+            Path = altPath,
+        };
+
+        video.LocalAlternateVersions = [videoAlt.Path];
+
+        Assert.Equal(name, video.GetMediaSourceName(video));
+        Assert.Equal(altName, video.GetMediaSourceName(videoAlt));
+    }
 }