More xbmc metadata improvements
New: Create/update episode metadata when series is refreshed Fixed: Episode Metadata when screenshot is not available Fixed: Episode metadata being stored in database incorrectly Fixed: Do not create metadata when series folder does not exist
This commit is contained in:
parent
1dec725941
commit
cbd8e98677
|
@ -0,0 +1,27 @@
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(44)]
|
||||||
|
public class fix_xbmc_episode_metadata : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
//Convert Episode Metadata to proper type
|
||||||
|
Execute.Sql("UPDATE MetadataFiles " +
|
||||||
|
"SET Type = 2 " +
|
||||||
|
"WHERE Consumer = 'XbmcMetadata' " +
|
||||||
|
"AND EpisodeFileId IS NOT NULL " +
|
||||||
|
"AND Type = 4 " +
|
||||||
|
"AND RelativePath LIKE '%.nfo'");
|
||||||
|
|
||||||
|
//Convert Episode Images to proper type
|
||||||
|
Execute.Sql("UPDATE MetadataFiles " +
|
||||||
|
"SET Type = 5 " +
|
||||||
|
"WHERE Consumer = 'XbmcMetadata' " +
|
||||||
|
"AND EpisodeFileId IS NOT NULL " +
|
||||||
|
"AND Type = 4");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ using System.Xml.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.MediaCover;
|
using NzbDrone.Core.MediaCover;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
@ -25,6 +26,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
private readonly IMetadataFileService _metadataFileService;
|
private readonly IMetadataFileService _metadataFileService;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly IHttpProvider _httpProvider;
|
private readonly IHttpProvider _httpProvider;
|
||||||
|
private readonly IEpisodeService _episodeService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public XbmcMetadata(IEventAggregator eventAggregator,
|
public XbmcMetadata(IEventAggregator eventAggregator,
|
||||||
|
@ -33,6 +35,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
IMetadataFileService metadataFileService,
|
IMetadataFileService metadataFileService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IHttpProvider httpProvider,
|
IHttpProvider httpProvider,
|
||||||
|
IEpisodeService episodeService,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(diskProvider, httpProvider, logger)
|
: base(diskProvider, httpProvider, logger)
|
||||||
{
|
{
|
||||||
|
@ -42,6 +45,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
_metadataFileService = metadataFileService;
|
_metadataFileService = metadataFileService;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_httpProvider = httpProvider;
|
_httpProvider = httpProvider;
|
||||||
|
_episodeService = episodeService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,40 +55,63 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
|
|
||||||
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles)
|
public override void OnSeriesUpdated(Series series, List<MetadataFile> existingMetadataFiles)
|
||||||
{
|
{
|
||||||
|
if (!_diskProvider.FolderExists(series.Path))
|
||||||
|
{
|
||||||
|
_logger.Info("Series folder does not exist, skipping metadata creation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Settings.SeriesMetadata)
|
if (Settings.SeriesMetadata)
|
||||||
{
|
{
|
||||||
EnsureFolder(series.Path);
|
|
||||||
WriteTvShowNfo(series, existingMetadataFiles);
|
WriteTvShowNfo(series, existingMetadataFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.SeriesImages)
|
if (Settings.SeriesImages)
|
||||||
{
|
{
|
||||||
EnsureFolder(series.Path);
|
|
||||||
WriteSeriesImages(series, existingMetadataFiles);
|
WriteSeriesImages(series, existingMetadataFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.SeasonImages)
|
if (Settings.SeasonImages)
|
||||||
{
|
{
|
||||||
EnsureFolder(series.Path);
|
|
||||||
WriteSeasonImages(series, existingMetadataFiles);
|
WriteSeasonImages(series, existingMetadataFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var episodeFiles = GetEpisodeFiles(series.Id);
|
||||||
|
|
||||||
|
foreach (var episodeFile in episodeFiles)
|
||||||
|
{
|
||||||
|
if (Settings.EpisodeMetadata)
|
||||||
|
{
|
||||||
|
WriteEpisodeNfo(series, episodeFile, existingMetadataFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var episodeFile in episodeFiles)
|
||||||
|
{
|
||||||
|
if (Settings.EpisodeImages)
|
||||||
|
{
|
||||||
|
WriteEpisodeImages(series, episodeFile, existingMetadataFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
|
public override void OnEpisodeImport(Series series, EpisodeFile episodeFile, bool newDownload)
|
||||||
{
|
{
|
||||||
if (Settings.EpisodeMetadata)
|
if (Settings.EpisodeMetadata)
|
||||||
{
|
{
|
||||||
WriteEpisodeNfo(series, episodeFile);
|
WriteEpisodeNfo(series, episodeFile, new List<MetadataFile>());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.EpisodeImages)
|
if (Settings.EpisodeImages)
|
||||||
{
|
{
|
||||||
WriteEpisodeImages(series, episodeFile);
|
WriteEpisodeImages(series, episodeFile, new List<MetadataFile>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void AfterRename(Series series)
|
public override void AfterRename(Series series)
|
||||||
{
|
{
|
||||||
|
//TODO: This should be part of the base class, but could be overwritten if the logic needs to be different
|
||||||
|
//or it could be done in MetadataService instead of having each metadata consumer do it
|
||||||
var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id);
|
var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id);
|
||||||
var episodeFilesMetadata = _metadataFileService.GetFilesBySeries(series.Id).Where(c => c.EpisodeFileId > 0).ToList();
|
var episodeFilesMetadata = _metadataFileService.GetFilesBySeries(series.Id).Where(c => c.EpisodeFileId > 0).ToList();
|
||||||
|
|
||||||
|
@ -305,7 +332,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteEpisodeNfo(Series series, EpisodeFile episodeFile)
|
private void WriteEpisodeNfo(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
|
||||||
{
|
{
|
||||||
var filename = episodeFile.Path.Replace(Path.GetExtension(episodeFile.Path), ".nfo");
|
var filename = episodeFile.Path.Replace(Path.GetExtension(episodeFile.Path), ".nfo");
|
||||||
|
|
||||||
|
@ -322,6 +349,7 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
using (var xw = XmlWriter.Create(sb, xws))
|
using (var xw = XmlWriter.Create(sb, xws))
|
||||||
{
|
{
|
||||||
var doc = new XDocument();
|
var doc = new XDocument();
|
||||||
|
var image = episode.Images.SingleOrDefault(i => i.CoverType == MediaCoverTypes.Screenshot);
|
||||||
|
|
||||||
var details = new XElement("episodedetails");
|
var details = new XElement("episodedetails");
|
||||||
details.Add(new XElement("title", episode.Title));
|
details.Add(new XElement("title", episode.Title));
|
||||||
|
@ -334,7 +362,16 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
details.Add(new XElement("displayseason"));
|
details.Add(new XElement("displayseason"));
|
||||||
details.Add(new XElement("displayepisode"));
|
details.Add(new XElement("displayepisode"));
|
||||||
|
|
||||||
details.Add(new XElement("thumb", episode.Images.Single(i => i.CoverType == MediaCoverTypes.Screenshot).Url));
|
if (image == null)
|
||||||
|
{
|
||||||
|
details.Add(new XElement("thumb"));
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
details.Add(new XElement("thumb", image.Url));
|
||||||
|
}
|
||||||
|
|
||||||
details.Add(new XElement("watched", "false"));
|
details.Add(new XElement("watched", "false"));
|
||||||
details.Add(new XElement("rating", episode.Ratings.Percentage));
|
details.Add(new XElement("rating", episode.Ratings.Percentage));
|
||||||
|
|
||||||
|
@ -353,19 +390,21 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
_logger.Debug("Saving episodedetails to: {0}", filename);
|
_logger.Debug("Saving episodedetails to: {0}", filename);
|
||||||
_diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
|
_diskProvider.WriteAllText(filename, xmlResult.Trim(Environment.NewLine.ToCharArray()));
|
||||||
|
|
||||||
var metadata = new MetadataFile
|
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeMetadata &&
|
||||||
|
c.EpisodeFileId == episodeFile.Id) ??
|
||||||
|
new MetadataFile
|
||||||
{
|
{
|
||||||
SeriesId = series.Id,
|
SeriesId = series.Id,
|
||||||
EpisodeFileId = episodeFile.Id,
|
EpisodeFileId = episodeFile.Id,
|
||||||
Consumer = GetType().Name,
|
Consumer = GetType().Name,
|
||||||
Type = MetadataType.SeasonImage,
|
Type = MetadataType.EpisodeMetadata,
|
||||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
||||||
};
|
};
|
||||||
|
|
||||||
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
|
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteEpisodeImages(Series series, EpisodeFile episodeFile)
|
private void WriteEpisodeImages(Series series, EpisodeFile episodeFile, List<MetadataFile> existingMetadataFiles)
|
||||||
{
|
{
|
||||||
var screenshot = episodeFile.Episodes.Value.First().Images.Single(i => i.CoverType == MediaCoverTypes.Screenshot);
|
var screenshot = episodeFile.Episodes.Value.First().Images.Single(i => i.CoverType == MediaCoverTypes.Screenshot);
|
||||||
|
|
||||||
|
@ -373,16 +412,32 @@ namespace NzbDrone.Core.Metadata.Consumers.Xbmc
|
||||||
|
|
||||||
DownloadImage(series, screenshot.Url, filename);
|
DownloadImage(series, screenshot.Url, filename);
|
||||||
|
|
||||||
var metadata = new MetadataFile
|
var metadata = existingMetadataFiles.SingleOrDefault(c => c.Type == MetadataType.EpisodeImage &&
|
||||||
|
c.EpisodeFileId == episodeFile.Id) ??
|
||||||
|
new MetadataFile
|
||||||
{
|
{
|
||||||
SeriesId = series.Id,
|
SeriesId = series.Id,
|
||||||
EpisodeFileId = episodeFile.Id,
|
EpisodeFileId = episodeFile.Id,
|
||||||
Consumer = GetType().Name,
|
Consumer = GetType().Name,
|
||||||
Type = MetadataType.SeasonImage,
|
Type = MetadataType.EpisodeImage,
|
||||||
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
RelativePath = DiskProviderBase.GetRelativePath(series.Path, filename)
|
||||||
};
|
};
|
||||||
|
|
||||||
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
|
_eventAggregator.PublishEvent(new MetadataFileUpdated(metadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<EpisodeFile> GetEpisodeFiles(int seriesId)
|
||||||
|
{
|
||||||
|
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
|
||||||
|
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
|
||||||
|
|
||||||
|
foreach (var episodeFile in episodeFiles)
|
||||||
|
{
|
||||||
|
var localEpisodeFile = episodeFile;
|
||||||
|
episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return episodeFiles;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Metadata.Files
|
||||||
|
{
|
||||||
|
public interface ICleanMetadataService
|
||||||
|
{
|
||||||
|
void Clean(Series series);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CleanMetadataService : ICleanMetadataService
|
||||||
|
{
|
||||||
|
private readonly IMetadataFileService _metadataFileService;
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public CleanMetadataService(IMetadataFileService metadataFileService,
|
||||||
|
IDiskProvider diskProvider,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_metadataFileService = metadataFileService;
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clean(Series series)
|
||||||
|
{
|
||||||
|
_logger.Trace("Cleaning missing metadata files for series: {0}", series.Title);
|
||||||
|
|
||||||
|
var metadataFiles = _metadataFileService.GetFilesBySeries(series.Id);
|
||||||
|
|
||||||
|
foreach (var metadataFile in metadataFiles)
|
||||||
|
{
|
||||||
|
if (!_diskProvider.FileExists(Path.Combine(series.Path, metadataFile.RelativePath)))
|
||||||
|
{
|
||||||
|
_logger.Trace("Deleting metadata file from database: {0}", metadataFile.RelativePath);
|
||||||
|
_metadataFileService.Delete(metadataFile.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,24 +7,30 @@ using NzbDrone.Core.Metadata.Files;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Metadata
|
namespace NzbDrone.Core.Metadata
|
||||||
{
|
{
|
||||||
public class NotificationService
|
public class MetadataService
|
||||||
: IHandle<MediaCoversUpdatedEvent>,
|
: IHandle<MediaCoversUpdatedEvent>,
|
||||||
IHandle<EpisodeImportedEvent>,
|
IHandle<EpisodeImportedEvent>,
|
||||||
IHandle<SeriesRenamedEvent>
|
IHandle<SeriesRenamedEvent>
|
||||||
{
|
{
|
||||||
private readonly IMetadataFactory _metadataFactory;
|
private readonly IMetadataFactory _metadataFactory;
|
||||||
private readonly IMetadataFileService _metadataFileService;
|
private readonly IMetadataFileService _metadataFileService;
|
||||||
|
private readonly ICleanMetadataService _cleanMetadataService;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public NotificationService(IMetadataFactory metadataFactory, IMetadataFileService metadataFileService, Logger logger)
|
public MetadataService(IMetadataFactory metadataFactory,
|
||||||
|
IMetadataFileService metadataFileService,
|
||||||
|
ICleanMetadataService cleanMetadataService,
|
||||||
|
Logger logger)
|
||||||
{
|
{
|
||||||
_metadataFactory = metadataFactory;
|
_metadataFactory = metadataFactory;
|
||||||
_metadataFileService = metadataFileService;
|
_metadataFileService = metadataFileService;
|
||||||
|
_cleanMetadataService = cleanMetadataService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(MediaCoversUpdatedEvent message)
|
public void Handle(MediaCoversUpdatedEvent message)
|
||||||
{
|
{
|
||||||
|
_cleanMetadataService.Clean(message.Series);
|
||||||
var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
var seriesMetadata = _metadataFileService.GetFilesBySeries(message.Series.Id);
|
||||||
|
|
||||||
foreach (var consumer in _metadataFactory.Enabled())
|
foreach (var consumer in _metadataFactory.Enabled())
|
||||||
|
|
|
@ -19,6 +19,7 @@ namespace NzbDrone.Core.Metadata.Files
|
||||||
MetadataFile FindByPath(string path);
|
MetadataFile FindByPath(string path);
|
||||||
List<string> FilterExistingFiles(List<string> files, Series series);
|
List<string> FilterExistingFiles(List<string> files, Series series);
|
||||||
MetadataFile Upsert(MetadataFile metadataFile);
|
MetadataFile Upsert(MetadataFile metadataFile);
|
||||||
|
void Delete(int id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MetadataFileService : IMetadataFileService,
|
public class MetadataFileService : IMetadataFileService,
|
||||||
|
@ -72,6 +73,11 @@ namespace NzbDrone.Core.Metadata.Files
|
||||||
return _repository.Upsert(metadataFile);
|
return _repository.Upsert(metadataFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Delete(int id)
|
||||||
|
{
|
||||||
|
_repository.Delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
public void HandleAsync(SeriesDeletedEvent message)
|
public void HandleAsync(SeriesDeletedEvent message)
|
||||||
{
|
{
|
||||||
_logger.Trace("Deleting Metadata from database for series: {0}", message.Series);
|
_logger.Trace("Deleting Metadata from database for series: {0}", message.Series);
|
||||||
|
|
|
@ -55,11 +55,6 @@ namespace NzbDrone.Core.Metadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void EnsureFolder(string path)
|
|
||||||
{
|
|
||||||
_diskProvider.CreateFolder(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void DownloadImage(Series series, string url, string path)
|
protected virtual void DownloadImage(Series series, string url, string path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
@ -200,6 +200,9 @@
|
||||||
<Compile Include="Datastore\Migration\041_fix_xbmc_season_images_metadata.cs" />
|
<Compile Include="Datastore\Migration\041_fix_xbmc_season_images_metadata.cs" />
|
||||||
<Compile Include="Datastore\Migration\042_add_download_clients_table.cs" />
|
<Compile Include="Datastore\Migration\042_add_download_clients_table.cs" />
|
||||||
<Compile Include="Datastore\Migration\043_convert_config_to_download_clients.cs" />
|
<Compile Include="Datastore\Migration\043_convert_config_to_download_clients.cs" />
|
||||||
|
<Compile Include="Datastore\Migration\044_fix_xbmc_episode_metadata.cs">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationExtension.cs" />
|
||||||
|
@ -334,6 +337,7 @@
|
||||||
<Compile Include="MetadataSource\Trakt\Actor.cs" />
|
<Compile Include="MetadataSource\Trakt\Actor.cs" />
|
||||||
<Compile Include="MetadataSource\Trakt\People.cs" />
|
<Compile Include="MetadataSource\Trakt\People.cs" />
|
||||||
<Compile Include="MetadataSource\Trakt\Ratings.cs" />
|
<Compile Include="MetadataSource\Trakt\Ratings.cs" />
|
||||||
|
<Compile Include="Metadata\Files\CleanMetadataService.cs" />
|
||||||
<Compile Include="Metadata\Consumers\Fake\Fake.cs" />
|
<Compile Include="Metadata\Consumers\Fake\Fake.cs" />
|
||||||
<Compile Include="Metadata\Consumers\Fake\FakeSettings.cs" />
|
<Compile Include="Metadata\Consumers\Fake\FakeSettings.cs" />
|
||||||
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadata.cs" />
|
<Compile Include="Metadata\Consumers\Xbmc\XbmcMetadata.cs" />
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body root-folders-modal">
|
<div class="modal-body root-folders-modal">
|
||||||
<div class="validation-errors"></div>
|
<div class="validation-errors"></div>
|
||||||
|
<div class="alert alert-info">Enter the path that contains some or all of your TV series, you will be able to choose which series you want to import<button type="button" class="close" data-dismiss="alert">×</button></div>
|
||||||
<div class="input-prepend input-append x-path control-group">
|
<div class="input-prepend input-append x-path control-group">
|
||||||
<span class="add-on"> <i class="icon-folder-open"></i></span>
|
<span class="add-on"> <i class="icon-folder-open"></i></span>
|
||||||
<input class="span9" type="text" validation-name="path" placeholder="Enter path to folder that contains your shows">
|
<input class="span9" type="text" validation-name="path" placeholder="Enter path to folder that contains your shows">
|
||||||
|
|
Loading…
Reference in New Issue