mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-23 06:45:19 -07:00
Merge branch 'rename-existing-folder' into develop
This commit is contained in:
commit
a340bc4da3
12 changed files with 397 additions and 330 deletions
|
@ -64,6 +64,11 @@ namespace NzbDrone.Common.Extensions
|
||||||
return childPath.Substring(parentPath.Length).Trim(Path.DirectorySeparatorChar);
|
return childPath.Substring(parentPath.Length).Trim(Path.DirectorySeparatorChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetRelativePathWithoutChildCheck(this string parentPath, string childPath)
|
||||||
|
{
|
||||||
|
return childPath.Substring(parentPath.Length).Trim(Path.DirectorySeparatorChar);
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetParentPath(this string childPath)
|
public static string GetParentPath(this string childPath)
|
||||||
{
|
{
|
||||||
var parentPath = childPath.TrimEnd('\\', '/');
|
var parentPath = childPath.TrimEnd('\\', '/');
|
||||||
|
|
|
@ -123,6 +123,17 @@ namespace NzbDrone.Core.Extras
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
public void Handle(MovieFolderCreatedEvent message)
|
||||||
|
{
|
||||||
|
var movie = message.Movie;
|
||||||
|
|
||||||
|
foreach(var extraFileManager in _extraFileManagers)
|
||||||
|
{
|
||||||
|
//extraFileManager.CreateAfterMovieImport(movie, message.MovieFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Handle(EpisodeFolderCreatedEvent message)
|
public void Handle(EpisodeFolderCreatedEvent message)
|
||||||
{
|
{
|
||||||
var series = message.Series;
|
var series = message.Series;
|
||||||
|
|
|
@ -116,6 +116,12 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
|
|
||||||
_diskTransferService.TransferFile(movieFilePath, destinationFilePath, mode);
|
_diskTransferService.TransferFile(movieFilePath, destinationFilePath, mode);
|
||||||
|
|
||||||
|
var oldMoviePath = new OsPath(movieFilePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
|
||||||
|
var newMoviePath = new OsPath(destinationFilePath).Directory.FullPath.TrimEnd(Path.DirectorySeparatorChar);
|
||||||
|
movie.Path = newMoviePath;
|
||||||
|
|
||||||
movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath);
|
movieFile.RelativePath = movie.Path.GetRelativePath(destinationFilePath);
|
||||||
|
|
||||||
_updateMovieFileService.ChangeFileDateForFile(movieFile, movie);
|
_updateMovieFileService.ChangeFileDateForFile(movieFile, movie);
|
||||||
|
@ -132,6 +138,8 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
|
|
||||||
_mediaFileAttributeService.SetFilePermissions(destinationFilePath);
|
_mediaFileAttributeService.SetFilePermissions(destinationFilePath);
|
||||||
|
|
||||||
|
_diskProvider.DeleteFolder(oldMoviePath, true);
|
||||||
|
|
||||||
return movieFile;
|
return movieFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +152,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
var movieFolder = Path.GetDirectoryName(filePath);
|
var movieFolder = Path.GetDirectoryName(filePath);
|
||||||
var rootFolder = new OsPath(movieFolder).Directory.FullPath;
|
var rootFolder = new OsPath(movieFolder).Directory.FullPath;
|
||||||
|
var fileName = Path.GetFileName(filePath);
|
||||||
|
|
||||||
if (!_diskProvider.FolderExists(rootFolder))
|
if (!_diskProvider.FolderExists(rootFolder))
|
||||||
{
|
{
|
||||||
|
@ -156,7 +165,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
if (!_diskProvider.FolderExists(movieFolder))
|
if (!_diskProvider.FolderExists(movieFolder))
|
||||||
{
|
{
|
||||||
CreateFolder(movieFolder);
|
CreateFolder(movieFolder);
|
||||||
newEvent.SeriesFolder = movieFolder;
|
newEvent.MovieFolder = movieFolder;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,8 +71,9 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
MovieId = movie.Id,
|
MovieId = movie.Id,
|
||||||
MovieFileId = file.Id,
|
MovieFileId = file.Id,
|
||||||
ExistingPath = file.RelativePath,
|
ExistingPath = movieFilePath,
|
||||||
NewPath = movie.Path.GetRelativePath(newPath)
|
//NewPath = movie.Path.GetRelativePath(newPath)
|
||||||
|
NewPath = newPath
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +95,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
_movieFileMover.MoveMovieFile(movieFile, movie);
|
_movieFileMover.MoveMovieFile(movieFile, movie);
|
||||||
|
|
||||||
_mediaFileService.Update(movieFile);
|
_mediaFileService.Update(movieFile);
|
||||||
|
_movieService.UpdateMovie(movie);
|
||||||
renamed.Add(movieFile);
|
renamed.Add(movieFile);
|
||||||
|
|
||||||
_logger.Debug("Renamed movie file: {0}", movieFile);
|
_logger.Debug("Renamed movie file: {0}", movieFile);
|
||||||
|
@ -122,7 +124,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
|
|
||||||
public void Execute(RenameMovieCommand message)
|
public void Execute(RenameMovieCommand message)
|
||||||
{
|
{
|
||||||
_logger.Debug("Renaming all files for selected movie");
|
_logger.Debug("Renaming all files for selected movies");
|
||||||
var moviesToRename = _movieService.GetMovies(message.MovieIds);
|
var moviesToRename = _movieService.GetMovies(message.MovieIds);
|
||||||
|
|
||||||
foreach(var movie in moviesToRename)
|
foreach(var movie in moviesToRename)
|
||||||
|
|
|
@ -210,7 +210,7 @@ namespace NzbDrone.Core.Notifications
|
||||||
public void Handle(MovieDownloadedEvent message)
|
public void Handle(MovieDownloadedEvent message)
|
||||||
{
|
{
|
||||||
var downloadMessage = new DownloadMessage();
|
var downloadMessage = new DownloadMessage();
|
||||||
downloadMessage.Message = GetMessage(message.Movie.Movie, message.Movie.Quality);
|
downloadMessage.Message = GetMessage(message.Movie.Movie, message.Movie.Quality);
|
||||||
downloadMessage.Series = null;
|
downloadMessage.Series = null;
|
||||||
downloadMessage.EpisodeFile = null;
|
downloadMessage.EpisodeFile = null;
|
||||||
downloadMessage.MovieFile = message.MovieFile;
|
downloadMessage.MovieFile = message.MovieFile;
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace NzbDrone.Core.Notifications.Plex
|
||||||
public interface IPlexServerProxy
|
public interface IPlexServerProxy
|
||||||
{
|
{
|
||||||
List<PlexSection> GetTvSections(PlexServerSettings settings);
|
List<PlexSection> GetTvSections(PlexServerSettings settings);
|
||||||
List<PlexSection> GetMovieSections(PlexServerSettings settings);
|
List<PlexSection> GetMovieSections(PlexServerSettings settings);
|
||||||
void Update(int sectionId, PlexServerSettings settings);
|
void Update(int sectionId, PlexServerSettings settings);
|
||||||
void UpdateSeries(int metadataId, PlexServerSettings settings);
|
void UpdateSeries(int metadataId, PlexServerSettings settings);
|
||||||
string Version(PlexServerSettings settings);
|
string Version(PlexServerSettings settings);
|
||||||
|
@ -81,12 +81,12 @@ namespace NzbDrone.Core.Notifications.Plex
|
||||||
return Json.Deserialize<PlexMediaContainerLegacy>(response.Content)
|
return Json.Deserialize<PlexMediaContainerLegacy>(response.Content)
|
||||||
.Sections
|
.Sections
|
||||||
.Where(d => d.Type == "movie")
|
.Where(d => d.Type == "movie")
|
||||||
.Select(s => new PlexSection
|
.Select(s => new PlexSection
|
||||||
{
|
{
|
||||||
Id = s.Id,
|
Id = s.Id,
|
||||||
Language = s.Language,
|
Language = s.Language,
|
||||||
Locations = s.Locations,
|
Locations = s.Locations,
|
||||||
Type = s.Type
|
Type = s.Type
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,8 +86,8 @@ namespace NzbDrone.Core.Notifications.Slack
|
||||||
};
|
};
|
||||||
|
|
||||||
NotifySlack(payload);
|
NotifySlack(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnRename(Series series)
|
public override void OnRename(Series series)
|
||||||
{
|
{
|
||||||
var payload = new SlackPayload
|
var payload = new SlackPayload
|
||||||
|
|
|
@ -1,324 +1,349 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Organizer
|
namespace NzbDrone.Core.Organizer
|
||||||
{
|
{
|
||||||
public interface IBuildFileNames
|
public interface IBuildFileNames
|
||||||
{
|
{
|
||||||
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
|
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
|
||||||
string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null);
|
string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null);
|
||||||
string BuildFilePath(Movie movie, string fileName, string extension);
|
string BuildFilePath(Movie movie, string fileName, string extension);
|
||||||
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
|
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
|
||||||
string BuildSeasonPath(Series series, int seasonNumber);
|
string BuildSeasonPath(Series series, int seasonNumber);
|
||||||
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
||||||
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
||||||
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
|
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
|
||||||
string GetMovieFolder(Movie movie, NamingConfig namingConfig = null);
|
string GetMovieFolder(Movie movie, NamingConfig namingConfig = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileNameBuilder : IBuildFileNames
|
public class FileNameBuilder : IBuildFileNames
|
||||||
{
|
{
|
||||||
private readonly INamingConfigService _namingConfigService;
|
private readonly INamingConfigService _namingConfigService;
|
||||||
private readonly IQualityDefinitionService _qualityDefinitionService;
|
private readonly IQualityDefinitionService _qualityDefinitionService;
|
||||||
private readonly ICached<EpisodeFormat[]> _episodeFormatCache;
|
private readonly ICached<EpisodeFormat[]> _episodeFormatCache;
|
||||||
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
|
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
private static readonly Regex TitleRegex = new Regex(@"\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9]+))?(?<suffix>[- ._)\]]*)\}",
|
private static readonly Regex TitleRegex = new Regex(@"\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9]+))?(?<suffix>[- ._)\]]*)\}",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
|
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly Regex TagsRegex = new Regex(@"(?<tags>\{tags(?:\:0+)?})",
|
private static readonly Regex TagsRegex = new Regex(@"(?<tags>\{tags(?:\:0+)?})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly Regex SeasonRegex = new Regex(@"(?<season>\{season(?:\:0+)?})",
|
private static readonly Regex SeasonRegex = new Regex(@"(?<season>\{season(?:\:0+)?})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly Regex AbsoluteEpisodeRegex = new Regex(@"(?<absolute>\{absolute(?:\:0+)?})",
|
private static readonly Regex AbsoluteEpisodeRegex = new Regex(@"(?<absolute>\{absolute(?:\:0+)?})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<seasonEpisode>s?{season(?:\:0+)?}(?<episodeSeparator>[- ._]?[ex])(?<episode>{episode(?:\:0+)?}))(?<separator>[- ._]+?(?={))?",
|
public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<seasonEpisode>s?{season(?:\:0+)?}(?<episodeSeparator>[- ._]?[ex])(?<episode>{episode(?:\:0+)?}))(?<separator>[- ._]+?(?={))?",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
public static readonly Regex AbsoluteEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<absolute>{absolute(?:\:0+)?})(?<separator>[- ._]+?(?={))?",
|
public static readonly Regex AbsoluteEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<absolute>{absolute(?:\:0+)?})(?<separator>[- ._]+?(?={))?",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
|
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{((?:(Movie|Original))(?<separator>[- ._])(Clean)?Title(The)?)\})",
|
public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{((?:(Movie|Original))(?<separator>[- ._])(Clean)?Title(The)?)\})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
|
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
|
||||||
private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled);
|
private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
//TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc)
|
//TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc)
|
||||||
private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' };
|
private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' };
|
||||||
|
|
||||||
public FileNameBuilder(INamingConfigService namingConfigService,
|
public FileNameBuilder(INamingConfigService namingConfigService,
|
||||||
IQualityDefinitionService qualityDefinitionService,
|
IQualityDefinitionService qualityDefinitionService,
|
||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
|
{
|
||||||
|
_namingConfigService = namingConfigService;
|
||||||
|
_qualityDefinitionService = qualityDefinitionService;
|
||||||
|
//_movieFormatCache = cacheManager.GetCache<MovieFormat>(GetType(), "movieFormat");
|
||||||
|
_episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
|
||||||
|
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null)
|
||||||
|
{
|
||||||
|
if (namingConfig == null)
|
||||||
|
{
|
||||||
|
namingConfig = _namingConfigService.GetConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!namingConfig.RenameEpisodes)
|
||||||
|
{
|
||||||
|
return GetOriginalTitle(episodeFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namingConfig.StandardEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Standard)
|
||||||
|
{
|
||||||
|
throw new NamingFormatException("Standard episode format cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namingConfig.DailyEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Daily)
|
||||||
|
{
|
||||||
|
throw new NamingFormatException("Daily episode format cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namingConfig.AnimeEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Anime)
|
||||||
|
{
|
||||||
|
throw new NamingFormatException("Anime episode format cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
var pattern = namingConfig.StandardEpisodeFormat;
|
||||||
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
|
episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList();
|
||||||
|
|
||||||
|
if (series.SeriesType == SeriesTypes.Daily)
|
||||||
|
{
|
||||||
|
pattern = namingConfig.DailyEpisodeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (series.SeriesType == SeriesTypes.Anime && episodes.All(e => e.AbsoluteEpisodeNumber.HasValue))
|
||||||
|
{
|
||||||
|
pattern = namingConfig.AnimeEpisodeFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
|
||||||
|
pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
|
||||||
|
|
||||||
|
AddSeriesTokens(tokenHandlers, series);
|
||||||
|
AddEpisodeTokens(tokenHandlers, episodes);
|
||||||
|
AddEpisodeFileTokens(tokenHandlers, episodeFile);
|
||||||
|
AddQualityTokens(tokenHandlers, series, episodeFile);
|
||||||
|
AddMediaInfoTokens(tokenHandlers, episodeFile);
|
||||||
|
|
||||||
|
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
||||||
|
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
||||||
|
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null)
|
||||||
|
{
|
||||||
|
if (namingConfig == null)
|
||||||
|
{
|
||||||
|
namingConfig = _namingConfigService.GetConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!namingConfig.RenameEpisodes)
|
||||||
|
{
|
||||||
|
return GetOriginalTitle(movieFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pattern = namingConfig.StandardMovieFormat;
|
||||||
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
|
AddMovieTokens(tokenHandlers, movie);
|
||||||
|
AddReleaseDateTokens(tokenHandlers, movie.Year);
|
||||||
|
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
|
||||||
|
AddQualityTokens(tokenHandlers, movie, movieFile);
|
||||||
|
AddMediaInfoTokens(tokenHandlers, movieFile);
|
||||||
|
AddMovieFileTokens(tokenHandlers, movieFile);
|
||||||
|
AddTagsTokens(tokenHandlers, movieFile);
|
||||||
|
|
||||||
|
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
||||||
|
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
||||||
|
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
||||||
|
{
|
||||||
|
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
||||||
|
|
||||||
|
var path = BuildSeasonPath(series, seasonNumber);
|
||||||
|
|
||||||
|
return Path.Combine(path, fileName + extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BuildFilePath(Movie movie, string fileName, string extension)
|
||||||
|
{
|
||||||
|
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
||||||
|
|
||||||
|
var path = BuildMoviePath(movie);
|
||||||
|
|
||||||
|
return Path.Combine(path, fileName + extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BuildMoviePath(Movie movie)
|
||||||
{
|
{
|
||||||
_namingConfigService = namingConfigService;
|
|
||||||
_qualityDefinitionService = qualityDefinitionService;
|
|
||||||
//_movieFormatCache = cacheManager.GetCache<MovieFormat>(GetType(), "movieFormat");
|
|
||||||
_episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
|
|
||||||
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null)
|
|
||||||
{
|
|
||||||
if (namingConfig == null)
|
|
||||||
{
|
|
||||||
namingConfig = _namingConfigService.GetConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!namingConfig.RenameEpisodes)
|
|
||||||
{
|
|
||||||
return GetOriginalTitle(episodeFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (namingConfig.StandardEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Standard)
|
|
||||||
{
|
|
||||||
throw new NamingFormatException("Standard episode format cannot be empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (namingConfig.DailyEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Daily)
|
|
||||||
{
|
|
||||||
throw new NamingFormatException("Daily episode format cannot be empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (namingConfig.AnimeEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Anime)
|
|
||||||
{
|
|
||||||
throw new NamingFormatException("Anime episode format cannot be empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
var pattern = namingConfig.StandardEpisodeFormat;
|
|
||||||
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
|
||||||
|
|
||||||
episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList();
|
|
||||||
|
|
||||||
if (series.SeriesType == SeriesTypes.Daily)
|
|
||||||
{
|
|
||||||
pattern = namingConfig.DailyEpisodeFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (series.SeriesType == SeriesTypes.Anime && episodes.All(e => e.AbsoluteEpisodeNumber.HasValue))
|
|
||||||
{
|
|
||||||
pattern = namingConfig.AnimeEpisodeFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
|
|
||||||
pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
|
|
||||||
|
|
||||||
AddSeriesTokens(tokenHandlers, series);
|
|
||||||
AddEpisodeTokens(tokenHandlers, episodes);
|
|
||||||
AddEpisodeFileTokens(tokenHandlers, episodeFile);
|
|
||||||
AddQualityTokens(tokenHandlers, series, episodeFile);
|
|
||||||
AddMediaInfoTokens(tokenHandlers, episodeFile);
|
|
||||||
|
|
||||||
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
|
||||||
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
|
||||||
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
|
||||||
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null)
|
|
||||||
{
|
|
||||||
if (namingConfig == null)
|
|
||||||
{
|
|
||||||
namingConfig = _namingConfigService.GetConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!namingConfig.RenameEpisodes)
|
|
||||||
{
|
|
||||||
return GetOriginalTitle(movieFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Update namingConfig for Movies!
|
|
||||||
var pattern = namingConfig.StandardMovieFormat;
|
|
||||||
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
|
||||||
|
|
||||||
AddMovieTokens(tokenHandlers, movie);
|
|
||||||
AddReleaseDateTokens(tokenHandlers, movie.Year); //In case we want to separate the year
|
|
||||||
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
|
|
||||||
AddQualityTokens(tokenHandlers, movie, movieFile);
|
|
||||||
AddMediaInfoTokens(tokenHandlers, movieFile);
|
|
||||||
AddMovieFileTokens(tokenHandlers, movieFile);
|
|
||||||
AddTagsTokens(tokenHandlers, movieFile);
|
|
||||||
|
|
||||||
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
|
||||||
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
|
||||||
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
|
||||||
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
|
||||||
{
|
|
||||||
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
|
||||||
|
|
||||||
var path = BuildSeasonPath(series, seasonNumber);
|
|
||||||
|
|
||||||
return Path.Combine(path, fileName + extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string BuildFilePath(Movie movie, string fileName, string extension)
|
|
||||||
{
|
|
||||||
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
|
||||||
|
|
||||||
var path = movie.Path;
|
var path = movie.Path;
|
||||||
|
var directory = new DirectoryInfo(path).Name;
|
||||||
|
var parentDirectoryPath = new DirectoryInfo(path).Parent.FullName;
|
||||||
|
var namingConfig = _namingConfigService.GetConfig();
|
||||||
|
|
||||||
return Path.Combine(path, fileName + extension);
|
var movieFile = movie.MovieFile;
|
||||||
}
|
|
||||||
|
|
||||||
public string BuildSeasonPath(Series series, int seasonNumber)
|
var pattern = namingConfig.MovieFolderFormat;
|
||||||
{
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
var path = series.Path;
|
|
||||||
|
AddMovieTokens(tokenHandlers, movie);
|
||||||
|
AddReleaseDateTokens(tokenHandlers, movie.Year);
|
||||||
|
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
|
||||||
|
AddQualityTokens(tokenHandlers, movie, movieFile);
|
||||||
|
AddMediaInfoTokens(tokenHandlers, movieFile);
|
||||||
|
AddMovieFileTokens(tokenHandlers, movieFile);
|
||||||
|
|
||||||
|
var directoryName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
||||||
|
directoryName = FileNameCleanupRegex.Replace(directoryName, match => match.Captures[0].Value[0].ToString());
|
||||||
|
directoryName = TrimSeparatorsRegex.Replace(directoryName, string.Empty);
|
||||||
|
|
||||||
if (series.SeasonFolder)
|
return Path.Combine(parentDirectoryPath, directoryName);
|
||||||
{
|
}
|
||||||
if (seasonNumber == 0)
|
|
||||||
{
|
public string BuildSeasonPath(Series series, int seasonNumber)
|
||||||
path = Path.Combine(path, "Specials");
|
{
|
||||||
}
|
var path = series.Path;
|
||||||
else
|
|
||||||
{
|
if (series.SeasonFolder)
|
||||||
var seasonFolder = GetSeasonFolder(series, seasonNumber);
|
{
|
||||||
|
if (seasonNumber == 0)
|
||||||
seasonFolder = CleanFileName(seasonFolder);
|
{
|
||||||
|
path = Path.Combine(path, "Specials");
|
||||||
path = Path.Combine(path, seasonFolder);
|
}
|
||||||
}
|
else
|
||||||
}
|
{
|
||||||
|
var seasonFolder = GetSeasonFolder(series, seasonNumber);
|
||||||
return path;
|
|
||||||
}
|
seasonFolder = CleanFileName(seasonFolder);
|
||||||
|
|
||||||
public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec)
|
path = Path.Combine(path, seasonFolder);
|
||||||
{
|
}
|
||||||
return new BasicNamingConfig(); //For now let's be lazy
|
}
|
||||||
|
|
||||||
var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault();
|
return path;
|
||||||
|
}
|
||||||
if (episodeFormat == null)
|
|
||||||
{
|
public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec)
|
||||||
return new BasicNamingConfig();
|
{
|
||||||
}
|
return new BasicNamingConfig(); //For now let's be lazy
|
||||||
|
|
||||||
var basicNamingConfig = new BasicNamingConfig
|
var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault();
|
||||||
{
|
|
||||||
Separator = episodeFormat.Separator,
|
if (episodeFormat == null)
|
||||||
NumberStyle = episodeFormat.SeasonEpisodePattern
|
{
|
||||||
};
|
return new BasicNamingConfig();
|
||||||
|
}
|
||||||
var titleTokens = TitleRegex.Matches(nameSpec.StandardEpisodeFormat);
|
|
||||||
|
var basicNamingConfig = new BasicNamingConfig
|
||||||
foreach (Match match in titleTokens)
|
{
|
||||||
{
|
Separator = episodeFormat.Separator,
|
||||||
var separator = match.Groups["separator"].Value;
|
NumberStyle = episodeFormat.SeasonEpisodePattern
|
||||||
var token = match.Groups["token"].Value;
|
};
|
||||||
|
|
||||||
if (!separator.Equals(" "))
|
var titleTokens = TitleRegex.Matches(nameSpec.StandardEpisodeFormat);
|
||||||
{
|
|
||||||
basicNamingConfig.ReplaceSpaces = true;
|
foreach (Match match in titleTokens)
|
||||||
}
|
{
|
||||||
|
var separator = match.Groups["separator"].Value;
|
||||||
if (token.StartsWith("{Series", StringComparison.InvariantCultureIgnoreCase))
|
var token = match.Groups["token"].Value;
|
||||||
{
|
|
||||||
basicNamingConfig.IncludeSeriesTitle = true;
|
if (!separator.Equals(" "))
|
||||||
}
|
{
|
||||||
|
basicNamingConfig.ReplaceSpaces = true;
|
||||||
if (token.StartsWith("{Episode", StringComparison.InvariantCultureIgnoreCase))
|
}
|
||||||
{
|
|
||||||
basicNamingConfig.IncludeEpisodeTitle = true;
|
if (token.StartsWith("{Series", StringComparison.InvariantCultureIgnoreCase))
|
||||||
}
|
{
|
||||||
|
basicNamingConfig.IncludeSeriesTitle = true;
|
||||||
if (token.StartsWith("{Quality", StringComparison.InvariantCultureIgnoreCase))
|
}
|
||||||
{
|
|
||||||
basicNamingConfig.IncludeQuality = true;
|
if (token.StartsWith("{Episode", StringComparison.InvariantCultureIgnoreCase))
|
||||||
}
|
{
|
||||||
}
|
basicNamingConfig.IncludeEpisodeTitle = true;
|
||||||
|
}
|
||||||
return basicNamingConfig;
|
|
||||||
}
|
if (token.StartsWith("{Quality", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
public string GetSeriesFolder(Series series, NamingConfig namingConfig = null)
|
basicNamingConfig.IncludeQuality = true;
|
||||||
{
|
}
|
||||||
if (namingConfig == null)
|
}
|
||||||
{
|
|
||||||
namingConfig = _namingConfigService.GetConfig();
|
return basicNamingConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
public string GetSeriesFolder(Series series, NamingConfig namingConfig = null)
|
||||||
|
{
|
||||||
AddSeriesTokens(tokenHandlers, series);
|
if (namingConfig == null)
|
||||||
|
{
|
||||||
return CleanFolderName(ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig));
|
namingConfig = _namingConfigService.GetConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null)
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
{
|
|
||||||
if (namingConfig == null)
|
AddSeriesTokens(tokenHandlers, series);
|
||||||
{
|
|
||||||
namingConfig = _namingConfigService.GetConfig();
|
return CleanFolderName(ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null)
|
||||||
|
{
|
||||||
AddSeriesTokens(tokenHandlers, series);
|
if (namingConfig == null)
|
||||||
AddSeasonTokens(tokenHandlers, seasonNumber);
|
{
|
||||||
|
namingConfig = _namingConfigService.GetConfig();
|
||||||
return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
|
}
|
||||||
}
|
|
||||||
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null)
|
|
||||||
{
|
AddSeriesTokens(tokenHandlers, series);
|
||||||
if(namingConfig == null)
|
AddSeasonTokens(tokenHandlers, seasonNumber);
|
||||||
{
|
|
||||||
namingConfig = _namingConfigService.GetConfig();
|
return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null)
|
||||||
|
{
|
||||||
AddMovieTokens(tokenHandlers, movie);
|
if(namingConfig == null)
|
||||||
AddReleaseDateTokens(tokenHandlers, movie.Year);
|
{
|
||||||
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
|
namingConfig = _namingConfigService.GetConfig();
|
||||||
|
}
|
||||||
return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig));
|
|
||||||
}
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
public static string CleanTitle(string title)
|
AddMovieTokens(tokenHandlers, movie);
|
||||||
{
|
AddReleaseDateTokens(tokenHandlers, movie.Year);
|
||||||
title = title.Replace("&", "and");
|
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
|
||||||
title = ScenifyReplaceChars.Replace(title, " ");
|
|
||||||
title = ScenifyRemoveChars.Replace(title, string.Empty);
|
return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig));
|
||||||
|
}
|
||||||
return title;
|
|
||||||
}
|
public static string CleanTitle(string title)
|
||||||
|
{
|
||||||
|
title = title.Replace("&", "and");
|
||||||
|
title = ScenifyReplaceChars.Replace(title, " ");
|
||||||
|
title = ScenifyRemoveChars.Replace(title, string.Empty);
|
||||||
|
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
public static string TitleThe(string title)
|
public static string TitleThe(string title)
|
||||||
{
|
{
|
||||||
|
|
|
@ -74,6 +74,19 @@ module.exports = Marionette.Layout.extend({
|
||||||
var file = movie.model.get("movieFile");
|
var file = movie.model.get("movieFile");
|
||||||
this.filesCollection.add(file);
|
this.filesCollection.add(file);
|
||||||
//this.listenTo(this.releaseCollection, 'sync', this._showSearchResults);
|
//this.listenTo(this.releaseCollection, 'sync', this._showSearchResults);
|
||||||
|
|
||||||
|
this.listenTo(this.model, 'change', function(model, options) {
|
||||||
|
if (options && options.changeSource === 'signalr') {
|
||||||
|
this._refresh(movie);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_refresh : function(movie) {
|
||||||
|
this.filesCollection = new FilesCollection();
|
||||||
|
var file = movie.model.get("movieFile");
|
||||||
|
this.filesCollection.add(file);
|
||||||
|
this.onShow();
|
||||||
},
|
},
|
||||||
|
|
||||||
onShow : function() {
|
onShow : function() {
|
||||||
|
|
|
@ -9,6 +9,7 @@ module.exports = Marionette.ItemView.extend({
|
||||||
//var type = this.model.get('seriesType');
|
//var type = this.model.get('seriesType');
|
||||||
return {
|
return {
|
||||||
rename : this.naming.get('renameEpisodes'),
|
rename : this.naming.get('renameEpisodes'),
|
||||||
|
folderFormat: this.naming.get('movieFolderFormat'),
|
||||||
format : this.naming.get('standardMovieFormat')
|
format : this.naming.get('standardMovieFormat')
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
{{#if rename}}
|
{{#if rename}}
|
||||||
|
Folder Naming pattern: {{folderFormat}}<br>
|
||||||
Naming pattern: {{format}}
|
Naming pattern: {{format}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<div class="path-info x-path-info">All paths are relative to: <strong>{{path}}</strong></div>
|
<div class="path-info x-path-info">Your movie may be moved; see the paths below</div>
|
||||||
<div class="x-format-region"></div>
|
<div class="x-format-region"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue