2 This file is part of the HandBrake source code.
\r
3 Homepage: <http://handbrake.fr>.
\r
4 It may be used under the terms of the GNU General Public License. */
\r
6 namespace Handbrake.Functions
\r
9 using System.Collections.Generic;
\r
10 using System.Diagnostics;
\r
13 using System.Security.Cryptography;
\r
15 using System.Text.RegularExpressions;
\r
16 using System.Windows.Forms;
\r
17 using System.Xml.Serialization;
\r
19 using HandBrake.Framework.Services;
\r
20 using HandBrake.Framework.Services.Interfaces;
\r
21 using HandBrake.ApplicationServices.Model;
\r
22 using HandBrake.ApplicationServices.Parsing;
\r
23 using HandBrake.ApplicationServices.Services.Interfaces;
\r
27 /// Useful functions which various screens can use.
\r
29 public static class Main
\r
32 /// The Error Service
\r
34 private static readonly IErrorService errorService = new ErrorService();
\r
37 /// The XML Serializer
\r
39 private static readonly XmlSerializer Ser = new XmlSerializer(typeof(List<QueueTask>));
\r
42 /// Calculate the duration of the selected title and chapters
\r
44 /// <param name="chapterStart">
\r
45 /// The chapter Start.
\r
47 /// <param name="chapterEnd">
\r
48 /// The chapter End.
\r
50 /// <param name="selectedTitle">
\r
51 /// The selected Title.
\r
54 /// The calculated duration.
\r
56 public static TimeSpan CalculateDuration(int chapterStart, int chapterEnd, Title selectedTitle)
\r
58 TimeSpan duration = TimeSpan.FromSeconds(0.0);
\r
61 if (chapterStart != 0 && chapterEnd != 0 && chapterEnd <= selectedTitle.Chapters.Count)
\r
63 for (int i = chapterStart; i <= chapterEnd; i++)
\r
64 duration += selectedTitle.Chapters[i - 1].Duration;
\r
71 /// Set's up the DataGridView on the Chapters tab (frmMain)
\r
73 /// <param name="title">
\r
74 /// The currently selected title object.
\r
75 /// This will be used to get chapter names if they exist.
\r
77 /// <param name="dataChpt">
\r
78 /// The DataGridView Control
\r
80 /// <param name="chapterEnd">
\r
81 /// The chapter End.
\r
84 /// The chapter naming.
\r
86 public static DataGridView ChapterNaming(Title title, DataGridView dataChpt, string chapterEnd)
\r
88 int i = 0, finish = 0;
\r
90 if (chapterEnd != "Auto")
\r
91 int.TryParse(chapterEnd, out finish);
\r
95 string chapterName = string.Empty;
\r
98 if (title.Chapters.Count <= i && title.Chapters[i] != null)
\r
100 chapterName = title.Chapters[i].ChapterName;
\r
104 int n = dataChpt.Rows.Add();
\r
105 dataChpt.Rows[n].Cells[0].Value = i + 1;
\r
106 dataChpt.Rows[n].Cells[1].Value = string.IsNullOrEmpty(chapterName) ? "Chapter " + (i + 1) : chapterName;
\r
107 dataChpt.Rows[n].Cells[0].ValueType = typeof(int);
\r
108 dataChpt.Rows[n].Cells[1].ValueType = typeof(string);
\r
116 /// Import a CSV file which contains Chapter Names
\r
118 /// <param name="dataChpt">
\r
119 /// The DataGridView Control
\r
121 /// <param name="filename">
\r
122 /// The filepath and name
\r
124 /// <returns>A Populated DataGridView</returns>
\r
125 public static DataGridView ImportChapterNames(DataGridView dataChpt, string filename)
\r
127 IDictionary<int, string> chapterMap = new Dictionary<int, string>();
\r
130 StreamReader sr = new StreamReader(filename);
\r
131 string csv = sr.ReadLine();
\r
132 while (csv != null)
\r
134 if (csv.Trim() != string.Empty)
\r
136 csv = csv.Replace("\\,", "<!comma!>");
\r
137 string[] contents = csv.Split(',');
\r
139 int.TryParse(contents[0], out chapter);
\r
140 chapterMap.Add(chapter, contents[1].Replace("<!comma!>", ","));
\r
142 csv = sr.ReadLine();
\r
150 foreach (DataGridViewRow item in dataChpt.Rows)
\r
153 chapterMap.TryGetValue((int)item.Cells[0].Value, out name);
\r
154 item.Cells[1].Value = name ?? "Chapter " + item.Cells[0].Value;
\r
161 /// Create a CSV file with the data from the Main Window Chapters tab
\r
163 /// <param name="mainWindow">Main Window</param>
\r
164 /// <param name="filePathName">Path to save the csv file</param>
\r
165 /// <returns>True if successful </returns>
\r
166 public static bool SaveChapterMarkersToCsv(frmMain mainWindow, string filePathName)
\r
170 string csv = string.Empty;
\r
172 foreach (DataGridViewRow row in mainWindow.data_chpt.Rows)
\r
174 csv += row.Cells[0].Value.ToString();
\r
176 csv += row.Cells[1].Value.ToString().Replace(",", "\\,");
\r
177 csv += Environment.NewLine;
\r
179 StreamWriter file = new StreamWriter(filePathName);
\r
185 catch (Exception exc)
\r
187 ShowExceptiowWindow("Unable to save Chapter Makrers file! \nChapter marker names will NOT be saved in your encode", exc.ToString());
\r
193 /// Function which generates the filename and path automatically based on
\r
194 /// the Source Name, DVD title and DVD Chapters
\r
196 /// <param name="mainWindow">
\r
197 /// The main Window.
\r
200 /// The Generated FileName
\r
202 public static string AutoName(frmMain mainWindow)
\r
204 string autoNamePath = string.Empty;
\r
205 if (mainWindow.drp_dvdtitle.Text != "Automatic")
\r
207 // Get the Source Name and remove any invalid characters
\r
209 string sourceName = Path.GetInvalidFileNameChars().Aggregate(Path.GetFileNameWithoutExtension(mainWindow.SourceName), (current, character) => current.Replace(character.ToString(), string.Empty));
\r
211 if (Properties.Settings.Default.AutoNameRemoveUnderscore)
\r
212 sourceName = sourceName.Replace("_", " ");
\r
214 if (Properties.Settings.Default.AutoNameTitleCase)
\r
215 sourceName = TitleCase(sourceName);
\r
217 // Get the Selected Title Number
\r
218 string[] titlesplit = mainWindow.drp_dvdtitle.Text.Split(' ');
\r
219 string dvdTitle = titlesplit[0].Replace("Automatic", string.Empty);
\r
221 // Get the Chapter Start and Chapter End Numbers
\r
222 string chapterStart = mainWindow.drop_chapterStart.Text.Replace("Auto", string.Empty);
\r
223 string chapterFinish = mainWindow.drop_chapterFinish.Text.Replace("Auto", string.Empty);
\r
224 string combinedChapterTag = chapterStart;
\r
225 if (chapterFinish != chapterStart && chapterFinish != string.Empty)
\r
226 combinedChapterTag = chapterStart + "-" + chapterFinish;
\r
228 // Get the destination filename.
\r
229 string destinationFilename;
\r
230 if (Properties.Settings.Default.autoNameFormat != string.Empty)
\r
232 destinationFilename = Properties.Settings.Default.autoNameFormat;
\r
233 destinationFilename = destinationFilename.Replace("{source}", sourceName)
\r
234 .Replace("{title}", dvdTitle)
\r
235 .Replace("{chapters}", combinedChapterTag);
\r
238 destinationFilename = sourceName + "_T" + dvdTitle + "_C" + combinedChapterTag;
\r
240 // Add the appropriate file extension
\r
241 if (mainWindow.drop_format.SelectedIndex == 0)
\r
243 destinationFilename += Properties.Settings.Default.useM4v == 0 || Properties.Settings.Default.useM4v == 2 || mainWindow.Check_ChapterMarkers.Checked ||
\r
244 mainWindow.AudioSettings.RequiresM4V() || mainWindow.Subtitles.RequiresM4V()
\r
248 else if (mainWindow.drop_format.SelectedIndex == 1)
\r
249 destinationFilename += ".mkv";
\r
251 // Now work out the path where the file will be stored.
\r
252 // First case: If the destination box doesn't already contain a path, make one.
\r
253 if (!mainWindow.text_destination.Text.Contains(Path.DirectorySeparatorChar.ToString()))
\r
255 // If there is an auto name path, use it...
\r
256 if (Properties.Settings.Default.autoNamePath.Trim() == "{source_path}" && !string.IsNullOrEmpty(mainWindow.sourcePath))
\r
258 autoNamePath = Path.Combine(Path.GetDirectoryName(mainWindow.sourcePath), destinationFilename);
\r
259 if (autoNamePath == mainWindow.sourcePath)
\r
261 // Append out_ to files that already exist or is the source file
\r
262 autoNamePath = Path.Combine(Path.GetDirectoryName(mainWindow.sourcePath), "output_" + destinationFilename);
\r
265 else if (Properties.Settings.Default.autoNamePath.Trim() != string.Empty && Properties.Settings.Default.autoNamePath.Trim() != "Click 'Browse' to set the default location")
\r
267 autoNamePath = Path.Combine(Properties.Settings.Default.autoNamePath, destinationFilename);
\r
269 else // ...otherwise, output to the source directory
\r
270 autoNamePath = null;
\r
272 else // Otherwise, use the path that is already there.
\r
274 // Use the path and change the file extension to match the previous destination
\r
275 autoNamePath = Path.Combine(Path.GetDirectoryName(mainWindow.text_destination.Text), destinationFilename);
\r
277 if (Path.HasExtension(mainWindow.text_destination.Text))
\r
278 autoNamePath = Path.ChangeExtension(autoNamePath,
\r
279 Path.GetExtension(mainWindow.text_destination.Text));
\r
283 return autoNamePath;
\r
287 /// Get's HandBrakes version data from the CLI.
\r
289 public static void SetCliVersionData()
\r
293 // 0 = SVN Build / Version
\r
296 // Get the SHA1 Hash of HandBrakeCLI
\r
298 using (Stream stream = File.OpenRead(Path.Combine(Application.StartupPath, "HandBrakeCLI.exe")))
\r
300 hash = SHA1.Create().ComputeHash(stream);
\r
302 string base64Hash = Convert.ToBase64String(hash);
\r
304 // Compare the hash with the last known hash. If it's the same, return.
\r
305 if (Properties.Settings.Default.CliExeHash == base64Hash)
\r
310 // It's not the same, so start the CLI to get it's version data.
\r
311 Process cliProcess = new Process();
\r
312 ProcessStartInfo handBrakeCli = new ProcessStartInfo("HandBrakeCLI.exe", " -u -v0")
\r
314 UseShellExecute = false,
\r
315 RedirectStandardError = true,
\r
316 RedirectStandardOutput = true,
\r
317 CreateNoWindow = true
\r
319 cliProcess.StartInfo = handBrakeCli;
\r
323 cliProcess.Start();
\r
325 // Retrieve standard output and report back to parent thread until the process is complete
\r
326 TextReader stdOutput = cliProcess.StandardError;
\r
328 while (!cliProcess.HasExited)
\r
330 line = stdOutput.ReadLine() ?? string.Empty;
\r
331 Match m = Regex.Match(line, @"HandBrake ([svnM0-9.]*) \(([0-9]*)\)");
\r
332 Match platform = Regex.Match(line, @"- ([A-Za-z0-9\s ]*) -");
\r
336 string version = m.Groups[1].Success ? m.Groups[1].Value : string.Empty;
\r
337 string build = m.Groups[2].Success ? m.Groups[2].Value : string.Empty;
\r
340 int.TryParse(build, out buildValue);
\r
342 Properties.Settings.Default.hb_build = buildValue;
\r
343 Properties.Settings.Default.hb_version = version;
\r
346 if (platform.Success)
\r
348 Properties.Settings.Default.hb_platform = platform.Value.Replace("-", string.Empty).Trim();
\r
351 if (cliProcess.TotalProcessorTime.Seconds > 10) // Don't wait longer than 10 seconds.
\r
353 Process cli = Process.GetProcessById(cliProcess.Id);
\r
354 if (!cli.HasExited)
\r
361 Properties.Settings.Default.CliExeHash = base64Hash;
\r
363 Properties.Settings.Default.Save();
\r
365 catch (Exception e)
\r
367 Properties.Settings.Default.hb_build = 0;
\r
368 Properties.Settings.Default.CliExeHash = null;
\r
369 Properties.Settings.Default.Save();
\r
371 ShowExceptiowWindow("Unable to retrieve version information from the CLI.", e.ToString());
\r
376 /// Check if the queue recovery file contains records.
\r
377 /// If it does, it means the last queue did not complete before HandBrake closed.
\r
378 /// So, return a boolean if true.
\r
381 /// True if there is a queue to recover.
\r
383 public static List<string> CheckQueueRecovery()
\r
387 string tempPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"HandBrake\");
\r
388 List<string> queueFiles = new List<string>();
\r
390 DirectoryInfo info = new DirectoryInfo(tempPath);
\r
391 FileInfo[] logFiles = info.GetFiles("*.xml");
\r
392 foreach (FileInfo file in logFiles)
\r
394 if (!file.Name.Contains("hb_queue_recovery"))
\r
397 using (FileStream strm = new FileStream(Path.Combine(file.DirectoryName, file.Name), FileMode.Open, FileAccess.Read))
\r
399 List<QueueTask> list = Ser.Deserialize(strm) as List<QueueTask>;
\r
402 if (list.Count != 0)
\r
404 queueFiles.Add(file.Name);
\r
414 return new List<string>(); // Keep quiet about the error.
\r
419 /// Recover a queue from file.
\r
421 /// <param name="encodeQueue">
\r
422 /// The encode Queue.
\r
424 public static void RecoverQueue(IQueueProcessor encodeQueue)
\r
426 string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"HandBrake\");
\r
428 DialogResult result = DialogResult.None;
\r
429 List<string> queueFiles = CheckQueueRecovery();
\r
430 if (queueFiles.Count == 1)
\r
432 result = MessageBox.Show(
\r
433 "HandBrake has detected unfinished items on the queue from the last time the application was launched. Would you like to recover these?",
\r
434 "Queue Recovery Possible", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
\r
436 else if (queueFiles.Count > 1)
\r
438 result = MessageBox.Show(
\r
439 "HandBrake has detected multiple unfinished queue files. These will be from multiple instances of HandBrake running. Would you like to recover all unfinished jobs?",
\r
440 "Queue Recovery Possible", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
\r
443 if (result == DialogResult.Yes)
\r
445 foreach (string file in queueFiles)
\r
447 encodeQueue.QueueManager.RestoreQueue(appDataPath + file); // Start Recovery
\r
452 if (IsMultiInstance) return; // Don't tamper with the files if we are multi instance
\r
454 foreach (string file in queueFiles)
\r
456 if (File.Exists(Path.Combine(appDataPath, file)))
\r
457 File.Delete(Path.Combine(appDataPath, file));
\r
463 /// Gets a value indicating whether HandBrake is running in multi instance mode
\r
465 /// <returns>True if the UI has another instance running</returns>
\r
466 public static bool IsMultiInstance
\r
470 return Process.GetProcessesByName("HandBrake").Length > 0 ? true : false;
\r
475 /// Map languages and their iso639_2 value into a IDictionary
\r
477 /// <returns>A Dictionary containing the language and iso code</returns>
\r
478 public static IDictionary<string, string> MapLanguages()
\r
480 IDictionary<string, string> languageMap = new Dictionary<string, string>
\r
484 {"Abkhazian", "abk"},
\r
485 {"Afrikaans", "afr"},
\r
487 {"Albanian", "sqi"},
\r
488 {"Amharic", "amh"},
\r
489 {"Arabic", "ara"},
\r
490 {"Aragonese", "arg"},
\r
491 {"Armenian", "hye"},
\r
492 {"Assamese", "asm"},
\r
493 {"Avaric", "ava"},
\r
494 {"Avestan", "ave"},
\r
495 {"Aymara", "aym"},
\r
496 {"Azerbaijani", "aze"},
\r
497 {"Bashkir", "bak"},
\r
498 {"Bambara", "bam"},
\r
499 {"Basque", "eus"},
\r
500 {"Belarusian", "bel"},
\r
501 {"Bengali", "ben"},
\r
502 {"Bihari", "bih"},
\r
503 {"Bislama", "bis"},
\r
504 {"Bosnian", "bos"},
\r
505 {"Breton", "bre"},
\r
506 {"Bulgarian", "bul"},
\r
507 {"Burmese", "mya"},
\r
508 {"Catalan", "cat"},
\r
509 {"Chamorro", "cha"},
\r
510 {"Chechen", "che"},
\r
511 {"Chinese", "zho"},
\r
512 {"Church Slavic", "chu"},
\r
513 {"Chuvash", "chv"},
\r
514 {"Cornish", "cor"},
\r
515 {"Corsican", "cos"},
\r
519 {"Divehi", "div"},
\r
520 {"Nederlands", "nld"},
\r
521 {"Dzongkha", "dzo"},
\r
522 {"English", "eng"},
\r
523 {"Esperanto", "epo"},
\r
524 {"Estonian", "est"},
\r
526 {"Faroese", "fao"},
\r
527 {"Fijian", "fij"},
\r
529 {"Francais", "fra"},
\r
530 {"Western Frisian", "fry"},
\r
532 {"Georgian", "kat"},
\r
533 {"Deutsch", "deu"},
\r
534 {"Gaelic (Scots)", "gla"},
\r
536 {"Galician", "glg"},
\r
538 {"Greek Modern", "ell"},
\r
539 {"Guarani", "grn"},
\r
540 {"Gujarati", "guj"},
\r
541 {"Haitian", "hat"},
\r
543 {"Hebrew", "heb"},
\r
544 {"Herero", "her"},
\r
546 {"Hiri Motu", "hmo"},
\r
547 {"Magyar", "hun"},
\r
549 {"Islenska", "isl"},
\r
551 {"Sichuan Yi", "iii"},
\r
552 {"Inuktitut", "iku"},
\r
553 {"Interlingue", "ile"},
\r
554 {"Interlingua", "ina"},
\r
555 {"Indonesian", "ind"},
\r
556 {"Inupiaq", "ipk"},
\r
557 {"Italiano", "ita"},
\r
558 {"Javanese", "jav"},
\r
559 {"Japanese", "jpn"},
\r
560 {"Kalaallisut", "kal"},
\r
561 {"Kannada", "kan"},
\r
562 {"Kashmiri", "kas"},
\r
563 {"Kanuri", "kau"},
\r
564 {"Kazakh", "kaz"},
\r
565 {"Central Khmer", "khm"},
\r
566 {"Kikuyu", "kik"},
\r
567 {"Kinyarwanda", "kin"},
\r
568 {"Kirghiz", "kir"},
\r
571 {"Korean", "kor"},
\r
572 {"Kuanyama", "kua"},
\r
573 {"Kurdish", "kur"},
\r
576 {"Latvian", "lav"},
\r
577 {"Limburgan", "lim"},
\r
578 {"Lingala", "lin"},
\r
579 {"Lithuanian", "lit"},
\r
580 {"Luxembourgish", "ltz"},
\r
581 {"Luba-Katanga", "lub"},
\r
583 {"Macedonian", "mkd"},
\r
584 {"Marshallese", "mah"},
\r
585 {"Malayalam", "mal"},
\r
587 {"Marathi", "mar"},
\r
589 {"Malagasy", "mlg"},
\r
590 {"Maltese", "mlt"},
\r
591 {"Moldavian", "mol"},
\r
592 {"Mongolian", "mon"},
\r
594 {"Navajo", "nav"},
\r
595 {"Ndebele, South", "nbl"},
\r
596 {"Ndebele, North", "nde"},
\r
597 {"Ndonga", "ndo"},
\r
598 {"Nepali", "nep"},
\r
599 {"Norwegian Nynorsk", "nno"},
\r
600 {"Norwegian Bokmål", "nob"},
\r
602 {"Chichewa; Nyanja", "nya"},
\r
603 {"Occitan", "oci"},
\r
604 {"Ojibwa", "oji"},
\r
607 {"Ossetian", "oss"},
\r
608 {"Panjabi", "pan"},
\r
609 {"Persian", "fas"},
\r
611 {"Polish", "pol"},
\r
612 {"Portugues", "por"},
\r
613 {"Pushto", "pus"},
\r
614 {"Quechua", "que"},
\r
615 {"Romansh", "roh"},
\r
616 {"Romanian", "ron"},
\r
618 {"Russian", "rus"},
\r
620 {"Sanskrit", "san"},
\r
621 {"Serbian", "srp"},
\r
622 {"Hrvatski", "hrv"},
\r
623 {"Sinhala", "sin"},
\r
624 {"Slovak", "slk"},
\r
625 {"Slovenian", "slv"},
\r
626 {"Northern Sami", "sme"},
\r
627 {"Samoan", "smo"},
\r
629 {"Sindhi", "snd"},
\r
630 {"Somali", "som"},
\r
631 {"Sotho Southern", "sot"},
\r
632 {"Espanol", "spa"},
\r
633 {"Sardinian", "srd"},
\r
635 {"Sundanese", "sun"},
\r
636 {"Swahili", "swa"},
\r
637 {"Svenska", "swe"},
\r
638 {"Tahitian", "tah"},
\r
641 {"Telugu", "tel"},
\r
643 {"Tagalog", "tgl"},
\r
645 {"Tibetan", "bod"},
\r
646 {"Tigrinya", "tir"},
\r
648 {"Tswana", "tsn"},
\r
649 {"Tsonga", "tso"},
\r
650 {"Turkmen", "tuk"},
\r
651 {"Turkish", "tur"},
\r
653 {"Uighur", "uig"},
\r
654 {"Ukrainian", "ukr"},
\r
658 {"Vietnamese", "vie"},
\r
659 {"Volapük", "vol"},
\r
661 {"Walloon", "wln"},
\r
664 {"Yiddish", "yid"},
\r
665 {"Yoruba", "yor"},
\r
666 {"Zhuang", "zha"},
\r
669 return languageMap;
\r
673 /// Change a string to Title Case/
\r
675 /// <param name="input">
\r
679 /// A string in title case.
\r
681 public static string TitleCase(string input)
\r
683 string[] tokens = input.Split(' ');
\r
684 StringBuilder sb = new StringBuilder(input.Length);
\r
685 foreach (string s in tokens)
\r
687 if (!string.IsNullOrEmpty(s))
\r
689 sb.Append(s[0].ToString().ToUpper());
\r
690 sb.Append(s.Substring(1).ToLower());
\r
695 return sb.ToString().Trim();
\r
699 /// Show the Exception Window
\r
701 /// <param name="shortError">
\r
702 /// The short error.
\r
704 /// <param name="longError">
\r
705 /// The long error.
\r
707 public static void ShowExceptiowWindow(string shortError, string longError)
\r
709 errorService.ShowError(shortError, longError);
\r
713 /// Get The Source from the CLI Query
\r
715 /// <param name="query">Full CLI Query</param>
\r
716 /// <returns>The Source Path</returns>
\r
717 public static string GetSourceFromQuery(string query)
\r
719 int startIndex = query.IndexOf("-i \"");
\r
720 if (startIndex != -1)
\r
722 string input = query.Substring(startIndex).Replace("-i \"", string.Empty).Trim();
\r
724 int closeIndex = input.IndexOf('"');
\r
726 return closeIndex == -1 ? "Unknown" : input.Substring(0, closeIndex);
\r
733 /// Get the Destination from the CLI Query
\r
735 /// <param name="query">Full CLI Query</param>
\r
736 /// <returns>The Destination path</returns>
\r
737 public static string GetDestinationFromQuery(string query)
\r
739 int startIndex = query.IndexOf("-o \"");
\r
740 if (startIndex != -1)
\r
742 string output = query.Substring(startIndex).Replace("-o \"", string.Empty).Trim();
\r
744 int closeIndex = output.IndexOf('"');
\r
746 return closeIndex == -1 ? "Unknown" : output.Substring(0, closeIndex);
\r