private void findArtwork(
     TvShow show, List<File> directoryContents, Pattern searchPattern, MediaFileType type) {
   for (File file : directoryContents) {
     Matcher matcher = searchPattern.matcher(file.getName());
     if (matcher.matches() && !file.getName().startsWith("._")) { // MacOS ignore
       // false positive exclusion: poster regexp would also find season posters..
       if (type == MediaFileType.POSTER && file.getName().matches(seasonPattern.pattern())) {
         continue;
       }
       MediaFile mf = new MediaFile(file, type);
       show.addToMediaFiles(mf);
       LOGGER.debug("found " + mf.getType().name().toLowerCase() + ": " + file.getPath());
       break;
     }
   }
 }
    @Override
    public Object getColumnValue(MediaFile mediaFile, int column) {
      switch (column) {
        case 0:
          if (mediaFile.isVideo()) {
            return IconManager.PLAY_SMALL;
          }
          if (mediaFile.isGraphic()) {
            return IconManager.SEARCH;
          }
          return null;

        case 1:
          return mediaFile.getFilename();

        case 2:
          return mediaFile.getFilesizeInMegabytes();

        case 3:
          return getMediaFileTypeLocalized(mediaFile.getType());

        case 4:
          return mediaFile.getCombinedCodecs();

        case 5:
          return mediaFile.getVideoResolution();

        case 6:
          return mediaFile.getDurationHM();

        case 7:
          return mediaFile.getSubtitlesAsString();
      }

      throw new IllegalStateException();
    }
  /**
   * Find additional episode files.<br>
   * adds everything which starts with "videoFile name"<br>
   * scans subs/subtitle directories aswell
   *
   * @param episode the episode
   * @param videoFile the video file
   * @param directoryContents the directory contents
   * @return indicator whether something new has been found or not
   */
  private boolean findAdditionalEpisodeFiles(
      TvShowEpisode episode, File videoFile, File[] directoryContents) {
    boolean newFileFound = false;
    List<MediaFile> existingMediaFiles = episode.getMediaFiles();

    for (File file : directoryContents) {
      if (file.isFile()) {
        MediaFile mf = new MediaFile(file);
        if (existingMediaFiles.contains(mf)) {
          continue;
        }
        if (mf.getType().equals(MediaFileType.VIDEO)
            || !mf.getBasename().startsWith(FilenameUtils.getBaseName(videoFile.getName()))
            || file.getName().startsWith(skipFilesStartingWith)) { // MacOS ignore)
          continue;
        }
        if (mf.getType() == MediaFileType.SUBTITLE) {
          episode.setSubtitles(true);
        }
        // check if it is a poster
        if (mf.getType() == MediaFileType.GRAPHIC) {
          LOGGER.debug("parsing unknown graphic " + mf.getFilename());
          String vfilename = FilenameUtils.getBaseName(videoFile.getName());
          if (vfilename.equals(FilenameUtils.getBaseName(mf.getFilename())) // basename match
              || Utils.cleanStackingMarkers(vfilename)
                  .trim()
                  .equals(FilenameUtils.getBaseName(mf.getFilename())) // basename w/o stacking
              || episode
                  .getTitle()
                  .equals(FilenameUtils.getBaseName(mf.getFilename()))) { // title match
            mf.setType(MediaFileType.THUMB);
          }
        }

        episode.addToMediaFiles(mf);
        newFileFound = true;
      } else {
        if (file.getName().equalsIgnoreCase("subs")
            || file.getName().equalsIgnoreCase("subtitle")) {
          File[] subDirContent = file.listFiles();
          if (subDirContent == null) {
            LOGGER.error("Whops. Cannot access directory: " + file.getName());
          } else {
            for (File subDirFile : subDirContent) {
              if (FilenameUtils.getBaseName(subDirFile.getName())
                  .startsWith(FilenameUtils.getBaseName(videoFile.getName()))) {
                MediaFile mf = new MediaFile(subDirFile);
                if (existingMediaFiles.contains(mf)) {
                  continue;
                }
                if (mf.getType() == MediaFileType.SUBTITLE) {
                  episode.setSubtitles(true);
                }
                episode.addToMediaFiles(mf);
                newFileFound = true;
              }
            }
          }
        }
      }
    }

    return newFileFound;
  }
  /**
   * Find tv episodes.
   *
   * @param tvShow the tv show
   * @param dir the dir
   */
  private void findTvEpisodes(TvShow tvShow, File dir) {
    LOGGER.debug("parsing " + dir.getPath());
    // crawl this folder and try to find every episode and its corresponding files in it
    File[] content = dir.listFiles();
    if (content == null) {
      LOGGER.error("Whops. Cannot access directory: " + dir.getName());
      return;
    }

    Arrays.sort(content);
    for (File file : content) {
      if (file.isFile()) {
        if (!file.getName().startsWith(skipFilesStartingWith)) {
          MediaFile mf = new MediaFile(file);
          // check filetype - we only proceed here if it's a video file
          if (!mf.getType().equals(MediaFileType.VIDEO)) {
            continue;
          }

          // is this file already assigned to another episode?
          List<TvShowEpisode> episodes = tvShowList.getTvEpisodesByFile(tvShow, file);
          if (episodes.size() == 0) {
            // try to check what episode//season
            // EpisodeMatchingResult result =
            // TvShowEpisodeAndSeasonParser.detectEpisodeFromFilename(file);
            String relativePath =
                new File(tvShow.getPath()).toURI().relativize(file.toURI()).getPath();
            EpisodeMatchingResult result =
                TvShowEpisodeAndSeasonParser.detectEpisodeFromFilenameAlternative(
                    relativePath, tvShow.getTitle());

            // second check: is the detected episode (>-1; season >-1) already in tmm and any valid
            // stacking markers found?
            if (result.episodes.size() == 1 && result.season > -1 && result.stackingMarkerFound) {
              // get any assigned episode
              TvShowEpisode ep = tvShow.getEpisode(result.season, result.episodes.get(0));
              if (ep != null) {
                ep.setNewlyAdded(true);
                ep.addToMediaFiles(mf);
                continue;
              }
            }

            if (result.episodes.size() == 0) {
              // try to parse out episodes/season from parent directory
              result =
                  TvShowEpisodeAndSeasonParser.detectEpisodeFromDirectory(dir, tvShow.getPath());
            }

            if (result.season == -1) {
              // did the search find a season?
              // no -> search for it in the folder name (relative path between tv show root and the
              // current dir)
              result.season = TvShowEpisodeAndSeasonParser.detectSeason(relativePath);
            }

            List<TvShowEpisode> episodesInNfo = TvShowEpisode.parseNFO(file);

            // did we find any episodes in the NFO?
            if (episodesInNfo.size() > 0) {
              // these have priority!
              for (TvShowEpisode e : episodesInNfo) {
                e.setPath(dir.getPath());
                e.setTvShow(tvShow);
                e.addToMediaFiles(mf);
                e.setDateAddedFromMediaFile(mf);
                findAdditionalEpisodeFiles(e, file, content);
                e.setNewlyAdded(true);
                e.saveToDb();
                tvShow.addEpisode(e);
              }
            } else if (result.episodes.size() > 0) {
              // something found with the season detection?
              for (int ep : result.episodes) {
                TvShowEpisode episode = new TvShowEpisode();
                episode.setDvdOrder(Globals.settings.getTvShowSettings().isDvdOrder());
                episode.setEpisode(ep);
                episode.setSeason(result.season);
                episode.setFirstAired(result.date);

                if (result.name.isEmpty()) {
                  result.name = FilenameUtils.getBaseName(file.getName());
                }
                episode.setTitle(result.name);

                episode.setPath(dir.getPath());
                episode.setTvShow(tvShow);
                episode.addToMediaFiles(mf);
                episode.setDateAddedFromMediaFile(mf);
                findAdditionalEpisodeFiles(episode, file, content);
                episode.setNewlyAdded(true);
                episode.saveToDb();
                tvShow.addEpisode(episode);
              }
            } else {
              // episode detection found nothing - simply add this file
              TvShowEpisode episode = new TvShowEpisode();
              episode.setDvdOrder(Globals.settings.getTvShowSettings().isDvdOrder());
              episode.setEpisode(-1);
              episode.setSeason(-1);
              episode.setPath(dir.getPath());

              episode.setTitle(FilenameUtils.getBaseName(file.getName()));
              episode.setTvShow(tvShow);
              episode.setFirstAired(result.date);
              episode.addToMediaFiles(mf);
              episode.setDateAddedFromMediaFile(mf);
              findAdditionalEpisodeFiles(episode, file, content);
              episode.setNewlyAdded(true);
              episode.saveToDb();
              tvShow.addEpisode(episode);
            }
          } else {
            // episode already added; look if any new additional files have been added
            for (TvShowEpisode episode : episodes) {
              if (findAdditionalEpisodeFiles(episode, file, content)) {
                episode.saveToDb();
              }
            }
          }
        } // end skipFilesStartingWith
      } // end isFile

      if (file.isDirectory()
          && !skipFolders.contains(file.getName().toUpperCase())
          && !file.getName().matches(skipFoldersRegex)
          && !TvShowModuleManager.TV_SHOW_SETTINGS
              .getTvShowSkipFolders()
              .contains(file.getAbsolutePath())) {
        // check if that directory contains a .tmmignore file
        File tmmIgnore = new File(file, ".tmmignore");
        if (!tmmIgnore.exists()) {
          // dig deeper
          if (file.getName().toUpperCase().equals("VIDEO_TS")) {
            findTvEpisodesAsDisc(tvShow, file);
          } else if (file.getName().toUpperCase().equals("BDMV")) {
            findTvEpisodesAsDisc(tvShow, file);
          } else {
            findTvEpisodes(tvShow, file);
          }
        }
      }
    }
  }
  private static String generateName(
      String template, TvShow tvShow, MediaFile mf, boolean forFile) {
    String filename = "";
    List<TvShowEpisode> eps = TvShowList.getInstance().getTvEpisodesByFile(tvShow, mf.getFile());
    if (eps == null || eps.size() == 0) {
      // this should not happen, but unluckily ODB does it sometimes; try a second time to get the
      // episode
      try {
        Thread.sleep(250);
      } catch (Exception ex) {
      }
      eps = TvShowList.getInstance().getTvEpisodesByFile(tvShow, mf.getFile());
    }
    if (eps == null || eps.size() == 0) {
      return "";
    }

    if (StringUtils.isBlank(template)) {
      filename = createDestination(SETTINGS.getRenamerFilename(), tvShow, eps);
    } else {
      filename = createDestination(template, tvShow, eps);
    }

    // since we can use this method for folders too, use the next options solely for files
    if (forFile) {
      if (mf.getType().equals(MediaFileType.THUMB)) {
        if (SETTINGS.isUseRenamerThumbPostfix()) {
          filename = filename + "-thumb";
        }
        // else let the filename as is
      }
      if (mf.getType().equals(MediaFileType.FANART)) {
        filename = filename + "-fanart";
      }
      if (mf.getType().equals(MediaFileType.TRAILER)) {
        filename = filename + "-trailer";
      }
      if (mf.getType().equals(MediaFileType.VIDEO_EXTRA)) {
        String name = mf.getBasename();
        Pattern p = Pattern.compile("(?i).*([ _.-]extras[ _.-]).*");
        Matcher m = p.matcher(name);
        if (m.matches()) {
          name = name.substring(m.end(1)); // everything behind
        }
        // if not, MF must be within /extras/ folder - use name 1:1
        filename = filename + "-extras-" + name;
      }
      if (mf.getType().equals(MediaFileType.SUBTITLE)) {
        List<MediaFileSubtitle> subtitles = mf.getSubtitles();
        if (subtitles != null && subtitles.size() > 0) {
          MediaFileSubtitle mfs = mf.getSubtitles().get(0);
          if (mfs != null) {
            if (!mfs.getLanguage().isEmpty()) {
              filename = filename + "." + mfs.getLanguage();
            }
            if (mfs.isForced()) {
              filename = filename + ".forced";
            }
          } else {
            // TODO: meh, we didn't have an actual MF yet - need to parse filename ourselves (like
            // movie). But with a recent scan of files/DB this
            // should not occur.
          }
        }
      }
    } // end forFile

    // ASCII replacement
    if (SETTINGS.isAsciiReplacement()) {
      filename = StrgUtils.convertToAscii(filename, false);
    }

    filename = filename + "." + mf.getExtension(); // readd original extension

    return filename;
  }
  /**
   * Renames a MediaFiles<br>
   * gets all episodes of it, creates season folder, updates MFs & DB
   *
   * @param mf the MediaFile
   * @param show the tvshow (only needed for path)
   */
  public static void renameMediaFile(MediaFile mf, TvShow show) {
    // #######################################################
    // Assumption: all multi-episodes share the same season!!!
    // #######################################################

    List<TvShowEpisode> eps = TvShowList.getInstance().getTvEpisodesByFile(show, mf.getFile());
    if (eps == null || eps.size() == 0) {
      // this should not happen, but unluckily ODB does it sometimes; try a second time to get the
      // episode
      try {
        Thread.sleep(250);
      } catch (Exception e) {
      }
      eps = TvShowList.getInstance().getTvEpisodesByFile(show, mf.getFile());
    }
    if (eps == null || eps.size() == 0) {
      // FIXME: workaround for r1972
      // when moving video file, all NFOs get deleted and a new gets created.
      // so this OLD NFO is not found anylonger - just delete it
      if (mf.getType() == MediaFileType.NFO) {
        FileUtils.deleteQuietly(mf.getFile());
        return;
      }

      LOGGER.warn("No episodes found for file '" + mf.getFilename() + "' - skipping");
      return;
    }

    // get first, for isDisc and season
    TvShowEpisode ep = eps.get(0);

    // test access rights or return
    LOGGER.debug(
        "testing file S:"
            + ep.getSeason()
            + " E:"
            + ep.getEpisode()
            + " MF:"
            + mf.getFile().getAbsolutePath());
    File f = mf.getFile();
    boolean testRenameOk = false;
    for (int i = 0; i < 5; i++) {
      testRenameOk = f.renameTo(f); // haahaa, try to rename to itself :P
      if (testRenameOk) {
        break; // ok it worked, step out
      }
      try {
        if (!f.exists()) {
          LOGGER.debug("Hmmm... file " + f + " does not even exists; delete from DB");
          // delete from MF
          for (TvShowEpisode e : eps) {
            e.removeFromMediaFiles(mf);
            e.saveToDb();
            e.writeNFO();
          }
          return;
        }
        LOGGER.debug("rename did not work - sleep a while and try again...");
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        LOGGER.warn("I'm so excited - could not sleep");
      }
    }
    if (!testRenameOk) {
      LOGGER.warn("File " + mf.getFile().getAbsolutePath() + " is not accessible!");
      MessageManager.instance.pushMessage(
          new Message(MessageLevel.ERROR, mf.getFilename(), "message.renamer.failedrename"));
      return;
    }

    // create SeasonDir
    // String seasonName = "Season " + String.valueOf(ep.getSeason());
    String seasonName = generateSeasonDir(SETTINGS.getRenamerSeasonFoldername(), ep);
    File seasonDir = null;
    if (StringUtils.isNotBlank(seasonName)) {
      seasonDir = new File(show.getPath(), seasonName);
      if (!seasonDir.exists()) {
        seasonDir.mkdir();
      }
    } else {
      seasonDir = new File(show.getPath());
    }

    // rename epFolder accordingly
    if (ep.isDisc() || mf.isDiscFile()) {
      // \Season 1\S01E02E03\VIDEO_TS\VIDEO_TS.VOB
      // ........ \epFolder \disc... \ file
      File disc = mf.getFile().getParentFile();
      File epFolder = disc.getParentFile();

      // sanity check
      if (!disc.getName().equalsIgnoreCase("BDMV")
          && !disc.getName().equalsIgnoreCase("VIDEO_TS")) {
        LOGGER.error(
            "Episode is labeled as 'on BD/DVD', but structure seems not to match. Better exit and do nothing... o_O");
        return;
      }

      String newFoldername =
          FilenameUtils.getBaseName(generateFolderename(show, mf)); // w/o extension
      if (newFoldername != null && !newFoldername.isEmpty()) {
        File newEpFolder = new File(seasonDir + File.separator + newFoldername);
        File newDisc = new File(newEpFolder + File.separator + disc.getName()); // old disc name

        try {
          // if (!epFolder.equals(newEpFolder)) {
          if (!epFolder.getAbsolutePath().equals(newEpFolder.getAbsolutePath())) {
            boolean ok = false;
            try {
              ok = Utils.moveDirectorySafe(epFolder, newEpFolder);
            } catch (Exception e) {
              LOGGER.error(e.getMessage());
              MessageManager.instance.pushMessage(
                  new Message(
                      MessageLevel.ERROR,
                      epFolder.getName(),
                      "message.renamer.failedrename",
                      new String[] {":", e.getLocalizedMessage()}));
            }
            if (ok) {
              // iterate over all EPs & MFs and fix new path
              LOGGER.debug("updating *all* MFs for new path -> " + newEpFolder);
              for (TvShowEpisode e : eps) {
                e.updateMediaFilePath(disc, newDisc);
                e.setPath(newEpFolder.getPath());
                e.saveToDb();
                e.writeNFO();
              }
            }
            // and cleanup
            cleanEmptyDir(epFolder);
          } else {
            // old and new folder are equal, do nothing
          }
        } catch (Exception e) {
          LOGGER.error("error moving video file " + disc.getName() + " to " + newFoldername, e);
          MessageManager.instance.pushMessage(
              new Message(
                  MessageLevel.ERROR,
                  mf.getFilename(),
                  "message.renamer.failedrename",
                  new String[] {":", e.getLocalizedMessage()}));
        }
      }
    } // end isDisc
    else {
      MediaFile newMF = new MediaFile(mf); // clone MF
      if (mf.getType().equals(MediaFileType.TRAILER)) {
        // move trailer into separate dir - not supported by XBMC
        File sample = new File(seasonDir, "sample");
        if (!sample.exists()) {
          sample.mkdir();
        }
        seasonDir = sample; // change directory storage
      }
      String filename = generateFilename(show, mf);
      LOGGER.debug("new filename should be " + filename);
      if (filename != null && !filename.isEmpty()) {
        File newFile = new File(seasonDir, filename);

        try {
          // if (!mf.getFile().equals(newFile)) {
          if (!mf.getFile().getAbsolutePath().equals(newFile.getAbsolutePath())) {
            File oldMfFile = mf.getFile();
            boolean ok = false;
            try {
              ok = Utils.moveFileSafe(oldMfFile, newFile);
            } catch (Exception e) {
              LOGGER.error(e.getMessage());
              MessageManager.instance.pushMessage(
                  new Message(
                      MessageLevel.ERROR,
                      oldMfFile.getPath(),
                      "message.renamer.failedrename",
                      new String[] {":", e.getLocalizedMessage()}));
            }
            if (ok) {
              newMF.setPath(seasonDir.getAbsolutePath());
              newMF.setFilename(filename);
              // iterate over all EPs and delete old / set new MF
              for (TvShowEpisode e : eps) {
                e.removeFromMediaFiles(mf);
                e.addToMediaFiles(newMF);
                e.setPath(seasonDir.getAbsolutePath());
                e.saveToDb();
                e.writeNFO();
              }
            }
            // and cleanup
            cleanEmptyDir(oldMfFile.getParentFile());
          } else {
            // old and new file are equal, keep MF
          }
        } catch (Exception e) {
          LOGGER.error(
              "error moving video file " + mf.getFilename() + " to " + newFile.getPath(), e);
          MessageManager.instance.pushMessage(
              new Message(
                  MessageLevel.ERROR,
                  mf.getFilename(),
                  "message.renamer.failedrename",
                  new String[] {":", e.getLocalizedMessage()}));
        }
      }
    }
  }