parent
365a6e77a6
commit
9f1e215120
|
@ -68,27 +68,27 @@ class MediaManagement extends Component {
|
||||||
<NamingConnector />
|
<NamingConnector />
|
||||||
|
|
||||||
{
|
{
|
||||||
isFetching &&
|
isFetching ?
|
||||||
<FieldSet legend="Naming Settings">
|
<FieldSet legend="Naming Settings">
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
</FieldSet>
|
</FieldSet> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!isFetching && error &&
|
!isFetching && error ?
|
||||||
<FieldSet legend="Naming Settings">
|
<FieldSet legend="Naming Settings">
|
||||||
<div>Unable to load Media Management settings</div>
|
<div>Unable to load Media Management settings</div>
|
||||||
</FieldSet>
|
</FieldSet> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
hasSettings && !isFetching && !error &&
|
hasSettings && !isFetching && !error ?
|
||||||
<Form
|
<Form
|
||||||
id="mediaManagementSettings"
|
id="mediaManagementSettings"
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
advancedSettings &&
|
advancedSettings ?
|
||||||
<FieldSet legend="Folders">
|
<FieldSet legend="Folders">
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
|
@ -121,11 +121,11 @@ class MediaManagement extends Component {
|
||||||
{...settings.deleteEmptyFolders}
|
{...settings.deleteEmptyFolders}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</FieldSet>
|
</FieldSet> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
advancedSettings &&
|
advancedSettings ?
|
||||||
<FieldSet
|
<FieldSet
|
||||||
legend="Importing"
|
legend="Importing"
|
||||||
>
|
>
|
||||||
|
@ -200,6 +200,41 @@ class MediaManagement extends Component {
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
isAdvanced={true}
|
||||||
|
size={sizes.MEDIUM}
|
||||||
|
>
|
||||||
|
<FormLabel>Import Using Script</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="useScriptImport"
|
||||||
|
helpText="Copy files for importing using a script (ex. for transcoding)"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...settings.useScriptImport}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
{
|
||||||
|
settings.useScriptImport.value ?
|
||||||
|
<FormGroup
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
isAdvanced={true}
|
||||||
|
>
|
||||||
|
<FormLabel>Import Script Path</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.PATH}
|
||||||
|
includeFiles={true}
|
||||||
|
name="scriptImportPath"
|
||||||
|
helpText="The path to the script to use for importing"
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...settings.scriptImportPath}
|
||||||
|
/>
|
||||||
|
</FormGroup> : null
|
||||||
|
}
|
||||||
|
|
||||||
<FormGroup size={sizes.MEDIUM}>
|
<FormGroup size={sizes.MEDIUM}>
|
||||||
<FormLabel>Import Extra Files</FormLabel>
|
<FormLabel>Import Extra Files</FormLabel>
|
||||||
|
|
||||||
|
@ -213,7 +248,7 @@ class MediaManagement extends Component {
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{
|
{
|
||||||
settings.importExtraFiles.value &&
|
settings.importExtraFiles.value ?
|
||||||
<FormGroup
|
<FormGroup
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
|
@ -230,9 +265,9 @@ class MediaManagement extends Component {
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...settings.extraFileExtensions}
|
{...settings.extraFileExtensions}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup> : null
|
||||||
}
|
}
|
||||||
</FieldSet>
|
</FieldSet> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
<FieldSet
|
<FieldSet
|
||||||
|
@ -358,7 +393,7 @@ class MediaManagement extends Component {
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
|
|
||||||
{
|
{
|
||||||
advancedSettings && !isWindows &&
|
advancedSettings && !isWindows ?
|
||||||
<FieldSet
|
<FieldSet
|
||||||
legend="Permissions"
|
legend="Permissions"
|
||||||
>
|
>
|
||||||
|
@ -411,9 +446,9 @@ class MediaManagement extends Component {
|
||||||
{...settings.chownGroup}
|
{...settings.chownGroup}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</FieldSet>
|
</FieldSet> : null
|
||||||
}
|
}
|
||||||
</Form>
|
</Form> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
<FieldSet legend="Root Folders">
|
<FieldSet legend="Root Folders">
|
||||||
|
|
|
@ -207,6 +207,20 @@ namespace NzbDrone.Core.Configuration
|
||||||
set { SetValue("EnableMediaInfo", value); }
|
set { SetValue("EnableMediaInfo", value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool UseScriptImport
|
||||||
|
{
|
||||||
|
get { return GetValueBoolean("UseScriptImport", false); }
|
||||||
|
|
||||||
|
set { SetValue("UseScriptImport", value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ScriptImportPath
|
||||||
|
{
|
||||||
|
get { return GetValue("ScriptImportPath"); }
|
||||||
|
|
||||||
|
set { SetValue("ScriptImportPath", value); }
|
||||||
|
}
|
||||||
|
|
||||||
public bool ImportExtraFiles
|
public bool ImportExtraFiles
|
||||||
{
|
{
|
||||||
get { return GetValueBoolean("ImportExtraFiles", false); }
|
get { return GetValueBoolean("ImportExtraFiles", false); }
|
||||||
|
|
|
@ -33,6 +33,8 @@ namespace NzbDrone.Core.Configuration
|
||||||
int MinimumFreeSpaceWhenImporting { get; set; }
|
int MinimumFreeSpaceWhenImporting { get; set; }
|
||||||
bool CopyUsingHardlinks { get; set; }
|
bool CopyUsingHardlinks { get; set; }
|
||||||
bool EnableMediaInfo { get; set; }
|
bool EnableMediaInfo { get; set; }
|
||||||
|
bool UseScriptImport { get; set; }
|
||||||
|
string ScriptImportPath { get; set; }
|
||||||
bool ImportExtraFiles { get; set; }
|
bool ImportExtraFiles { get; set; }
|
||||||
string ExtraFileExtensions { get; set; }
|
string ExtraFileExtensions { get; set; }
|
||||||
RescanAfterRefreshType RescanAfterRefresh { get; set; }
|
RescanAfterRefreshType RescanAfterRefresh { get; set; }
|
||||||
|
|
|
@ -31,6 +31,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
private readonly IDiskTransferService _diskTransferService;
|
private readonly IDiskTransferService _diskTransferService;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly IMediaFileAttributeService _mediaFileAttributeService;
|
private readonly IMediaFileAttributeService _mediaFileAttributeService;
|
||||||
|
private readonly IImportScript _scriptImportDecider;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
@ -41,6 +42,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
IDiskTransferService diskTransferService,
|
IDiskTransferService diskTransferService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IMediaFileAttributeService mediaFileAttributeService,
|
IMediaFileAttributeService mediaFileAttributeService,
|
||||||
|
IImportScript scriptImportDecider,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
|
@ -51,6 +53,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
_diskTransferService = diskTransferService;
|
_diskTransferService = diskTransferService;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_mediaFileAttributeService = mediaFileAttributeService;
|
_mediaFileAttributeService = mediaFileAttributeService;
|
||||||
|
_scriptImportDecider = scriptImportDecider;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -59,6 +62,11 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series)
|
public EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series)
|
||||||
{
|
{
|
||||||
var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id);
|
var episodes = _episodeService.GetEpisodesByFileId(episodeFile.Id);
|
||||||
|
return MoveEpisodeFile(episodeFile, series, episodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, Series series, List<Episode> episodes)
|
||||||
|
{
|
||||||
var filePath = _buildFileNames.BuildFilePath(episodes, series, episodeFile, Path.GetExtension(episodeFile.RelativePath));
|
var filePath = _buildFileNames.BuildFilePath(episodes, series, episodeFile, Path.GetExtension(episodeFile.RelativePath));
|
||||||
|
|
||||||
EnsureEpisodeFolder(episodeFile, series, episodes.Select(v => v.SeasonNumber).First(), filePath);
|
EnsureEpisodeFolder(episodeFile, series, episodes.Select(v => v.SeasonNumber).First(), filePath);
|
||||||
|
@ -76,7 +84,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
|
|
||||||
_logger.Debug("Moving episode file: {0} to {1}", episodeFile.Path, filePath);
|
_logger.Debug("Moving episode file: {0} to {1}", episodeFile.Path, filePath);
|
||||||
|
|
||||||
return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Move);
|
return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Move, localEpisode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpisodeFile CopyEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode)
|
public EpisodeFile CopyEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode)
|
||||||
|
@ -88,14 +96,14 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
if (_configService.CopyUsingHardlinks)
|
if (_configService.CopyUsingHardlinks)
|
||||||
{
|
{
|
||||||
_logger.Debug("Hardlinking episode file: {0} to {1}", episodeFile.Path, filePath);
|
_logger.Debug("Hardlinking episode file: {0} to {1}", episodeFile.Path, filePath);
|
||||||
return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.HardLinkOrCopy);
|
return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.HardLinkOrCopy, localEpisode);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Debug("Copying episode file: {0} to {1}", episodeFile.Path, filePath);
|
_logger.Debug("Copying episode file: {0} to {1}", episodeFile.Path, filePath);
|
||||||
return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Copy);
|
return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Copy, localEpisode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List<Episode> episodes, string destinationFilePath, TransferMode mode)
|
private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List<Episode> episodes, string destinationFilePath, TransferMode mode, LocalEpisode localEpisode = null)
|
||||||
{
|
{
|
||||||
Ensure.That(episodeFile, () => episodeFile).IsNotNull();
|
Ensure.That(episodeFile, () => episodeFile).IsNotNull();
|
||||||
Ensure.That(series, () => series).IsNotNull();
|
Ensure.That(series, () => series).IsNotNull();
|
||||||
|
@ -113,10 +121,33 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath);
|
throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
_diskTransferService.TransferFile(episodeFilePath, destinationFilePath, mode);
|
var transfer = true;
|
||||||
|
|
||||||
episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilePath);
|
episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilePath);
|
||||||
|
|
||||||
|
if (localEpisode is not null)
|
||||||
|
{
|
||||||
|
var scriptImportDecision = _scriptImportDecider.TryImport(episodeFilePath, destinationFilePath, localEpisode, episodeFile, mode);
|
||||||
|
|
||||||
|
switch (scriptImportDecision)
|
||||||
|
{
|
||||||
|
case ScriptImportDecision.DeferMove:
|
||||||
|
break;
|
||||||
|
case ScriptImportDecision.RenameRequested:
|
||||||
|
MoveEpisodeFile(episodeFile, series, episodeFile.Episodes);
|
||||||
|
transfer = false;
|
||||||
|
break;
|
||||||
|
case ScriptImportDecision.MoveComplete:
|
||||||
|
transfer = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transfer)
|
||||||
|
{
|
||||||
|
_diskTransferService.TransferFile(episodeFilePath, destinationFilePath, mode);
|
||||||
|
}
|
||||||
|
|
||||||
_updateEpisodeFileService.ChangeFileDateForFile(episodeFile, series, episodes);
|
_updateEpisodeFileService.ChangeFileDateForFile(episodeFile, series, episodes);
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
@ -9,6 +9,7 @@ using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Extras;
|
using NzbDrone.Core.Extras;
|
||||||
using NzbDrone.Core.MediaFiles.Commands;
|
using NzbDrone.Core.MediaFiles.Commands;
|
||||||
using NzbDrone.Core.MediaFiles.Events;
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
@ -110,8 +111,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
episodeFile.SceneName = localEpisode.SceneName;
|
episodeFile.SceneName = localEpisode.SceneName;
|
||||||
episodeFile.OriginalFilePath = GetOriginalFilePath(downloadClientItem, localEpisode);
|
episodeFile.OriginalFilePath = GetOriginalFilePath(downloadClientItem, localEpisode);
|
||||||
|
|
||||||
var moveResult = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly);
|
oldFiles = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly).OldFiles;
|
||||||
oldFiles = moveResult.OldFiles;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -89,6 +89,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||||
{
|
{
|
||||||
Series = series,
|
Series = series,
|
||||||
DownloadClientEpisodeInfo = downloadClientItemInfo,
|
DownloadClientEpisodeInfo = downloadClientItemInfo,
|
||||||
|
DownloadItem = downloadClientItem,
|
||||||
FolderEpisodeInfo = folderInfo,
|
FolderEpisodeInfo = folderInfo,
|
||||||
Path = file,
|
Path = file,
|
||||||
SceneSource = sceneSource,
|
SceneSource = sceneSource,
|
||||||
|
|
|
@ -161,6 +161,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||||
localEpisode.Episodes = _episodeService.GetEpisodes(episodeIds);
|
localEpisode.Episodes = _episodeService.GetEpisodes(episodeIds);
|
||||||
localEpisode.FileEpisodeInfo = Parser.Parser.ParsePath(path);
|
localEpisode.FileEpisodeInfo = Parser.Parser.ParsePath(path);
|
||||||
localEpisode.DownloadClientEpisodeInfo = downloadClientItem == null ? null : Parser.Parser.ParseTitle(downloadClientItem.Title);
|
localEpisode.DownloadClientEpisodeInfo = downloadClientItem == null ? null : Parser.Parser.ParseTitle(downloadClientItem.Title);
|
||||||
|
localEpisode.DownloadItem = downloadClientItem;
|
||||||
localEpisode.Path = path;
|
localEpisode.Path = path;
|
||||||
localEpisode.SceneSource = SceneSource(series, rootFolder);
|
localEpisode.SceneSource = SceneSource(series, rootFolder);
|
||||||
localEpisode.ExistingFile = series.Path.IsParentPath(path);
|
localEpisode.ExistingFile = series.Path.IsParentPath(path);
|
||||||
|
@ -187,6 +188,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||||
DownloadClientEpisodeInfo = downloadClientItem == null
|
DownloadClientEpisodeInfo = downloadClientItem == null
|
||||||
? null
|
? null
|
||||||
: Parser.Parser.ParseTitle(downloadClientItem.Title),
|
: Parser.Parser.ParseTitle(downloadClientItem.Title),
|
||||||
|
DownloadItem = downloadClientItem,
|
||||||
Path = path,
|
Path = path,
|
||||||
SceneSource = SceneSource(series, rootFolder),
|
SceneSource = SceneSource(series, rootFolder),
|
||||||
ExistingFile = series.Path.IsParentPath(path),
|
ExistingFile = series.Path.IsParentPath(path),
|
||||||
|
@ -479,6 +481,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||||
{
|
{
|
||||||
trackedDownload = _trackedDownloadService.Find(file.DownloadId);
|
trackedDownload = _trackedDownloadService.Find(file.DownloadId);
|
||||||
localEpisode.DownloadClientEpisodeInfo = trackedDownload?.RemoteEpisode?.ParsedEpisodeInfo;
|
localEpisode.DownloadClientEpisodeInfo = trackedDownload?.RemoteEpisode?.ParsedEpisodeInfo;
|
||||||
|
localEpisode.DownloadItem = trackedDownload?.DownloadItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.FolderName.IsNotNullOrWhiteSpace())
|
if (file.FolderName.IsNotNullOrWhiteSpace())
|
||||||
|
|
|
@ -12,6 +12,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
public interface IUpdateMediaInfo
|
public interface IUpdateMediaInfo
|
||||||
{
|
{
|
||||||
bool Update(EpisodeFile episodeFile, Series series);
|
bool Update(EpisodeFile episodeFile, Series series);
|
||||||
|
bool UpdateMediaInfo(EpisodeFile episodeFile, Series series);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UpdateMediaInfoService : IUpdateMediaInfo, IHandle<SeriesScannedEvent>
|
public class UpdateMediaInfoService : IUpdateMediaInfo, IHandle<SeriesScannedEvent>
|
||||||
|
@ -65,7 +66,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
return UpdateMediaInfo(episodeFile, series);
|
return UpdateMediaInfo(episodeFile, series);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool UpdateMediaInfo(EpisodeFile episodeFile, Series series)
|
public bool UpdateMediaInfo(EpisodeFile episodeFile, Series series)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(series.Path, episodeFile.RelativePath);
|
var path = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Processes;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles
|
||||||
|
{
|
||||||
|
public interface IImportScript
|
||||||
|
{
|
||||||
|
public ScriptImportDecision TryImport(string sourcePath, string destinationFilePath, LocalEpisode localEpisode, EpisodeFile episodeFile, TransferMode mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImportScriptService : IImportScript
|
||||||
|
{
|
||||||
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
private readonly IVideoFileInfoReader _videoFileInfoReader;
|
||||||
|
private readonly IProcessProvider _processProvider;
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public ImportScriptService(IProcessProvider processProvider,
|
||||||
|
IVideoFileInfoReader videoFileInfoReader,
|
||||||
|
IConfigService configService,
|
||||||
|
IConfigFileProvider configFileProvider,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_processProvider = processProvider;
|
||||||
|
_videoFileInfoReader = videoFileInfoReader;
|
||||||
|
_configService = configService;
|
||||||
|
_configFileProvider = configFileProvider;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScriptImportDecision TryImport(string sourcePath, string destinationFilePath, LocalEpisode localEpisode, EpisodeFile episodeFile, TransferMode mode)
|
||||||
|
{
|
||||||
|
var series = localEpisode.Series;
|
||||||
|
var oldFiles = localEpisode.OldFiles;
|
||||||
|
var downloadClientInfo = localEpisode.DownloadItem?.DownloadClientInfo;
|
||||||
|
var downloadId = localEpisode.DownloadItem?.DownloadId;
|
||||||
|
|
||||||
|
if (!_configService.UseScriptImport)
|
||||||
|
{
|
||||||
|
return ScriptImportDecision.DeferMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
var environmentVariables = new StringDictionary();
|
||||||
|
|
||||||
|
environmentVariables.Add("Sonarr_SourcePath", sourcePath);
|
||||||
|
environmentVariables.Add("Sonarr_DestinationPath", destinationFilePath);
|
||||||
|
|
||||||
|
environmentVariables.Add("Sonarr_InstanceName", _configFileProvider.InstanceName);
|
||||||
|
environmentVariables.Add("Sonarr_ApplicationUrl", _configService.ApplicationUrl);
|
||||||
|
environmentVariables.Add("Sonarr_TransferMode", mode.ToString());
|
||||||
|
|
||||||
|
environmentVariables.Add("Sonarr_Series_Id", series.Id.ToString());
|
||||||
|
environmentVariables.Add("Sonarr_Series_Title", series.Title);
|
||||||
|
environmentVariables.Add("Sonarr_Series_TitleSlug", series.TitleSlug);
|
||||||
|
environmentVariables.Add("Sonarr_Series_Path", series.Path);
|
||||||
|
environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString());
|
||||||
|
environmentVariables.Add("Sonarr_Series_TvMazeId", series.TvMazeId.ToString());
|
||||||
|
environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId ?? string.Empty);
|
||||||
|
environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString());
|
||||||
|
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeCount", localEpisode.Episodes.Count.ToString());
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeIds", string.Join(",", localEpisode.Episodes.Select(e => e.Id)));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_SeasonNumber", localEpisode.SeasonNumber.ToString());
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeNumbers", string.Join(",", localEpisode.Episodes.Select(e => e.EpisodeNumber)));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeAirDates", string.Join(",", localEpisode.Episodes.Select(e => e.AirDate)));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeAirDatesUtc", string.Join(",", localEpisode.Episodes.Select(e => e.AirDateUtc)));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeTitles", string.Join("|", localEpisode.Episodes.Select(e => e.Title)));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_EpisodeOverviews", string.Join("|", localEpisode.Episodes.Select(e => e.Overview)));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_Quality", localEpisode.Quality.Quality.Name);
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_QualityVersion", localEpisode.Quality.Revision.Version.ToString());
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_ReleaseGroup", localEpisode.ReleaseGroup ?? string.Empty);
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_SceneName", localEpisode.SceneName ?? string.Empty);
|
||||||
|
|
||||||
|
environmentVariables.Add("Sonarr_Download_Client", downloadClientInfo?.Name ?? string.Empty);
|
||||||
|
environmentVariables.Add("Sonarr_Download_Client_Type", downloadClientInfo?.Type ?? string.Empty);
|
||||||
|
environmentVariables.Add("Sonarr_Download_Id", downloadId ?? string.Empty);
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_AudioChannels", MediaInfoFormatter.FormatAudioChannels(localEpisode.MediaInfo).ToString());
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_AudioCodec", MediaInfoFormatter.FormatAudioCodec(localEpisode.MediaInfo, null));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_AudioLanguages", localEpisode.MediaInfo.AudioLanguages.Distinct().ConcatToString(" / "));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_Languages", localEpisode.MediaInfo.AudioLanguages.ConcatToString(" / "));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_Height", localEpisode.MediaInfo.Height.ToString());
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_Width", localEpisode.MediaInfo.Width.ToString());
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_Subtitles", localEpisode.MediaInfo.Subtitles.ConcatToString(" / "));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_VideoCodec", MediaInfoFormatter.FormatVideoCodec(localEpisode.MediaInfo, null));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_MediaInfo_VideoDynamicRangeType", MediaInfoFormatter.FormatVideoDynamicRangeType(localEpisode.MediaInfo));
|
||||||
|
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_CustomFormat", string.Join("|", localEpisode.CustomFormats));
|
||||||
|
environmentVariables.Add("Sonarr_EpisodeFile_CustomFormatScore", localEpisode.CustomFormatScore.ToString());
|
||||||
|
|
||||||
|
if (oldFiles.Any())
|
||||||
|
{
|
||||||
|
environmentVariables.Add("Sonarr_DeletedRelativePaths", string.Join("|", oldFiles.Select(e => e.RelativePath)));
|
||||||
|
environmentVariables.Add("Sonarr_DeletedPaths", string.Join("|", oldFiles.Select(e => Path.Combine(series.Path, e.RelativePath))));
|
||||||
|
environmentVariables.Add("Sonarr_DeletedDateAdded", string.Join("|", oldFiles.Select(e => e.DateAdded)));
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Debug("Executing external script: {0}", _configService.ScriptImportPath);
|
||||||
|
|
||||||
|
var processOutput = _processProvider.StartAndCapture(_configService.ScriptImportPath, $"\"{sourcePath}\" \"{destinationFilePath}\"", environmentVariables);
|
||||||
|
|
||||||
|
_logger.Debug("Executed external script: {0} - Status: {1}", _configService.ScriptImportPath, processOutput.ExitCode);
|
||||||
|
_logger.Debug("Script Output: \r\n{0}", string.Join("\r\n", processOutput.Lines));
|
||||||
|
|
||||||
|
switch (processOutput.ExitCode)
|
||||||
|
{
|
||||||
|
case 0: // Copy complete
|
||||||
|
return ScriptImportDecision.MoveComplete;
|
||||||
|
case 2: // Copy complete, file potentially changed, should try renaming again
|
||||||
|
episodeFile.MediaInfo = _videoFileInfoReader.GetMediaInfo(destinationFilePath);
|
||||||
|
episodeFile.Path = null;
|
||||||
|
return ScriptImportDecision.RenameRequested;
|
||||||
|
case 3: // Let Sonarr handle it
|
||||||
|
return ScriptImportDecision.DeferMove;
|
||||||
|
default: // Error, fail to import
|
||||||
|
throw new ScriptImportException("Moving with script failed! Exit code {0}", processOutput.ExitCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace NzbDrone.Core.MediaFiles
|
||||||
|
{
|
||||||
|
public enum ScriptImportDecision
|
||||||
|
{
|
||||||
|
MoveComplete,
|
||||||
|
RenameRequested,
|
||||||
|
RejectExtra,
|
||||||
|
DeferMove
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles
|
||||||
|
{
|
||||||
|
public class ScriptImportException : NzbDroneException
|
||||||
|
{
|
||||||
|
public ScriptImportException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScriptImportException(string message, params object[] args)
|
||||||
|
: base(message, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScriptImportException(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles
|
namespace NzbDrone.Core.MediaFiles
|
||||||
|
@ -68,6 +69,8 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
_mediaFileService.Delete(file, DeleteMediaFileReason.Upgrade);
|
_mediaFileService.Delete(file, DeleteMediaFileReason.Upgrade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localEpisode.OldFiles = moveFileResult.OldFiles;
|
||||||
|
|
||||||
if (copyOnly)
|
if (copyOnly)
|
||||||
{
|
{
|
||||||
moveFileResult.EpisodeFile = _episodeFileMover.CopyEpisodeFile(episodeFile, localEpisode);
|
moveFileResult.EpisodeFile = _episodeFileMover.CopyEpisodeFile(episodeFile, localEpisode);
|
||||||
|
|
|
@ -2,7 +2,9 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.CustomFormats;
|
using NzbDrone.Core.CustomFormats;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.Languages;
|
using NzbDrone.Core.Languages;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
@ -22,9 +24,11 @@ namespace NzbDrone.Core.Parser.Model
|
||||||
public long Size { get; set; }
|
public long Size { get; set; }
|
||||||
public ParsedEpisodeInfo FileEpisodeInfo { get; set; }
|
public ParsedEpisodeInfo FileEpisodeInfo { get; set; }
|
||||||
public ParsedEpisodeInfo DownloadClientEpisodeInfo { get; set; }
|
public ParsedEpisodeInfo DownloadClientEpisodeInfo { get; set; }
|
||||||
|
public DownloadClientItem DownloadItem { get; set; }
|
||||||
public ParsedEpisodeInfo FolderEpisodeInfo { get; set; }
|
public ParsedEpisodeInfo FolderEpisodeInfo { get; set; }
|
||||||
public Series Series { get; set; }
|
public Series Series { get; set; }
|
||||||
public List<Episode> Episodes { get; set; }
|
public List<Episode> Episodes { get; set; }
|
||||||
|
public List<EpisodeFile> OldFiles { get; set; }
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { get; set; }
|
||||||
public List<Language> Languages { get; set; }
|
public List<Language> Languages { get; set; }
|
||||||
public MediaInfoModel MediaInfo { get; set; }
|
public MediaInfoModel MediaInfo { get; set; }
|
||||||
|
|
|
@ -34,6 +34,8 @@ namespace Sonarr.Api.V3.Config
|
||||||
.SetValidator(seriesPathValidator)
|
.SetValidator(seriesPathValidator)
|
||||||
.When(c => !string.IsNullOrWhiteSpace(c.RecycleBin));
|
.When(c => !string.IsNullOrWhiteSpace(c.RecycleBin));
|
||||||
|
|
||||||
|
SharedValidator.RuleFor(c => c.ScriptImportPath).IsValidPath().When(c => c.UseScriptImport);
|
||||||
|
|
||||||
SharedValidator.RuleFor(c => c.MinimumFreeSpaceWhenImporting).GreaterThanOrEqualTo(100);
|
SharedValidator.RuleFor(c => c.MinimumFreeSpaceWhenImporting).GreaterThanOrEqualTo(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ namespace Sonarr.Api.V3.Config
|
||||||
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
|
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
|
||||||
public int MinimumFreeSpaceWhenImporting { get; set; }
|
public int MinimumFreeSpaceWhenImporting { get; set; }
|
||||||
public bool CopyUsingHardlinks { get; set; }
|
public bool CopyUsingHardlinks { get; set; }
|
||||||
|
public bool UseScriptImport { get; set; }
|
||||||
|
public string ScriptImportPath { get; set; }
|
||||||
public bool ImportExtraFiles { get; set; }
|
public bool ImportExtraFiles { get; set; }
|
||||||
public string ExtraFileExtensions { get; set; }
|
public string ExtraFileExtensions { get; set; }
|
||||||
public bool EnableMediaInfo { get; set; }
|
public bool EnableMediaInfo { get; set; }
|
||||||
|
@ -53,6 +55,8 @@ namespace Sonarr.Api.V3.Config
|
||||||
SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
|
SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
|
||||||
MinimumFreeSpaceWhenImporting = model.MinimumFreeSpaceWhenImporting,
|
MinimumFreeSpaceWhenImporting = model.MinimumFreeSpaceWhenImporting,
|
||||||
CopyUsingHardlinks = model.CopyUsingHardlinks,
|
CopyUsingHardlinks = model.CopyUsingHardlinks,
|
||||||
|
UseScriptImport = model.UseScriptImport,
|
||||||
|
ScriptImportPath = model.ScriptImportPath,
|
||||||
ImportExtraFiles = model.ImportExtraFiles,
|
ImportExtraFiles = model.ImportExtraFiles,
|
||||||
ExtraFileExtensions = model.ExtraFileExtensions,
|
ExtraFileExtensions = model.ExtraFileExtensions,
|
||||||
EnableMediaInfo = model.EnableMediaInfo
|
EnableMediaInfo = model.EnableMediaInfo
|
||||||
|
|
|
@ -9177,6 +9177,12 @@
|
||||||
"copyUsingHardlinks": {
|
"copyUsingHardlinks": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"useScriptImport": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"scriptImportPath": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"importExtraFiles": {
|
"importExtraFiles": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue