diff --git a/Userland/Applications/FileManager/CMakeLists.txt b/Userland/Applications/FileManager/CMakeLists.txt index 548c0ed585..baf6f811fc 100644 --- a/Userland/Applications/FileManager/CMakeLists.txt +++ b/Userland/Applications/FileManager/CMakeLists.txt @@ -7,6 +7,7 @@ serenity_component( compile_gml(FileManagerWindow.gml FileManagerWindowGML.h file_manager_window_gml) compile_gml(FileOperationProgress.gml FileOperationProgressGML.h file_operation_progress_gml) +compile_gml(PropertiesWindowAudioTab.gml PropertiesWindowAudioTabGML.h properties_window_audio_tab_gml) compile_gml(PropertiesWindowGeneralTab.gml PropertiesWindowGeneralTabGML.h properties_window_general_tab_gml) set(SOURCES @@ -21,8 +22,9 @@ set(SOURCES set(GENERATED_SOURCES FileManagerWindowGML.h FileOperationProgressGML.h + PropertiesWindowAudioTabGML.h PropertiesWindowGeneralTabGML.h ) serenity_app(FileManager ICON app-file-manager) -target_link_libraries(FileManager PRIVATE LibCore LibFileSystem LibGfx LibGUI LibDesktop LibConfig LibMain LibThreading) +target_link_libraries(FileManager PRIVATE LibAudio LibCore LibFileSystem LibGfx LibGUI LibDesktop LibConfig LibMain LibThreading) diff --git a/Userland/Applications/FileManager/PropertiesWindow.cpp b/Userland/Applications/FileManager/PropertiesWindow.cpp index 71c5039472..2c5bd97956 100644 --- a/Userland/Applications/FileManager/PropertiesWindow.cpp +++ b/Userland/Applications/FileManager/PropertiesWindow.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2022-2023, the SerenityOS developers. + * Copyright (c) 2023, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -9,7 +10,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -58,6 +61,7 @@ ErrorOr PropertiesWindow::create_widgets(bool disable_rename) auto tab_widget = TRY(main_widget->try_add()); TRY(create_general_tab(tab_widget, disable_rename)); + TRY(create_file_type_specific_tabs(tab_widget)); auto button_widget = TRY(main_widget->try_add()); TRY(button_widget->try_set_layout(GUI::Margins {}, 5)); @@ -196,6 +200,62 @@ ErrorOr PropertiesWindow::create_general_tab(GUI::TabWidget& tab_widget, b return {}; } +ErrorOr PropertiesWindow::create_file_type_specific_tabs(GUI::TabWidget& tab_widget) +{ + auto mapped_file_or_error = Core::MappedFile::map(m_path); + if (mapped_file_or_error.is_error()) { + warnln("{}: {}", m_path, mapped_file_or_error.release_error()); + return {}; + } + auto mapped_file = mapped_file_or_error.release_value(); + + auto file_name_guess = Core::guess_mime_type_based_on_filename(m_path); + auto mime_type = Core::guess_mime_type_based_on_sniffed_bytes(mapped_file->bytes()).value_or(file_name_guess); + + if (mime_type.starts_with("audio/"sv)) + return create_audio_tab(tab_widget, move(mapped_file)); + + return {}; +} + +ErrorOr PropertiesWindow::create_audio_tab(GUI::TabWidget& tab_widget, NonnullRefPtr mapped_file) +{ + auto loader_or_error = Audio::Loader::create(mapped_file->bytes()); + if (loader_or_error.is_error()) { + warnln("Failed to open '{}': {}", m_path, loader_or_error.release_error()); + return {}; + } + auto loader = loader_or_error.release_value(); + + auto tab = TRY(tab_widget.try_add_tab("Audio"_short_string)); + TRY(tab->load_from_gml(properties_window_audio_tab_gml)); + + tab->find_descendant_of_type_named("audio_type")->set_text(TRY(String::from_deprecated_string(loader->format_name()))); + auto duration_seconds = loader->total_samples() / loader->sample_rate(); + tab->find_descendant_of_type_named("audio_duration")->set_text(TRY(String::from_deprecated_string(human_readable_digital_time(duration_seconds)))); + tab->find_descendant_of_type_named("audio_sample_rate")->set_text(TRY(String::formatted("{} Hz", loader->sample_rate()))); + tab->find_descendant_of_type_named("audio_format")->set_text(TRY(String::formatted("{}-bit", loader->bits_per_sample()))); + + auto channel_count = loader->num_channels(); + String channels_string; + if (channel_count == 1 || channel_count == 2) { + channels_string = TRY(String::formatted("{} ({})", channel_count, channel_count == 1 ? "Mono"sv : "Stereo"sv)); + } else { + channels_string = TRY(String::number(channel_count)); + } + tab->find_descendant_of_type_named("audio_channels")->set_text(channels_string); + + tab->find_descendant_of_type_named("audio_title")->set_text(loader->metadata().title.value_or({})); + tab->find_descendant_of_type_named("audio_artists")->set_text(TRY(loader->metadata().all_artists()).value_or({})); + tab->find_descendant_of_type_named("audio_album")->set_text(loader->metadata().album.value_or({})); + tab->find_descendant_of_type_named("audio_track_number") + ->set_text(TRY(loader->metadata().track_number.map([](auto number) { return String::number(number); })).value_or({})); + tab->find_descendant_of_type_named("audio_genre")->set_text(loader->metadata().genre.value_or({})); + tab->find_descendant_of_type_named("audio_comment")->set_text(loader->metadata().comment.value_or({})); + + return {}; +} + void PropertiesWindow::update() { m_icon->set_bitmap(GUI::FileIconProvider::icon_for_path(make_full_path(m_name), m_mode).bitmap_for_size(32)); diff --git a/Userland/Applications/FileManager/PropertiesWindow.h b/Userland/Applications/FileManager/PropertiesWindow.h index 7791774af4..92aad1b460 100644 --- a/Userland/Applications/FileManager/PropertiesWindow.h +++ b/Userland/Applications/FileManager/PropertiesWindow.h @@ -29,6 +29,8 @@ private: PropertiesWindow(DeprecatedString const& path, Window* parent = nullptr); ErrorOr create_widgets(bool disable_rename); ErrorOr create_general_tab(GUI::TabWidget&, bool disable_rename); + ErrorOr create_file_type_specific_tabs(GUI::TabWidget&); + ErrorOr create_audio_tab(GUI::TabWidget&, NonnullRefPtr); struct PermissionMasks { mode_t read; diff --git a/Userland/Applications/FileManager/PropertiesWindowAudioTab.gml b/Userland/Applications/FileManager/PropertiesWindowAudioTab.gml new file mode 100644 index 0000000000..6f4f906fa4 --- /dev/null +++ b/Userland/Applications/FileManager/PropertiesWindowAudioTab.gml @@ -0,0 +1,228 @@ +@GUI::Widget { + layout: @GUI::VerticalBoxLayout { + margins: [8] + spacing: 12 + } + + @GUI::GroupBox { + title: "Audio" + preferred_height: "shrink" + layout: @GUI::VerticalBoxLayout { + margins: [12, 8, 0] + spacing: 2 + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Type:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "audio_type" + text: "MP3" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Duration:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "audio_duration" + text: "3:21" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Sample rate:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "audio_sample_rate" + text: "44100 Hz" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Format:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "audio_format" + text: "16-bit" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Channels:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "audio_channels" + text: "2 (Stereo)" + text_alignment: "TopLeft" + } + } + } + + @GUI::GroupBox { + title: "Track" + layout: @GUI::VerticalBoxLayout { + margins: [12, 8, 0] + spacing: 2 + } + + @GUI::Widget { + preferred_height: "shrink" + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Title:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "audio_title" + text: "Ana Ng" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + preferred_height: "shrink" + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Artist(s):" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "audio_artists" + text: "They Might Be Giants" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + preferred_height: "shrink" + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Album:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "audio_album" + text: "Lincoln" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + preferred_height: "shrink" + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Track number:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "audio_track_number" + text: "1" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + preferred_height: "shrink" + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Genre:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + name: "audio_genre" + text: "Alternative" + text_alignment: "TopLeft" + } + } + + @GUI::Widget { + preferred_height: "grow" + layout: @GUI::HorizontalBoxLayout { + spacing: 12 + } + + @GUI::Label { + text: "Comment:" + text_alignment: "TopLeft" + fixed_width: 80 + } + + @GUI::Label { + preferred_height: "grow" + name: "audio_comment" + text: "Ana Ng and I are getting old and we still haven't walked in the glow of each other's majestic presence." + text_alignment: "TopLeft" + } + } + } +}