BizHawk/BizHawk.Client.EmuHawk/movie/PlayMovie.cs

724 lines
18 KiB
C#

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;
using BizHawk.Client.EmuHawk.WinFormExtensions;
namespace BizHawk.Client.EmuHawk
{
public partial class PlayMovie : Form
{
private readonly PlatformFrameRates PlatformFrameRates = new PlatformFrameRates();
private List<IMovie> _movieList = new List<IMovie>();
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();
TurboCheckbox.Checked = Global.Config.TurboSeek;
}
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;
}
//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));
int? index;
lock (_movieList)
{
//need to check IsDuplicateOf within the lock
index = IsDuplicateOf(filename);
if (index.HasValue) return index;
_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<int>();
// 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<int>();
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<int>(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<string>();
var fpTodo = new List<string>();
dpTodo.Enqueue(directory);
Dictionary<string, int> ordinals = new Dictionary<string, int>();
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<fpTodo.Count;i++)
{
var file = fpTodo[i];
lock(ordinals) ordinals[file] = i;
AddMovieToList(file, force: false);
}
);
//sort by the ordinal key to maintain relatively stable results when rescanning
_movieList.Sort((a, b) => 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<int>()
.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<MovieDetails>();
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)
{
Global.Config.TurboSeek = TurboCheckbox.Checked;
Run();
Global.MovieSession.ReadOnly = ReadOnlyCheckBox.Checked;
if (StopOnFrameCheckbox.Checked &&
(StopOnFrameTextBox.ToRawInt().HasValue || LastFrameCheckbox.Checked))
{
if (LastFrameCheckbox.Checked)
{
GlobalWin.MainForm.PauseOnFrame = Global.MovieSession.Movie.InputLogLength;
}
else
{
GlobalWin.MainForm.PauseOnFrame = StopOnFrameTextBox.ToRawInt();
}
}
Close();
}
private void Cancel_Click(object sender, EventArgs e)
{
Close();
}
#endregion
private bool _programmaticallyChangingStopFrameCheckbox = false;
private void StopOnFrameCheckbox_CheckedChanged(object sender, EventArgs e)
{
if (!_programmaticallyChangingStopFrameCheckbox)
{
StopOnFrameTextBox.Focus();
}
}
private void StopOnFrameTextBox_TextChanged_1(object sender, EventArgs e)
{
_programmaticallyChangingStopFrameCheckbox = true;
StopOnFrameCheckbox.Checked = !string.IsNullOrWhiteSpace(StopOnFrameTextBox.Text);
_programmaticallyChangingStopFrameCheckbox = false;
}
private void LastFrameCheckbox_CheckedChanged(object sender, EventArgs e)
{
if (LastFrameCheckbox.Checked == true)
{
_programmaticallyChangingStopFrameCheckbox = true;
StopOnFrameCheckbox.Checked = true;
_programmaticallyChangingStopFrameCheckbox = false;
}
StopOnFrameTextBox.Enabled = !LastFrameCheckbox.Checked;
}
#endregion
}
}