Example #1
0
        public AutoTagSettings(string configPath)
        {
            this.configPath = configPath;
            if (File.Exists(configPath))
            {
                try {
                    config = JsonSerializer.Deserialize <AutoTagConfig>(File.ReadAllText(configPath));
                } catch (JsonException) {
                    Console.Error.WriteLine($"Error parsing config file '{configPath}'");
                } catch (UnauthorizedAccessException) {
                    Console.Error.WriteLine($"Config file '{configPath}' not readable");
                } catch (Exception e) {
                    Console.Error.WriteLine($"Error reading config file '{configPath}': {e.Message}");
                }

                if (config.configVer != AutoTagConfig.currentVer)
                {
                    if (config.configVer < 5 && config.tvRenamePattern == "%1 - %2x%3 - %4")
                    {
                        config.tvRenamePattern = "%1 - %2x%3:00 - %4";
                    }

                    // if config file outdated, update it with new options
                    config.configVer = AutoTagConfig.currentVer;
                    Save();
                }
            }
            else
            {
                Console.WriteLine($"Generating new config file with default options: '{configPath}'");
                FileInfo configFile = new FileInfo(configPath);
                configFile.Directory.Create();

                try {
                    File.WriteAllText(configPath, JsonSerializer.Serialize <AutoTagConfig>(new AutoTagConfig(), jsonOptions));
                } catch (UnauthorizedAccessException) {
                    Console.Error.WriteLine($"Config file '{configPath}'not writeable");
                } catch (Exception e) {
                    Console.Error.WriteLine($"Error writing config file '{configPath}': {e.Message}");
                }

                try {
                    config = JsonSerializer.Deserialize <AutoTagConfig>(File.ReadAllText(configPath));
                } catch (JsonException) {
                    Console.Error.WriteLine($"Error parsing config file '{configPath}'");
                } catch (UnauthorizedAccessException) {
                    Console.Error.WriteLine($"Config file '{configPath}' not readable");
                } catch (Exception e) {
                    Console.Error.WriteLine($"Error reading config file '{configPath}': {e.Message}");
                }
            }
        }
Example #2
0
        public static async Task <bool> Write(string filePath, FileMetadata metadata, Action <string> setPath, Action <string, MessageType> setStatus, AutoTagConfig config)
        {
            bool fileSuccess = true;

            if (config.tagFiles)
            {
                if (invalidFilenameChars == null)
                {
                    invalidFilenameChars = Path.GetInvalidFileNameChars();

                    if (config.windowsSafe)
                    {
                        invalidFilenameChars = invalidFilenameChars.Union(invalidNtfsChars).ToArray();
                    }
                }

                TagLib.File file = null;
                try {
                    file = TagLib.File.Create(filePath);

                    file.Tag.Title       = metadata.Title;
                    file.Tag.Description = metadata.Overview;

                    if (metadata.Genres != null && metadata.Genres.Any())
                    {
                        file.Tag.Genres = metadata.Genres;
                    }

                    if (config.extendedTagging && file.MimeType == "video/x-matroska")
                    {
                        if (metadata.FileType == FileMetadata.Types.TV && metadata.Id.HasValue)
                        {
                            var custom = (TagLib.Matroska.Tag)file.GetTag(TagLib.TagTypes.Matroska);
                            custom.Set("TMDB", "", $"tv/{metadata.Id}");
                        }

                        file.Tag.Conductor      = metadata.Director;
                        file.Tag.Performers     = metadata.Actors;
                        file.Tag.PerformersRole = metadata.Characters;
                    }

                    if (metadata.FileType == FileMetadata.Types.TV)
                    {
                        file.Tag.Album      = metadata.SeriesName;
                        file.Tag.Disc       = (uint)metadata.Season;
                        file.Tag.Track      = (uint)metadata.Episode;
                        file.Tag.TrackCount = (uint)metadata.SeasonEpisodes;
                    }
                    else
                    {
                        file.Tag.Year = (uint)metadata.Date.Year;
                    }

                    if (!string.IsNullOrEmpty(metadata.CoverFilename) && config.addCoverArt == true)   // if there is an image available and cover art is enabled
                    {
                        if (!_images.ContainsKey(metadata.CoverFilename))
                        {
                            var response = await _client.GetAsync(metadata.CoverURL, HttpCompletionOption.ResponseHeadersRead);

                            if (response.IsSuccessStatusCode)
                            {
                                _images[metadata.CoverFilename] = await response.Content.ReadAsByteArrayAsync();
                            }
                            else
                            {
                                if (config.verbose)
                                {
                                    setStatus($"Error: failed to download cover art ({(int) response.StatusCode}:{metadata.CoverURL})", MessageType.Error);
                                }
                                else
                                {
                                    setStatus($"Error: failed to download cover art", MessageType.Error);
                                }
                                fileSuccess = false;
                            }
                        }

                        if (_images.TryGetValue(metadata.CoverFilename, out byte[] imgBytes))
Example #3
0
        public async Task <bool> process(
            string filePath,
            Action <string> setPath,
            Action <string, MessageType> setStatus,
            Func <List <Tuple <string, string> >, int> selectResult,
            AutoTagConfig config
            )
        {
            if (tvdb.Authentication.Token == null)
            {
                await tvdb.Authentication.AuthenticateAsync(apiKey);
            }

            FileMetadata result = new FileMetadata(FileMetadata.Types.TV);

            #region Filename parsing
            FileMetadata episodeData;

            if (string.IsNullOrEmpty(config.parsePattern))
            {
                try {
                    episodeData = EpisodeParser.ParseEpisodeInfo(Path.GetFileName(filePath)); // Parse info from filename
                } catch (FormatException ex) {
                    setStatus($"Error: {ex.Message}", MessageType.Error);
                    return(false);
                }
            }
            else
            {
                try {
                    var match = Regex.Match(Path.GetFullPath(filePath), config.parsePattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

                    episodeData            = new FileMetadata(FileMetadata.Types.TV);
                    episodeData.SeriesName = match.Groups["SeriesName"].Value;
                    episodeData.Season     = int.Parse(match.Groups["Season"].Value);
                    episodeData.Episode    = int.Parse(match.Groups["Episode"].Value);
                } catch (FormatException ex) {
                    if (config.verbose)
                    {
                        setStatus($"Error: Unable to parse required information from filename ({ex.GetType().Name}: {ex.Message})", MessageType.Error);
                    }
                    else
                    {
                        setStatus($"Error: Unable to parse required information from filename", MessageType.Error);
                    }
                    return(false);
                }
            }

            result.Season  = episodeData.Season;
            result.Episode = episodeData.Episode;

            setStatus($"Parsed file as {episodeData}", MessageType.Information);
            #endregion

            #region TVDB API searching
            if (!seriesResultCache.ContainsKey(episodeData.SeriesName))   // if not already searched for series
            {
                TvDbResponse <SeriesSearchResult[]> seriesIdResponse;
                try {
                    seriesIdResponse = await tvdb.Search.SearchSeriesByNameAsync(episodeData.SeriesName);
                } catch (TvDbServerException ex) {
                    if (config.verbose)
                    {
                        setStatus($"Error: Cannot find series {episodeData.SeriesName} ({ex.GetType().Name}: {ex.Message})", MessageType.Error);
                    }
                    else
                    {
                        setStatus($"Error: Cannot find series {episodeData.SeriesName} on TheTVDB", MessageType.Error);
                    }
                    return(false);
                }

                // sort results by similarity to parsed series name
                List <SeriesSearchResult> seriesResults = seriesIdResponse.Data
                                                          .OrderByDescending(seriesResult => SeriesNameSimilarity(episodeData.SeriesName, seriesResult.SeriesName))
                                                          .ToList();

                if (config.manualMode && seriesResults.Count > 1)
                {
                    int chosen = selectResult(seriesResults.Select(
                                                  r => new Tuple <string, string>(r.SeriesName, r.FirstAired ?? "Unknown")
                                                  ).ToList()
                                              );

                    // add only the chosen series to cache if in manual mode
                    seriesResultCache.Add(episodeData.SeriesName, new List <SeriesSearchResult> {
                        seriesResults[chosen]
                    });
                }
                else
                {
                    seriesResultCache.Add(episodeData.SeriesName, seriesResults);
                }
            }

            // try searching for each series search result
            foreach (var series in seriesResultCache[episodeData.SeriesName])
            {
                result.Id         = series.Id;
                result.SeriesName = series.SeriesName;

                try {
                    TvDbResponse <EpisodeRecord[]> episodeResponse = await tvdb.Series.GetEpisodesAsync(series.Id, 1,
                                                                                                        new EpisodeQuery {
                        AiredSeason  = episodeData.Season,
                        AiredEpisode = episodeData.Episode
                    }
                                                                                                        );

                    result.Title    = episodeResponse.Data[0].EpisodeName;
                    result.Overview = episodeResponse.Data[0].Overview;

                    break;
                } catch (TvDbServerException ex) {
                    if (series.Id == seriesResultCache[episodeData.SeriesName].Last().Id)
                    {
                        if (config.verbose)
                        {
                            setStatus($"Error: Cannot find {episodeData} ({ex.GetType().Name}: {ex.Message})", MessageType.Error);
                        }
                        else
                        {
                            setStatus($"Error: Cannot find {episodeData} on TheTVDB", MessageType.Error);
                        }
                        return(false);
                    }
                }
            }

            setStatus($"Found {episodeData} ({result.Title}) on TheTVDB", MessageType.Information);

            TvDbResponse <TvDbSharper.Dto.Image[]> imagesResponse = null;

            if (config.addCoverArt)
            {
                try {
                    imagesResponse = await tvdb.Series.GetImagesAsync(result.Id, new ImagesQuery {
                        KeyType = KeyType.Season,
                        SubKey  = episodeData.Season.ToString()
                    });
                } catch (TvDbServerException) {
                    try {
                        // use a series image if a season-specific image is not available
                        imagesResponse = await tvdb.Series.GetImagesAsync(result.Id, new ImagesQuery {
                            KeyType = KeyType.Series
                        });
                    } catch (TvDbServerException ex) {
                        if (config.verbose)
                        {
                            setStatus($"Error: Failed to find episode cover ({ex.GetType().Name}: {ex.Message})", MessageType.Error);
                        }
                        else
                        {
                            setStatus("Error: Failed to find episode cover", MessageType.Error);
                        }
                        result.Complete = false;
                    }
                }
            }

            string imageFilename = "";
            if (imagesResponse != null)
            {
                imageFilename = imagesResponse.Data
                                .OrderByDescending(img => img.RatingsInfo.Average)
                                .First().FileName; // Find highest rated image
            }
            #endregion

            result.CoverURL = (String.IsNullOrEmpty(imageFilename)) ? null : $"https://artworks.thetvdb.com/banners/{imageFilename}";

            result.CoverFilename = imageFilename.Split('/').Last();

            bool taggingSuccess = FileWriter.write(filePath, result, setPath, setStatus, config);

            return(taggingSuccess && result.Success && result.Complete);
        }
Example #4
0
        public async Task <bool> process(
            string filePath,
            Action <string> setPath,
            Action <string, MessageType> setStatus,
            Func <List <Tuple <string, string> >, int> selectResult,
            AutoTagConfig config
            )
        {
            FileMetadata result = new FileMetadata(FileMetadata.Types.Movie);

            #region "Filename parsing"
            string pattern =
                "^((?<Title>.+?)[\\. _-]?)" +                                                                                                                                                                // get title by reading from start to a field (whichever field comes first)
                "?(" +
                "([\\(]?(?<Year>(19|20)[0-9]{2})[\\)]?)|" +                                                                                                                                                  // year - extract for use in searching
                "([0-9]{3,4}(p|i))|" +                                                                                                                                                                       // resolution (e.g. 1080p, 720i)
                "((?:PPV\\.)?[HPS]DTV|[. ](?:HD)?CAM[| ]|B[DR]Rip|[.| ](?:HD-?)?TS[.| ]|(?:PPV )?WEB-?DL(?: DVDRip)?|HDRip|DVDRip|CamRip|W[EB]Rip|BluRay|DvDScr|hdtv|REMUX|3D|Half-(OU|SBS)+|4K|NF|AMZN)|" + // rip type
                "(xvid|[hx]\\.?26[45]|AVC)|" +                                                                                                                                                               // video codec
                "(MP3|DD5\\.?1|Dual[\\- ]Audio|LiNE|DTS[-HD]+|AAC[.-]LC|AAC(?:\\.?2\\.0)?|AC3(?:\\.5\\.1)?|7\\.1|DDP5.1)|" +                                                                                 // audio codec
                "(REPACK|INTERNAL|PROPER)|" +                                                                                                                                                                // scene tags
                "\\.(mp4|m4v|mkv)$" +                                                                                                                                                                        // file extensions
                ")";

            Match  match = Regex.Match(Path.GetFileName(filePath), pattern);
            string title, year;
            if (match.Success)
            {
                title = match.Groups["Title"].ToString();
                year  = match.Groups["Year"].ToString();
            }
            else
            {
                setStatus("Error: Failed to parse required information from filename", MessageType.Error);
                return(false);
            }

            title = title.Replace('.', ' '); // change dots to spaces

            if (String.IsNullOrWhiteSpace(title))
            {
                setStatus("Error: Failed to parse required information from filename", MessageType.Error);
                return(false);
            }

            setStatus($"Parsed file as {title}", MessageType.Information);
            #endregion

            #region "TMDB API Searching"
            SearchContainer <SearchMovie> searchResults;
            if (!String.IsNullOrWhiteSpace(year))
            {
                searchResults = await tmdb.SearchMovieAsync(query : title, year : int.Parse(year)); // if year was parsed, use it to narrow down search further
            }
            else
            {
                searchResults = await tmdb.SearchMovieAsync(query : title);
            }

            int selected = 0;

            if (searchResults.Results.Count > 1 && (searchResults.Results[0].Title != title || config.manualMode))
            {
                selected = selectResult(
                    searchResults.Results
                    .Select(m => new Tuple <string, string>(
                                m.Title,
                                m.ReleaseDate == null ? "Unknown" : m.ReleaseDate.Value.Year.ToString()
                                )).ToList()
                    );
            }
            else if (searchResults.Results.Count == 0)
            {
                setStatus($"Error: failed to find title {title} on TheMovieDB", MessageType.Error);
                result.Success = false;
                return(false);
            }

            SearchMovie selectedResult = searchResults.Results[selected];

            setStatus($"Found {selectedResult.Title} ({selectedResult.ReleaseDate.Value.Year}) on TheMovieDB", MessageType.Information);
            #endregion

            result.Title         = selectedResult.Title;
            result.Overview      = selectedResult.Overview;
            result.CoverURL      = (String.IsNullOrEmpty(selectedResult.PosterPath)) ? null : $"https://image.tmdb.org/t/p/original{selectedResult.PosterPath}";
            result.CoverFilename = selectedResult.PosterPath.Replace("/", "");
            result.Date          = selectedResult.ReleaseDate.Value;

            if (String.IsNullOrEmpty(result.CoverURL))
            {
                setStatus("Error: failed to fetch movie cover", MessageType.Error);
                result.Complete = false;
            }

            bool taggingSuccess = FileWriter.write(filePath, result, setPath, setStatus, config);

            return(taggingSuccess && result.Success && result.Complete);
        }
Example #5
0
        public static bool write(string filePath, FileMetadata metadata, Action <string> setPath, Action <string, MessageType> setStatus, AutoTagConfig config)
        {
            bool fileSuccess = true;

            if (config.tagFiles)
            {
                if (invalidFilenameChars == null)
                {
                    invalidFilenameChars = Path.GetInvalidFileNameChars();

                    if (config.windowsSafe)
                    {
                        invalidFilenameChars = invalidFilenameChars.Union(invalidNtfsChars).ToArray();
                    }
                }

                try {
                    TagLib.File file = TagLib.File.Create(filePath);

                    file.Tag.Title   = metadata.Title;
                    file.Tag.Comment = metadata.Overview;
                    file.Tag.Genres  = new string[] { (metadata.FileType == FileMetadata.Types.TV) ? "TVShows" : "Movie" };

                    if (metadata.FileType == FileMetadata.Types.TV)
                    {
                        file.Tag.Album = metadata.SeriesName;
                        file.Tag.Disc  = (uint)metadata.Season;
                        file.Tag.Track = (uint)metadata.Episode;
                    }
                    else
                    {
                        file.Tag.Year = (uint)metadata.Date.Year;
                    }

                    if (metadata.CoverFilename != "" && config.addCoverArt == true)   // if there is an image available and cover art is enabled
                    {
                        string downloadPath = Path.Combine(Path.GetTempPath(), "autotag");
                        string downloadFile = Path.Combine(downloadPath, metadata.CoverFilename);

                        if (!File.Exists(downloadFile))   // only download file if it hasn't already been downloaded
                        {
                            if (!Directory.Exists(downloadPath))
                            {
                                Directory.CreateDirectory(downloadPath); // create temp directory
                            }

                            try {
                                using (WebClient client = new WebClient()) {
                                    client.DownloadFile(metadata.CoverURL, downloadFile); // download image
                                }
                                file.Tag.Pictures = new TagLib.Picture[] { new TagLib.Picture(downloadFile)
                                                                           {
                                                                               Filename = "cover.jpg"
                                                                           } };
                            } catch (WebException ex) {
                                if (config.verbose)
                                {
                                    setStatus($"Error: Failed to download cover art ({ex.GetType().Name}: {ex.Message})", MessageType.Error);
                                }
                                else
                                {
                                    setStatus("Error: Failed to download cover art", MessageType.Error);
                                }
                                fileSuccess = false;
                            }
                        }
                        else
                        {
                            // overwrite default file name - allows software such as Icaros to display cover art thumbnails - default isn't compliant with Matroska guidelines
                            file.Tag.Pictures = new TagLib.Picture[] { new TagLib.Picture(downloadFile)
                                                                       {
                                                                           Filename = "cover.jpg"
                                                                       } };
                        }
                    }
                    else if (String.IsNullOrEmpty(metadata.CoverFilename) && config.addCoverArt == true)
                    {
                        fileSuccess = false;
                    }

                    file.Save();

                    if (fileSuccess == true)
                    {
                        setStatus($"Successfully tagged file as {metadata}", MessageType.Information);
                    }
                } catch (Exception ex) {
                    if (config.verbose)
                    {
                        setStatus($"Error: Failed to write tags to file ({ex.GetType().Name}: {ex.Message})", MessageType.Error);
                    }
                    else
                    {
                        setStatus("Error: Failed to write tags to file", MessageType.Error);
                    }
                    fileSuccess = false;
                }
            }

            if (config.renameFiles)
            {
                string newPath;
                if (config.mode == 0)
                {
                    newPath = Path.Combine(
                        Path.GetDirectoryName(filePath),
                        EscapeFilename(
                            String.Format(
                                GetTVRenamePattern(config),
                                metadata.SeriesName,
                                metadata.Season,
                                metadata.Episode.ToString("00"),
                                metadata.Title
                                ),
                            Path.GetFileNameWithoutExtension(filePath),
                            setStatus
                            )
                        + Path.GetExtension(filePath)
                        );
                }
                else
                {
                    newPath = Path.Combine(
                        Path.GetDirectoryName(filePath),
                        EscapeFilename(
                            String.Format(
                                GetMovieRenamePattern(config),
                                metadata.Title,
                                metadata.Date.Year
                                ),
                            Path.GetFileNameWithoutExtension(filePath),
                            setStatus
                            )
                        + Path.GetExtension(filePath)
                        );
                }

                if (filePath != newPath)
                {
                    try {
                        if (File.Exists(newPath))
                        {
                            setStatus("Error: Could not rename - file already exists", MessageType.Error);
                            fileSuccess = false;
                        }
                        else
                        {
                            File.Move(filePath, newPath);
                            setPath(newPath);
                            setStatus($"Successfully renamed file to '{Path.GetFileName(newPath)}'", MessageType.Information);
                        }
                    } catch (Exception ex) {
                        if (config.verbose)
                        {
                            setStatus($"Error: Failed to rename file ({ex.GetType().Name}: {ex.Message})", MessageType.Error);
                        }
                        else
                        {
                            setStatus("Error: Failed to rename file", MessageType.Error);
                        }
                        fileSuccess = false;
                    }
                }
            }

            return(fileSuccess);
        }
Example #6
0
 private static string GetMovieRenamePattern(AutoTagConfig config)   // Get usable renaming pattern
 {
     return(config.movieRenamePattern.Replace("%1", "{0}").Replace("%2", "{1}"));
 }
Example #7
0
 private static string GetTVRenamePattern(AutoTagConfig config)   // Get usable renaming pattern
 {
     return(config.tvRenamePattern.Replace("%1", "{0}").Replace("%2", "{1}").Replace("%3", "{2}").Replace("%4", "{3}"));
 }