using System; using System.Threading.Tasks; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; using BizHawk.Client.Common; using BizHawk.Common; namespace BizHawk.Client.EmuHawk { public partial class PlayMovie : Form { private readonly PlatformFrameRates PlatformFrameRates = new PlatformFrameRates(); private List _movieList = new List(); private bool _sortReverse; private string _sortedCol; private bool _sortDetailsReverse; private string _sortedDetailsCol; public PlayMovie() { InitializeComponent(); MovieView.QueryItemText += MovieView_QueryItemText; MovieView.VirtualMode = true; _sortReverse = false; _sortedCol = string.Empty; _sortDetailsReverse = false; _sortedDetailsCol = string.Empty; } private void PlayMovie_Load(object sender, EventArgs e) { IncludeSubDirectories.Checked = Global.Config.PlayMovie_IncludeSubdir; MatchHashCheckBox.Checked = Global.Config.PlayMovie_MatchHash; ScanFiles(); PreHighlightMovie(); } private void MovieView_QueryItemText(int index, int column, out string text) { text = string.Empty; if (column == 0) // File { text = Path.GetFileName(_movieList[index].Filename); } if (column == 1) // System { text = _movieList[index].SystemID; } if (column == 2) // Game { text = _movieList[index].GameName; } if (column == 3) // Time { text = PlatformFrameRates.MovieTime(_movieList[index]).ToString(@"hh\:mm\:ss\.fff"); } } private void Run() { var indices = MovieView.SelectedIndices; if (indices.Count > 0) // Import file if necessary { GlobalWin.MainForm.StartNewMovie(_movieList[MovieView.SelectedIndices[0]], false); } } private int? AddMovieToList(string filename, bool force) { using (var file = new HawkFile(filename)) { if (!file.Exists) { return null; } var index = IsDuplicateOf(filename); if (!index.HasValue) { //System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); watch.Start(); var movie = PreLoadMovieFile(file, force); if (movie == null) { return null; } //watch.Stop(); Console.WriteLine("[{0}] {1}",watch.ElapsedMilliseconds,Path.GetFileName(filename)); lock (_movieList) { _movieList.Add(movie); index = _movieList.Count - 1; } _sortReverse = false; _sortedCol = string.Empty; } return index; } } private int? IsDuplicateOf(string filename) { for (var i = 0; i < _movieList.Count; i++) { if (_movieList[i].Filename == filename) { return i; } } return null; } private IMovie PreLoadMovieFile(HawkFile hf, bool force) { var movie = MovieService.Get(hf.CanonicalFullPath); try { movie.PreLoadHeaderAndLength(hf); // Don't do this from browse if (movie.Hash == Global.Game.Hash || Global.Config.PlayMovie_MatchHash == false || force) { return movie; } } catch (Exception ex) { // TODO: inform the user that a movie failed to parse in some way Console.WriteLine(ex.Message); } return null; } private void UpdateList() { MovieView.Refresh(); MovieCount.Text = _movieList.Count + " movie" + (_movieList.Count != 1 ? "s" : string.Empty); } private void PreHighlightMovie() { if (Global.Game == null) { return; } var indices = new List(); // Pull out matching names for (var i = 0; i < _movieList.Count; i++) { if (PathManager.FilesystemSafeName(Global.Game) == _movieList[i].GameName) { indices.Add(i); } } if (indices.Count == 0) { return; } if (indices.Count == 1) { HighlightMovie(indices[0]); return; } // Prefer tas files var tas = new List(); for (var i = 0; i < indices.Count; i++) { foreach (var ext in MovieService.MovieExtensions) { if (Path.GetExtension(_movieList[indices[i]].Filename).ToUpper() == "." + ext) { tas.Add(i); } } } if (tas.Count == 1) { HighlightMovie(tas[0]); return; } if (tas.Count > 1) { indices = new List(tas); } // Final tie breaker - Last used file var file = new FileInfo(_movieList[indices[0]].Filename); var time = file.LastAccessTime; var mostRecent = indices.First(); for (var i = 1; i < indices.Count; i++) { file = new FileInfo(_movieList[indices[0]].Filename); if (file.LastAccessTime > time) { time = file.LastAccessTime; mostRecent = indices[i]; } } HighlightMovie(mostRecent); return; } private void HighlightMovie(int index) { MovieView.SelectedIndices.Clear(); MovieView.setSelection(index); MovieView.SelectItem(index, true); } private void ScanFiles() { _movieList.Clear(); MovieView.ItemCount = 0; MovieView.Update(); var directory = PathManager.MakeAbsolutePath(Global.Config.PathEntries.MoviesPathFragment, null); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } var dpTodo = new Queue(); var fpTodo = new List(); dpTodo.Enqueue(directory); Dictionary ordinals = new Dictionary(); while (dpTodo.Count > 0) { string dp = dpTodo.Dequeue(); //enqueue subdirectories if appropriate if (Global.Config.PlayMovie_IncludeSubdir) foreach(var subdir in Directory.GetDirectories(dp)) dpTodo.Enqueue(subdir); //add movies fpTodo.AddRange(Directory.GetFiles(dp, "*." + MovieService.DefaultExtension)); fpTodo.AddRange(Directory.GetFiles(dp, "*." + TasMovie.Extension)); } //in parallel, scan each movie Parallel.For(0, fpTodo.Count, (i) => //for(int i=0;i ordinals[a.Filename].CompareTo(ordinals[b.Filename])); RefreshMovieList(); } #region Events #region Movie List void RefreshMovieList() { MovieView.ItemCount = _movieList.Count; UpdateList(); } private void MovieView_DragEnter(object sender, DragEventArgs e) { e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None; } private void MovieView_DragDrop(object sender, DragEventArgs e) { var filePaths = (string[])e.Data.GetData(DataFormats.FileDrop); filePaths .Where(path => MovieService.MovieExtensions.Contains(Path.GetExtension(path).Replace(".", ""))) .ToList() .ForEach(path => AddMovieToList(path, force: true)); RefreshMovieList(); } private void MovieView_KeyDown(object sender, KeyEventArgs e) { if (e.Control && e.KeyCode == Keys.C) { var indexes = MovieView.SelectedIndices; if (indexes.Count > 0) { var copyStr = new StringBuilder(); foreach (int index in indexes) { copyStr .Append(_movieList[index].Filename).Append('\t') .Append(_movieList[index].SystemID).Append('\t') .Append(_movieList[index].GameName).Append('\t') .Append(PlatformFrameRates.MovieTime(_movieList[index]).ToString(@"hh\:mm\:ss\.fff")) .AppendLine(); Clipboard.SetDataObject(copyStr.ToString()); } } } } private void MovieView_DoubleClick(object sender, EventArgs e) { Run(); Close(); } private void MovieView_ColumnClick(object sender, ColumnClickEventArgs e) { var columnName = MovieView.Columns[e.Column].Text; if (_sortedCol != columnName) { _sortReverse = false; } switch (columnName) { case "File": if (_sortReverse) { _movieList = _movieList .OrderByDescending(x => Path.GetFileName(x.Filename)) .ThenBy(x => x.SystemID) .ThenBy(x => x.GameName) .ThenBy(x => x.FrameCount) .ToList(); } else { _movieList = _movieList .OrderBy(x => Path.GetFileName(x.Filename)) .ThenBy(x => x.SystemID) .ThenBy(x => x.GameName) .ThenBy(x => x.FrameCount) .ToList(); } break; case "SysID": if (_sortReverse) { _movieList = _movieList .OrderByDescending(x => x.SystemID) .ThenBy(x => Path.GetFileName(x.Filename)) .ThenBy(x => x.GameName) .ThenBy(x => x.FrameCount) .ToList(); } else { _movieList = _movieList .OrderBy(x => x.SystemID) .ThenBy(x => Path.GetFileName(x.Filename)) .ThenBy(x => x.GameName) .ThenBy(x => x.FrameCount) .ToList(); } break; case "Game": if (_sortReverse) { _movieList = _movieList .OrderByDescending(x => x.GameName) .ThenBy(x => Path.GetFileName(x.Filename)) .ThenBy(x => x.SystemID) .ThenBy(x => x.FrameCount) .ToList(); } else { _movieList = _movieList .OrderBy(x => x.GameName) .ThenBy(x => Path.GetFileName(x.Filename)) .ThenBy(x => x.SystemID) .ThenBy(x => x.FrameCount) .ToList(); } break; case "Length (est.)": if (_sortReverse) { _movieList = _movieList .OrderByDescending(x => x.FrameCount) .ThenBy(x => Path.GetFileName(x.Filename)) .ThenBy(x => x.SystemID) .ThenBy(x => x.FrameCount) .ToList(); } else { _movieList = _movieList .OrderBy(x => x.FrameCount) .ThenBy(x => Path.GetFileName(x.Filename)) .ThenBy(x => x.SystemID) .ThenBy(x => x.GameName) .ToList(); } break; } _sortedCol = columnName; _sortReverse = !_sortReverse; MovieView.Refresh(); } private void MovieView_SelectedIndexChanged(object sender, EventArgs e) { toolTip1.SetToolTip(DetailsView, string.Empty); DetailsView.Items.Clear(); if (MovieView.SelectedIndices.Count < 1) { OK.Enabled = false; return; } OK.Enabled = true; var firstIndex = MovieView.SelectedIndices[0]; MovieView.ensureVisible(firstIndex); foreach (var kvp in _movieList[firstIndex].HeaderEntries) { var item = new ListViewItem(kvp.Key); item.SubItems.Add(kvp.Value); bool add = true; switch (kvp.Key) { case HeaderKeys.SHA1: if (kvp.Value != Global.Game.Hash) { item.BackColor = Color.Pink; toolTip1.SetToolTip(DetailsView, "Current SHA1: " + Global.Game.Hash); } break; case HeaderKeys.EMULATIONVERSION: if (kvp.Value != VersionInfo.GetEmuVersion()) { item.BackColor = Color.Yellow; } break; case HeaderKeys.PLATFORM: if (kvp.Value != Global.Game.System) { item.BackColor = Color.Pink; } break; } if(add) DetailsView.Items.Add(item); } var FpsItem = new ListViewItem("Fps"); FpsItem.SubItems.Add(string.Format("{0:0.#######}", Fps(_movieList[firstIndex]))); DetailsView.Items.Add(FpsItem); var FramesItem = new ListViewItem("Frames"); FramesItem.SubItems.Add(_movieList[firstIndex].FrameCount.ToString()); DetailsView.Items.Add(FramesItem); CommentsBtn.Enabled = _movieList[firstIndex].Comments.Any(); SubtitlesBtn.Enabled = _movieList[firstIndex].Subtitles.Any(); } public double Fps(IMovie movie) { var system = movie.HeaderEntries[HeaderKeys.PLATFORM]; var pal = movie.HeaderEntries.ContainsKey(HeaderKeys.PAL) && movie.HeaderEntries[HeaderKeys.PAL] == "1"; return new PlatformFrameRates()[system, pal]; } private void EditMenuItem_Click(object sender, EventArgs e) { MovieView.SelectedIndices .Cast() .Select(index => _movieList[index]) .ToList() .ForEach(movie => System.Diagnostics.Process.Start(movie.Filename)); } #endregion #region Details private void DetailsView_ColumnClick(object sender, ColumnClickEventArgs e) { var detailsList = new List(); for (var i = 0; i < DetailsView.Items.Count; i++) { detailsList.Add(new MovieDetails { Keys = DetailsView.Items[i].Text, Values = DetailsView.Items[i].SubItems[1].Text, BackgroundColor = DetailsView.Items[i].BackColor }); } var columnName = DetailsView.Columns[e.Column].Text; if (_sortedDetailsCol != columnName) { _sortDetailsReverse = false; } switch (columnName) { // Header, Value case "Header": if (_sortDetailsReverse) { detailsList = detailsList .OrderByDescending(x => x.Keys) .ThenBy(x => x.Values).ToList(); } else { detailsList = detailsList .OrderBy(x => x.Keys) .ThenBy(x => x.Values).ToList(); } break; case "Value": if (_sortDetailsReverse) { detailsList = detailsList .OrderByDescending(x => x.Values) .ThenBy(x => x.Keys).ToList(); } else { detailsList = detailsList .OrderBy(x => x.Values) .ThenBy(x => x.Keys).ToList(); } break; } DetailsView.Items.Clear(); foreach (var detail in detailsList) { var item = new ListViewItem { Text = detail.Keys, BackColor = detail.BackgroundColor }; item.SubItems.Add(detail.Values); DetailsView.Items.Add(item); } _sortedDetailsCol = columnName; _sortDetailsReverse = !_sortDetailsReverse; } private void CommentsBtn_Click(object sender, EventArgs e) { var indices = MovieView.SelectedIndices; if (indices.Count > 0) { var form = new EditCommentsForm(); form.GetMovie(_movieList[MovieView.SelectedIndices[0]]); form.Show(); } } private void SubtitlesBtn_Click(object sender, EventArgs e) { var indices = MovieView.SelectedIndices; if (indices.Count > 0) { var s = new EditSubtitlesForm { ReadOnly = true }; s.GetMovie(_movieList[MovieView.SelectedIndices[0]]); s.Show(); } } #endregion #region Misc Widgets private void BrowseMovies_Click(object sender, EventArgs e) { var ofd = new OpenFileDialog { Filter = "Movie Files (*." + MovieService.DefaultExtension + ")|*." + MovieService.DefaultExtension + "|TAS project Files (*." + TasMovie.Extension + ")|*." + TasMovie.Extension + "|All Files|*.*", InitialDirectory = PathManager.MakeAbsolutePath(Global.Config.PathEntries.MoviesPathFragment, null) }; var result = ofd.ShowHawkDialog(); if (result == DialogResult.OK) { var file = new FileInfo(ofd.FileName); if (!file.Exists) { return; } int? index = AddMovieToList(ofd.FileName, true); RefreshMovieList(); if (index.HasValue) { MovieView.SelectedIndices.Clear(); MovieView.setSelection(index.Value); MovieView.SelectItem(index.Value, true); } } } private void Scan_Click(object sender, EventArgs e) { ScanFiles(); PreHighlightMovie(); } private void IncludeSubDirectories_CheckedChanged(object sender, EventArgs e) { Global.Config.PlayMovie_IncludeSubdir = IncludeSubDirectories.Checked; ScanFiles(); PreHighlightMovie(); } private void MatchHashCheckBox_CheckedChanged(object sender, EventArgs e) { Global.Config.PlayMovie_MatchHash = MatchHashCheckBox.Checked; ScanFiles(); PreHighlightMovie(); } private void Ok_Click(object sender, EventArgs e) { Run(); Global.MovieSession.ReadOnly = ReadOnlyCheckBox.Checked; Close(); } private void Cancel_Click(object sender, EventArgs e) { Close(); } #endregion #endregion } }