using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using file_finder__test; using ShadowStream; using ShadowStream.LogHelper; using ShadowStream.Obejeckte; using ShadowStream.ObjecktForJason; using ShadowStream.Modules; using LibVLCSharp.Shared; using ModuleManager.Modules; using Brushes = System.Windows.Media.Brushes; using Color = System.Windows.Media.Color; using File = System.IO.File; using Path = System.IO.Path; using System.Text.RegularExpressions; using System.Windows.Threading; using ShadowStream.Views; using Point = System.Windows.Point; namespace ModuleManager; /// /// Interaction logic for MainWindow.xaml /// //Quinn and Reda and Yazan public partial class MainWindow : Window { //quinn #region no dont cahnge //adding base components used threw out the code LogWindow logWin = new LogWindow(log.GetEntries()); static LogHelper log = new LogHelper(); PlaylistEditor popupWindow; ObjListBP _favorites = new ObjListBP(); ObjListBP _playlists = new ObjListBP(); private Catagory Muvie = new Catagory("Muvie"); private Catagory Serie = new Catagory("Serie"); private Catagory Music = new Catagory("Music"); private Catagory Photo = new Catagory("Photos"); List suportedVidioFiles = new List(); List suportedMusicFiles = new List(); List suportedPhotoFiles = new List(); //root directory List dirs = new List(); private int option = 2; int specificOption = 0; string rootPath = "G:/"; FileScanner fileScanner; //video player variables public string _path ; public string _category; StringConversions stringConversions = new StringConversions(); private DispatcherTimer _progressTimer; #endregion //Quinn #region vlc logic private LibVLC _libVLC; private LibVLCSharp.Shared.MediaPlayer _mediaPlayer; #endregion //code start public MainWindow() { #region Init wpf InitializeComponent(); //Initialise but Hide logWin.Show(); //logWin.Hide(); //this.Hide(); //Begin Login Process var login = new LogIn(); login.Show(); #endregion //Quinn Dont change or remuve!!! #region vlc init Core.Initialize(); // Important: load native libvlc binaries _libVLC = new LibVLC(); _mediaPlayer = new LibVLCSharp.Shared.MediaPlayer(_libVLC); VideoView.MediaPlayer = _mediaPlayer; _mediaPlayer.Volume = Convert.ToInt32(vol.Value); _progressTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; _progressTimer.Tick += ProgressTimer_Tick; #endregion #region only exdend. no remuving of code //adding all extensions... example values added suportedVidioFiles.Add(".mp4"); suportedVidioFiles.Add(".mkv"); suportedMusicFiles.Add(".wav"); suportedMusicFiles.Add(".mp3"); suportedPhotoFiles.Add(".jpg"); Createscan(); //dirs.Add("C:/"); //execute and wait for task completion var tmp = fileScanner.ScanAllDrivesAsync(); //load json parallel to finding drives //add code laiter tmp.Wait(); foreach (var VARIABLE in tmp.Result) { dirs.Add(VARIABLE); } #endregion LoadSavedData(); } //Quinn #region dont change //reminder to self. if issue acures. check if anyone changed folder names #region ScannLogic void Createscan() { //adding all string arrays to one temp array to do the initial scan of the designated drive int count = 0; string[] tmp = new string[suportedMusicFiles.Count + suportedVidioFiles.Count + suportedPhotoFiles.Count]; foreach (var suportedVidioFile in suportedVidioFiles) { tmp[count] = suportedVidioFile; count++; } foreach (var suportedMusicFile in suportedMusicFiles) { tmp[count] = suportedMusicFile; count++; } foreach (var suportedPhotoFile in suportedPhotoFiles) { tmp[count] = suportedPhotoFile; count++; } //initialise the file scanner with all files endings wanted fileScanner = new FileScanner(tmp); } #endregion #region Click Methods //-_- warum klapt es nicht private ProgressBar progressScann; private void OnItemPlayButtonClick(object sender, RoutedEventArgs e) { if (sender is Button btn && btn.Tag is string filePath) { string[] parts = filePath.Split("||"); string left = parts[0]; PlayVideo(left); } } private void OnItemNextButtonClick(object sender, RoutedEventArgs e) { setStrings(); switch (_category.ToLower()) { case "muvie": videoArrows(ref Muvie,true); break; case "serie": videoArrows(ref Serie,true);break; case "music": videoArrows(ref Music,true); break; case "photos": videoArrows(ref Photo,true); break; default: break; } } private void OnItemPriorButtonClick(object sender, RoutedEventArgs e) { setStrings(); switch (_category.ToLower()) { case "muvie": videoArrows(ref Muvie,false); break; case "serie": videoArrows(ref Serie,false);break; case "music": videoArrows(ref Music,false); break; case "photos": videoArrows(ref Photo,false); break; default: break; } } private void OnItemPauseBtn_Click(object sender, RoutedEventArgs e) { if (_mediaPlayer.IsPlaying) { _mediaPlayer.Pause(); // Pauses the video } else { _mediaPlayer.SetPause(false); // Unpauses the video } } private void Close_Player(object sender, RoutedEventArgs e) { _mediaPlayer.Stop(); VideoView.Visibility = Visibility.Collapsed; } private async void scanButton_Click(object sender, RoutedEventArgs e) { try { Muvie.clear(); Serie.clear(); Music.clear(); Photo.clear(); log.Log("Scanning files..."); List tmp = new List(); switch (option) { case 0: foreach (var VARIABLE in dirs) { var scanResult = await fileScanner.ScanDriveParallel(VARIABLE); tmp.AddRange(scanResult); } break; case 1: var scanResult1 = await fileScanner.ScanDriveParallel(dirs[specificOption]); tmp.AddRange(scanResult1); break; case 2: var scanResult2 = await fileScanner.ScanDriveParallel(rootPath); tmp.AddRange(scanResult2); break; } log.Log($"Total scanned files: {tmp.Count}"); var classifier = new FileClassifier(); var (musicFiles, videoFiles, photoFiles) = await classifier.ClassifyFilesAsync(tmp, suportedMusicFiles, suportedVidioFiles, suportedPhotoFiles); var separator = new VideoSeparator(); var (series, movies) = await separator.SeparateVideosAsync(videoFiles); log.Log($"musicFiles count: {musicFiles.Count}"); log.Log($"videoFiles count: {videoFiles.Count}"); log.Log($"photoFiles count: {photoFiles.Count}"); log.Log($"series count: {series.Count}"); log.Log($"movies count: {movies.Count}"); progressScann = new ProgressBar("ImageGen",musicFiles.Count + videoFiles.Count + photoFiles.Count); progressScann.Show(); videoFiles = null; log.Log("files sorted"); // Prepare JSON lists List muviesJS = new List(); List seriesJS = new List(); List photosJS = new List(); List mucicJS = new List(); JasonToString jasonToString = new JasonToString(); // Use async ItemCreaterAsync var movieItems = await ItemCreater(movies, "Muvie", false); foreach (var VARIABLE in movieItems) { Muvie.addItem(VARIABLE); muviesJS.Add(new locJason { path = VARIABLE.getLink(), imageData = jasonToString.BitmapToBase64String(BitmapConversions.BitmapImageToBitmap(VARIABLE.getImage())), type = VARIABLE.getType() }); } var seriesItems = await ItemCreater(series, "Serie", false); foreach (var VARIABLE in seriesItems) { Serie.addItem(VARIABLE); seriesJS.Add(new locJason { path = VARIABLE.getLink(), imageData = jasonToString.BitmapToBase64String(BitmapConversions.BitmapImageToBitmap(VARIABLE.getImage())), type = VARIABLE.getType() }); } var photoItems = await ItemCreater(photoFiles, "Photo", true); foreach (var VARIABLE in photoItems) { Photo.addItem(VARIABLE); photosJS.Add(new locJason { path = VARIABLE.getLink(), imageData = jasonToString.BitmapToBase64String(BitmapConversions.BitmapImageToBitmap(VARIABLE.getImage())), type = VARIABLE.getType() }); } var musicItems = await ItemCreater(musicFiles, "Music", false, true); foreach (var VARIABLE in musicItems) { Music.addItem(VARIABLE); mucicJS.Add(new locJason { path = VARIABLE.getLink(), imageData = jasonToString.BitmapToBase64String(BitmapConversions.BitmapImageToBitmap(VARIABLE.getImage())), type = VARIABLE.getType() }); } log.Log("scan finished"); Jason_Writer jason_Writer = new Jason_Writer(); List tasks = new List { jason_Writer.SaveList("Muvies", muviesJS), jason_Writer.SaveList("Series", seriesJS), jason_Writer.SaveList("Photos", photosJS), jason_Writer.SaveList("Music", mucicJS) }; await Task.WhenAll(tasks); // Clear lists to free memory mucicJS = null; seriesJS = null; photosJS = null; MenueItems(); progressScann.Hide(); MessageBox.Show("Scan finished"); } catch (Exception ex) { MessageBox.Show($"An error occurred during scanning:\n\n{ex}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); log.Error($"An error occurred during scanning:\n\n{ex}"); } } private void PlayListButton_Click(object sender, RoutedEventArgs e) { ObservableCollection tmpBP= new ObservableCollection(); tmpBP.Add(Muvie); tmpBP.Add(Serie); tmpBP.Add(Photo); tmpBP.Add(Music); popupWindow = new PlaylistEditor(tmpBP); popupWindow.Owner = this; popupWindow.ShowInTaskbar = false; popupWindow.Show(); } #region Catagory btns private void Home_OnClick(object sender, RoutedEventArgs e) { Close_Player(); ScrollContentCat.Visibility = Visibility.Collapsed; ScrollContentHome.Visibility = Visibility.Visible; MenueItems(); } private void Musik_OnClick(object sender, RoutedEventArgs e) { Close_Player(); Name_of_Catagory_Text1.Visibility = Visibility.Collapsed; ScrollContentHome.Visibility = Visibility.Collapsed; ScrollContentCat.Visibility = Visibility.Visible; Name_of_Catagory1.Visibility = Visibility.Collapsed; MenueItems(ref Music); } private void Photo_OnClick(object sender, RoutedEventArgs e) { Close_Player(); Name_of_Catagory_Text1.Visibility = Visibility.Collapsed; ScrollContentHome.Visibility = Visibility.Collapsed; ScrollContentCat.Visibility = Visibility.Visible; Name_of_Catagory1.Visibility = Visibility.Collapsed; MenueItems(ref Photo); } private void Video_OnClick(object sender, RoutedEventArgs e) { Close_Player(); Name_of_Catagory_Text1.Visibility = Visibility.Visible; ScrollContentHome.Visibility = Visibility.Collapsed; ScrollContentCat.Visibility = Visibility.Visible; Name_of_Catagory1.Visibility = Visibility.Visible; MenueItems(ref Muvie,ref Serie); } #endregion #endregion #region itemCreation async Task> ItemCreater(List paths, string type, bool isFoto) { var semaphore = new SemaphoreSlim(Math.Max(1, Environment.ProcessorCount / 2)); var tasks = paths.Select(async path => { await semaphore.WaitAsync(); try { BitmapImage frame200 = null; if (!isFoto) { frame200 = await Task.Run(() => VideoFrameExtractor.GetFrame200(path)); } else { await Application.Current.Dispatcher.InvokeAsync(() => { frame200 = new BitmapImage(new Uri(path, UriKind.Absolute)); frame200.DecodePixelWidth = 150; frame200.DecodePixelHeight = 100; }); } Application.Current.Dispatcher.Invoke(() => progressScann.UpdateProgress(1)); return new Item(path, type, frame200, isFoto, OnItemPlayButtonClick); } finally { semaphore.Release(); } }).ToList(); return (await Task.WhenAll(tasks)).ToList(); } public async Task> ItemCreater(List paths, string type, bool isFoto, bool isMusic = false) { var itemsBag = new ConcurrentBag(); var workQueue = new ConcurrentQueue(paths); int maxWorkers = Math.Max(1, Environment.ProcessorCount / 2); var workers = new List(); // Prepare default image for music if needed BitmapImage defaultImage = null; if (isMusic) { string baseDir = AppDomain.CurrentDomain.BaseDirectory; string imagePath = System.IO.Path.Combine(baseDir, "Resources", "Pics", "MusicD.jpeg"); if (System.IO.File.Exists(imagePath)) { await Application.Current.Dispatcher.InvokeAsync(() => { defaultImage = new BitmapImage(new Uri(imagePath)); defaultImage.DecodePixelWidth = 150; defaultImage.DecodePixelHeight = 100; defaultImage.Freeze(); }); } else { MessageBox.Show($"Default image not found at: {imagePath}"); } } for (int i = 0; i < maxWorkers; i++) { workers.Add(Task.Run(async () => { while (workQueue.TryDequeue(out var filePath)) { BitmapImage bitmapImage = null; try { if (isMusic) { bitmapImage = defaultImage; try { var file = TagLib.File.Create(filePath); if (file.Tag.Pictures.Length > 0) { var pic = file.Tag.Pictures[0]; var ms = new System.IO.MemoryStream(pic.Data.Data); await Application.Current.Dispatcher.InvokeAsync(() => { var img = new BitmapImage(); img.BeginInit(); img.CacheOption = BitmapCacheOption.OnLoad; ms.Position = 0; img.StreamSource = ms; img.EndInit(); img.Freeze(); img.DecodePixelWidth = 150; img.DecodePixelHeight = 100; bitmapImage = img; }); ms.Dispose(); } } catch { // keep defaultImage on failure } } } catch { // fallback: bitmapImage stays null or default } // Create Item on UI thread because it creates UI controls internally Item newItem = null; await Application.Current.Dispatcher.InvokeAsync(() => { newItem = new Item(filePath, type, bitmapImage, isFoto, OnItemPlayButtonClick); }); itemsBag.Add(newItem); Application.Current.Dispatcher.Invoke(() => progressScann.UpdateProgress(1)); } })); } await Task.WhenAll(workers); return itemsBag.ToList(); } #endregion #region Comunication public void addFavorit(ref Item item) { _favorites.SharedRefs.Add(item); } public void ReciveErrorLog(string logMessage) { log.Error(logMessage); } public void PlayVideo(string filePath) { if(suportedVidioFiles.Contains(Path.GetExtension(filePath))||suportedPhotoFiles.Contains(Path.GetExtension(filePath))) VideoView.Visibility = Visibility.Visible; var media = new Media(_libVLC, filePath, FromType.FromPath); media.ParsedChanged += (sender, args) => { if (args.ParsedStatus == MediaParsedStatus.Done) { Dispatcher.Invoke(() => { var duration = media.Duration; videoSliderInit(duration / 1000); }); } }; media.Parse(MediaParseOptions.ParseLocal); _mediaPlayer.Play(media); } public void setStrings() { #region type? var currentMedia = _mediaPlayer?.Media; if (currentMedia != null) { var mrl = currentMedia.Mrl; // This is the media resource locator _path = new Uri(mrl).LocalPath;; _path = stringConversions.ReplaceFirst(_path, '/', '\\'); Console.WriteLine("Now Playing Path: " + _path); } if (_mediaPlayer.IsPlaying) { _mediaPlayer.Stop(); } if (suportedMusicFiles.Any(keyword => _path.Contains(keyword, StringComparison.OrdinalIgnoreCase))) { _category = "music"; } else if (suportedPhotoFiles.Any(keyword => _path.Contains(keyword, StringComparison.OrdinalIgnoreCase))) { _category = "photos"; } else { if (Regex.IsMatch(_path, @"\b(E|EP|Ep|e|ep)\d+\b", RegexOptions.IgnoreCase)) { _category = "serie"; } else { _category = "muvie"; } } log.Log($"replacing: {_path} from {_category}"); #endregion } public void videoArrows(ref Catagory catagory, bool next) { catagory = catagory as Catagory; var items = catagory?.getAllItems(); if (catagory == null || items == null) { MessageBox.Show("Category or items are null"); return; } int tmp = catagory.contains(_path); if (tmp == -1 || tmp >= items.Count) { MessageBox.Show("Current item not found in category"); return; } Media media = null; if (next) { if (tmp + 1 < items.Count) { var link = items[tmp + 1]?.getLink(); if (!string.IsNullOrEmpty(link)) media = new Media(_libVLC, link, FromType.FromPath); } } if (!next) { int newIndex = (tmp - 1 >= 0) ? tmp - 1 : items.Count - 1; // wrap around var link = items[newIndex]?.getLink(); Console.WriteLine(link); if (!string.IsNullOrEmpty(link)) media = new Media(_libVLC, link, FromType.FromPath); } if (media != null) _mediaPlayer.Play(media); else MessageBox.Show("Could not load media. Media or link is null."); } private void Close_Player() { _mediaPlayer.Stop(); VideoView.Visibility = Visibility.Collapsed; } public void RecivePlaylist(ObjListBP items) { _playlists.SharedRefs.Add(items); RecivePlaylist(); } public void RecivePlaylist() { popupWindow.Close(); } #endregion #endregion #region Menue void MenueItems(ref Catagory catagory) { PopulatePanelWithItemsCatagorised(Name_of_Catagory,ref catagory); } void MenueItems(ref Catagory catagory,ref Catagory catagory1) { PopulatePanelWithItemsCatagorised(Name_of_Catagory,ref catagory); PopulatePanelWithItemsCatagorised(Name_of_Catagory1,ref catagory1); } void MenueItems() { PopulatePanelWithItems(Muvie.getAllItems(), Muvies_Home); PopulatePanelWithItems(Serie.getAllItems(), Series_Home); PopulatePanelWithItems(Photo.getAllItems(), Photos_Home); PopulatePanelWithItems(Music.getAllItems(), Music_Home); } #endregion #region PanelPop private void PopulatePanelWithItemsCatagorised(Panel targetPanel,ref Catagory catagory) { if (catagory.name == "Serie") { Name_of_Catagory_Text1.Text = catagory.name; } else { Name_of_Catagory_Text.Text = catagory.name; } PopulatePanelWithItems(catagory.getAllItems(),targetPanel); } private void PopulatePanelWithItems(List items, Panel targetPanel) { targetPanel.Children.Clear(); byte tmp = 0; foreach (var item in items) { if(tmp >= 10) break; if (ScrollContentHome.Visibility == Visibility.Visible) tmp++; if (tmp < 10) { var name = item.getName(); var image = item.getImage(); var isFoto = item.getType() == "Photo"; var path = item.getLink(); var buttonText = isFoto ? "Show" : "Play"; if (image == null) continue; // Skip if image is not available // Container for stacking label, image, and button var container = new Grid { Width = 150, Height = 120, Margin = new Thickness(5) }; // Define rows for layout container.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }); // Image container.RowDefinitions.Add(new RowDefinition { Height = new GridLength(30) }); // Button // Image var imgControl = new System.Windows.Controls.Image { Source = image, Width = 150, Height = 90, Stretch = Stretch.UniformToFill }; Grid.SetRow(imgControl, 0); container.Children.Add(imgControl); // Label (overlays top-left, optional) var label = new Label { Content = name, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, Background = new SolidColorBrush(Color.FromArgb(180, 0, 0, 0)), Foreground = Brushes.White, Padding = new Thickness(4), FontSize = 12 }; Grid.SetRow(label, 0); container.Children.Add(label); // Button var btn = new Button { Content = buttonText, Height = 25, Width = 140, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 0, 5) }; btn.Click += (s, e) => PlayVideo(path); Grid.SetRow(btn, 1); container.Children.Add(btn); // Add to target UI panel targetPanel.Children.Add(container); } } } #endregion #region load saved data private async void LoadSavedData() { try { Jason_Writer jason_Writer = new Jason_Writer(); // Use default "Temp Data" folder JasonToString jasonToString = new JasonToString(); var muvies = await jason_Writer.LoadListAsync("Muvies"); var series = await jason_Writer.LoadListAsync("Series"); var photos = await jason_Writer.LoadListAsync("Photos"); var music = await jason_Writer.LoadListAsync("Music"); progressScann = new ProgressBar("Reading saved data",muvies.Count+series.Count+photos.Count+music.Count); progressScann.Show(); List MuvieItems = CreateItemsFromJson(muvies); List SerieItems = CreateItemsFromJson(series); List PhotoItems = CreateItemsFromJson(photos, isPhoto: true); List MusicItems = CreateItemsFromJson(music, isPhoto: false, isMusic: true); Muvie.addItems(MuvieItems); Serie.addItems(SerieItems); Photo.addItems(PhotoItems); Music.addItems(MusicItems); MenueItems(); // Re-populate the UI progressScann.Hide(); log.Log("Loaded saved data from JSON."); } catch (Exception ex) { MessageBox.Show($"Failed to load saved data:\n{ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error); log.Error($"LoadSavedData error: {ex}"); } } private List CreateItemsFromJson(List jsonItems) { return CreateItemsFromJson(jsonItems, isPhoto: false, isMusic: false); } private List CreateItemsFromJson(List jsonItems, bool isPhoto) { return CreateItemsFromJson(jsonItems, isPhoto, isMusic: false); } private List CreateItemsFromJson(List jsonItems, bool isPhoto, bool isMusic) { List items = new List(); JasonToString jasonToString = new JasonToString(); foreach (var loc in jsonItems) { BitmapImage bitmapImage = null; try { if (!string.IsNullOrEmpty(loc.imageData)) { bitmapImage = BitmapConversions.BitmapToBitmapImage( jasonToString.Base64StringToBitmap(loc.imageData) ); } else if (isMusic) { string defaultImagePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Pics", "MusicD.jpeg"); if (File.Exists(defaultImagePath)) { bitmapImage = new BitmapImage(new Uri(defaultImagePath)); bitmapImage.Freeze(); } } else if (isPhoto && File.Exists(loc.path)) { bitmapImage = new BitmapImage(new Uri(loc.path)); bitmapImage.Freeze(); } } catch { // Handle any corrupt or missing image cases } items.Add(new Item(loc.path, loc.type, bitmapImage, isPhoto, OnItemPlayButtonClick)); Application.Current.Dispatcher.Invoke(() => { progressScann.UpdateProgress(1); }); } return items; } #endregion #region sliders private void RangeBase_OnValueChanged(object sender, RoutedPropertyChangedEventArgs e) { if (sender is Slider&&_mediaPlayer != null) { Slider slider = sender as Slider; int value = Convert.ToInt32(slider.Value); _mediaPlayer.Volume = value; log.Log("VolumeChanged"); } } #endregion #region progress slider private void ItemProgress_OnValueChanged(object sender, RoutedPropertyChangedEventArgs e) { if (itemProgress.IsMouseOver) { ProgressBarValueChange(); } } private void ItemProgress_OnValueChangedMil(long timeStap) { itemProgress.Value = timeStap/1000; itemProgressVisual.Value = itemProgress.Value; } private void ProgressBarValueChange() { itemProgressVisual.Value = itemProgress.Value; if(itemProgress.IsMouseOver) _mediaPlayer.Time = Convert.ToInt64(itemProgress.Value) * 1000; // convert to milliseconds } private void videoSliderInit(long timeStap) { itemProgress.Maximum = timeStap; itemProgressVisual.Maximum = timeStap; itemProgress.Minimum = 0; itemProgressVisual.Minimum = 0; StartProgressTimer(); Console.WriteLine(timeStap); } private void ItemProgress_OnPreviewMouseDown(object sender, MouseButtonEventArgs e) { var slider = sender as Slider; if (slider == null) return; Point position = e.GetPosition(slider); double relativePosition = position.X / slider.ActualWidth; relativePosition = Math.Max(0, Math.Min(1, relativePosition)); double newValue = slider.Minimum + (relativePosition * (slider.Maximum - slider.Minimum)); slider.Value = newValue; ProgressBarValueChange(); } private void ProgressTimer_Tick(object sender, EventArgs e) { if (!_mediaPlayer.IsPlaying) { StopProgressTimer(); } // Get current timestamp from media player in seconds long currentTimeInSeconds = _mediaPlayer.Time; // Update progress bar value ItemProgress_OnValueChangedMil(currentTimeInSeconds); } private void StartProgressTimer() { if (!_progressTimer.IsEnabled) _progressTimer.Start(); } private void StopProgressTimer() { if (_progressTimer.IsEnabled) _progressTimer.Stop(); } #endregion }