Smaller fixes (#795)

* Improve a little documentation, add missing bad extension

* Allow to find the smallest files from CLo

* Show error when all directories are reference folders

* 2 more testing objects

* Add missing original preset
This commit is contained in:
Rafał Mikrut 2022-07-31 08:45:19 +02:00 committed by GitHub
parent 67db713a05
commit 44fb75ada5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 196 additions and 38 deletions

View File

@ -44,7 +44,6 @@ jobs:
run: cargo build --release --features heif
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0 -D warnings"
if: ${{ matrix.type == 'release'}}
- name: Store MacOS CLI

View File

@ -1,3 +1,10 @@
## Version 5.0.1 - .08.2022r
- Fixed problem with removing ending slash with empty disk window path
- Added to CLI bad extensions mode
- Fixed default sorting method in CLI where finding biggest files
- Added tests to CI
- Show error message when all directories are set as reference folders
## Version 5.0.0 - 28.07.2022r
- GUI ported to use GTK 4 - [#466](https://github.com/qarmin/czkawka/pull/466)
- Use multithreading and improved algorithm to compare image hashes - [#762](https://github.com/qarmin/czkawka/pull/762)

View File

@ -27,6 +27,13 @@
![Czkawka](https://user-images.githubusercontent.com/41945903/145280350-506f7e94-4db0-4de7-a68d-6e7c26bbd2bf.gif)
## Supported OS
Linux - Ubuntu 22.04+, Fedora 36+, Alpine Linux 3.16+, Debian 12+ and a lot of more
Windows - 7, 8.1, 10, 11
MacOS - 10.15+
If you are looking for older version that use GTK 3 and have support for more OS(like e.g. Ubuntu 20.04), look at [4.1.0](https://github.com/qarmin/czkawka/releases/tag/4.1.0) or older versions.
## How do I use it?
You can find the instructions on how to use Czkawka [**here**](instructions/Instruction.md).
@ -140,6 +147,7 @@ You can help by creating:
If the change is bigger, then it's a good idea to open a new issue to discuss changes, but issues with label `PR welcome` are already checked and accepted.
- Documentation - There is an [instruction](instructions/Instruction.md) which you can improve.
- Translations - Instruction how to translate files is available [here](instructions/Translations.md)
- External contributions - App use big number of external libraries like [lofty](https://github.com/Serial-ATA/lofty-rs), [image-rs](https://github.com/image-rs/image) or [symphonia](https://github.com/pdeljanov/Symphonia) so improving this libraries will automatically improve Czkawka
You can also help by doing other things:
- Creating text articles - [LinuxUprising](https://www.linuxuprising.com/2021/03/find-and-remove-duplicate-files-similar.html) or [Ubunlog](https://ubunlog.com/en/czkawka-finds-and-removes-empty-and-broken-duplicate-files/)

View File

@ -36,6 +36,8 @@ pub enum Commands {
file_to_save: FileToSave,
#[clap(flatten)]
not_recursive: NotRecursive,
#[clap(flatten)]
case_sensitive_name_comparison: CaseSensitiveNameComparison,
#[cfg(target_family = "unix")]
#[clap(flatten)]
exclude_other_filesystems: ExcludeOtherFilesystems,
@ -60,7 +62,7 @@ pub enum Commands {
#[clap(flatten)]
exclude_other_filesystems: ExcludeOtherFilesystems,
},
#[clap(name = "big", about = "Finds big files", help_message = HELP_MESSAGE, after_help = "EXAMPLE:\n czkawka big -d /home/rafal/ /home/piszczal -e /home/rafal/Roman -n 25 -x VIDEO -f results.txt")]
#[clap(name = "big", about = "Finds big files", help_message = HELP_MESSAGE, after_help = "EXAMPLE:\n czkawka big -d /home/rafal/ /home/piszczal -e /home/rafal/Roman -n 25 -J -x VIDEO -f results.txt")]
BiggestFiles {
#[clap(flatten)]
directories: Directories,
@ -78,6 +80,8 @@ pub enum Commands {
file_to_save: FileToSave,
#[clap(flatten)]
not_recursive: NotRecursive,
#[clap(short = 'J', long, help = "Finds the smallest files instead the biggest")]
smallest_mode: bool,
#[cfg(target_family = "unix")]
#[clap(flatten)]
exclude_other_filesystems: ExcludeOtherFilesystems,
@ -130,7 +134,7 @@ pub enum Commands {
minimal_file_size: u64,
#[clap(short = 'i', long, parse(try_from_str = parse_maximal_file_size), default_value = "18446744073709551615", help = "Maximum size in bytes", long_help = "Maximum size of checked files in bytes, assigning lower value may speed up searching")]
maximal_file_size: u64,
#[clap(short, long, default_value = "High", parse(try_from_str = parse_similar_images_similarity), help = "Similairty level (Minimal, VerySmall, Small, Medium, High, VeryHigh)", long_help = "Methods to choose similarity level of images which will be considered as duplicated.")]
#[clap(short, long, default_value = "High", parse(try_from_str = parse_similar_images_similarity), help = "Similairty level (Minimal, VerySmall, Small, Medium, High, VeryHigh, Original)", long_help = "Methods to choose similarity level of images which will be considered as duplicated.")]
similarity_preset: SimilarityPreset,
#[clap(flatten)]
excluded_items: ExcludedItems,
@ -143,7 +147,7 @@ pub enum Commands {
exclude_other_filesystems: ExcludeOtherFilesystems,
#[clap(short = 'g', long, default_value = "Gradient", parse(try_from_str = parse_similar_hash_algorithm), help = "Hash algorithm (allowed: Mean, Gradient, Blockhash, VertGradient, DoubleGradient)")]
hash_alg: HashAlg,
#[clap(short = 'z', long, default_value = "Lanczos3", parse(try_from_str = parse_similar_image_filter), help = "Hash algorithm (allowed: Lanczos3, Nearest, Triangle, Faussian, Catmullrom)")]
#[clap(short = 'z', long, default_value = "Nearest", parse(try_from_str = parse_similar_image_filter), help = "Hash algorithm (allowed: Lanczos3, Nearest, Triangle, Faussian, Catmullrom)")]
image_filter: FilterType,
#[clap(short = 'c', long, default_value = "16", parse(try_from_str = parse_image_hash_size), help = "Hash size (allowed: 8, 16, 32, 64)")]
hash_size: u8,
@ -238,11 +242,26 @@ pub enum Commands {
#[clap(short = 't', long, parse(try_from_str = parse_tolerance), default_value = "10", help = "Video maximium difference (allowed values <0,20>)", long_help = "Maximum difference between video frames, bigger value means that videos can looks more and more different (allowed values <0,20>)")]
tolerance: i32,
},
#[clap(name = "tester", about = "Contains various test", help_message = HELP_MESSAGE, after_help = "EXAMPLE:\n czkawka tests -i")]
Tester {
#[clap(short = 'i', long = "test_image", help = "Test speed of hashing provided test.jpg image with different filters and methods.")]
test_image: bool,
#[clap(name = "ext", about = "Finds files with invalid extensions", help_message = HELP_MESSAGE, after_help = "EXAMPLE:\n czkawka broken -d /home/czokolada/ -f results.txt")]
BadExtensions {
#[clap(flatten)]
directories: Directories,
#[clap(flatten)]
excluded_directories: ExcludedDirectories,
#[clap(flatten)]
excluded_items: ExcludedItems,
#[clap(flatten)]
allowed_extensions: AllowedExtensions,
#[clap(flatten)]
file_to_save: FileToSave,
#[clap(flatten)]
not_recursive: NotRecursive,
#[cfg(target_family = "unix")]
#[clap(flatten)]
exclude_other_filesystems: ExcludeOtherFilesystems,
},
#[clap(name = "tester", about = "Small utility to test supported speed of ", help_message = HELP_MESSAGE, after_help = "EXAMPLE:\n czkawka tester")]
Tester {},
}
#[derive(Debug, clap::StructOpt)]
@ -317,6 +336,12 @@ pub struct AllowHardLinks {
pub allow_hard_links: bool,
}
#[derive(Debug, clap::StructOpt)]
pub struct CaseSensitiveNameComparison {
#[clap(short = 'l', long, help = "Use case sensitive name comparison")]
pub case_sensitive_name_comparison: bool,
}
#[derive(Debug, clap::StructOpt)]
pub struct DryRun {
#[clap(long, help = "Do nothing and print the operation that would happen.")]

View File

@ -5,17 +5,18 @@ use std::process;
use clap::Parser;
use commands::Commands;
use czkawka_core::big_file::SearchMode;
#[allow(unused_imports)] // It is used in release for print_results().
use czkawka_core::common_traits::*;
use czkawka_core::similar_images::test_image_conversion_speed;
use czkawka_core::{
bad_extensions::BadExtensions,
big_file::{self, BigFile},
broken_files::{self, BrokenFiles},
duplicate::DuplicateFinder,
empty_files::{self, EmptyFiles},
empty_folder::EmptyFolder,
invalid_symlinks,
invalid_symlinks::InvalidSymlinks,
invalid_symlinks::{self, InvalidSymlinks},
same_music::SameMusic,
similar_images::{return_similarity_from_similarity_preset, SimilarImages},
similar_videos::SimilarVideos,
@ -49,6 +50,7 @@ fn main() {
exclude_other_filesystems,
allow_hard_links,
dryrun,
case_sensitive_name_comparison,
} => {
let mut df = DuplicateFinder::new();
@ -67,6 +69,7 @@ fn main() {
df.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems);
df.set_ignore_hard_links(!allow_hard_links.allow_hard_links);
df.set_dryrun(dryrun.dryrun);
df.set_case_sensitive_name_comparison(case_sensitive_name_comparison.case_sensitive_name_comparison);
df.find_duplicates(None, None);
@ -123,6 +126,7 @@ fn main() {
#[cfg(target_family = "unix")]
exclude_other_filesystems,
delete_files,
smallest_mode,
} => {
let mut bf = BigFile::new();
@ -137,6 +141,9 @@ fn main() {
if delete_files {
bf.set_delete_method(big_file::DeleteMethod::Delete);
}
if smallest_mode {
bf.set_search_mode(SearchMode::SmallestFiles);
}
bf.find_big_files(None, None);
@ -425,12 +432,41 @@ fn main() {
vr.print_results();
vr.get_text_messages().print_messages();
}
Commands::Tester { test_image } => {
if test_image {
test_image_conversion_speed();
} else {
println!("At least one test should be choosen!");
Commands::BadExtensions {
directories,
excluded_directories,
excluded_items,
file_to_save,
not_recursive,
#[cfg(target_family = "unix")]
exclude_other_filesystems,
allowed_extensions,
} => {
let mut be = BadExtensions::new();
be.set_included_directory(directories.directories);
be.set_excluded_directory(excluded_directories.excluded_directories);
be.set_excluded_items(excluded_items.excluded_items);
be.set_allowed_extensions(allowed_extensions.allowed_extensions.join(","));
be.set_recursive_search(!not_recursive.not_recursive);
#[cfg(target_family = "unix")]
be.set_exclude_other_filesystems(exclude_other_filesystems.exclude_other_filesystems);
if let Some(file_name) = file_to_save.file_name() {
if !be.save_results_to_file(file_name) {
be.get_text_messages().print_messages();
process::exit(1);
}
}
be.find_bad_extensions_files(None, None);
#[cfg(not(debug_assertions))] // This will show too much probably unnecessary data to debug, comment line only if needed
be.print_results();
be.get_text_messages().print_messages();
}
Commands::Tester {} => {
test_image_conversion_speed();
}
}
}

View File

@ -469,7 +469,7 @@ impl BadExtensions {
self.information.number_of_files_with_bad_extension = self.bad_extensions_files.len();
Common::print_time(system_time, SystemTime::now(), "sort_images - reading data from files in parallel".to_string());
Common::print_time(system_time, SystemTime::now(), "bad extension finding".to_string());
// Clean unused data
self.files_to_check = Default::default();
@ -539,7 +539,7 @@ impl SaveResults for BadExtensions {
if !self.bad_extensions_files.is_empty() {
writeln!(writer, "Found {} files with invalid extension.", self.information.number_of_files_with_bad_extension).unwrap();
for file_entry in self.bad_extensions_files.iter() {
writeln!(writer, "{}", file_entry.path.display()).unwrap();
writeln!(writer, "{} ----- {}", file_entry.path.display(), file_entry.proper_extensions).unwrap();
}
} else {
write!(writer, "Not found any files with invalid extension.").unwrap();
@ -556,7 +556,7 @@ impl PrintResults for BadExtensions {
let start_time: SystemTime = SystemTime::now();
println!("Found {} files with invalid extension.\n", self.information.number_of_files_with_bad_extension);
for file_entry in self.bad_extensions_files.iter() {
println!("{}", file_entry.path.display());
println!("{} ----- {}", file_entry.path.display(), file_entry.proper_extensions);
}
Common::print_time(start_time, SystemTime::now(), "print_entries".to_string());

View File

@ -464,9 +464,12 @@ impl SaveResults for BigFile {
}
if self.information.number_of_real_files != 0 {
write!(writer, "{} the biggest files.\n\n", self.information.number_of_real_files).unwrap();
for (size, file_entry) in self.big_files.iter().rev() {
if self.search_mode == SearchMode::BiggestFiles {
write!(writer, "{} the biggest files.\n\n", self.information.number_of_real_files).unwrap();
} else {
write!(writer, "{} the smallest files.\n\n", self.information.number_of_real_files).unwrap();
}
for (size, file_entry) in self.big_files.iter() {
writeln!(writer, "{} ({}) - {}", size.file_size(options::BINARY).unwrap(), size, file_entry.path.display()).unwrap();
}
} else {
@ -480,9 +483,13 @@ impl SaveResults for BigFile {
impl PrintResults for BigFile {
fn print_results(&self) {
let start_time: SystemTime = SystemTime::now();
for (size, file_entry) in self.big_files.iter().rev() {
// TODO Align all to same width
println!("{} ({} bytes) - {}", size.file_size(options::BINARY).unwrap(), size, file_entry.path.display());
if self.search_mode == SearchMode::BiggestFiles {
println!("{} the biggest files.\n\n", self.information.number_of_real_files);
} else {
println!("{} the smallest files.\n\n", self.information.number_of_real_files);
}
for (size, file_entry) in self.big_files.iter() {
println!("{} ({}) - {}", size.file_size(options::BINARY).unwrap(), size, file_entry.path.display());
}
Common::print_time(start_time, SystemTime::now(), "print_entries".to_string());
}

View File

@ -32,7 +32,7 @@ use crate::flc;
use crate::localizer_core::generate_translation_hashmap;
pub const SIMILAR_VALUES: [[u32; 6]; 4] = [
[0, 2, 5, 7, 14, 20], // 8
[1, 2, 5, 7, 14, 20], // 8
[2, 5, 15, 30, 40, 40], // 16
[4, 10, 20, 40, 40, 40], // 32
[6, 20, 40, 40, 40, 40], // 64
@ -59,6 +59,7 @@ pub struct FileEntry {
/// Used by CLI tool when we cannot use directly values
#[derive(Clone, Debug)]
pub enum SimilarityPreset {
Original,
VeryHigh,
High,
Medium,
@ -1283,6 +1284,7 @@ pub fn return_similarity_from_similarity_preset(similarity_preset: &SimilarityPr
_ => panic!(),
};
match similarity_preset {
SimilarityPreset::Original => 0,
SimilarityPreset::VeryHigh => SIMILAR_VALUES[index_preset][0],
SimilarityPreset::High => SIMILAR_VALUES[index_preset][1],
SimilarityPreset::Medium => SIMILAR_VALUES[index_preset][2],

View File

@ -463,6 +463,7 @@ invalid_symlink_infinite_recursion = Infinite recursion
invalid_symlink_non_existent_destination = Non-existent destination file
# Other
selected_all_reference_folders = Cannot start search, when all directories are set as reference folders
searching_for_data = Searching data, it may take a while, please wait...
text_view_messages = MESSAGES
text_view_warnings = WARNINGS

View File

@ -117,6 +117,13 @@ pub fn connect_button_search(
let check_button_settings_save_also_json = gui_data.settings.check_button_settings_save_also_json.clone();
buttons_search_clone.connect_clicked(move |_| {
// Check if user selected all referenced folders
let list_store_included_directories = get_list_store(&tree_view_included_directories);
if check_if_list_store_column_have_all_same_values(&list_store_included_directories, ColumnsIncludedDirectory::ReferenceButton as i32, true) {
entry_info.set_text(&flg!("selected_all_reference_folders"));
return;
}
let included_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_included_directories, ColumnsIncludedDirectory::Path as i32, None));
let excluded_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_excluded_directories, ColumnsExcludedDirectory::Path as i32, None));
let reference_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(

View File

@ -184,6 +184,7 @@ fn add_manually_directories(window_main: &Window, tree_view: &TreeView, excluded
dialog.close();
});
}
fn remove_ending_slashes(original_string: &mut String) {
let mut windows_disk_path: bool = false;
let mut chars = original_string.chars();
@ -202,6 +203,7 @@ fn remove_ending_slashes(original_string: &mut String) {
original_string.pop();
}
}
#[test]
pub fn test_remove_ending_slashes() {
let mut original = "/home/rafal".to_string();
@ -248,6 +250,14 @@ pub fn test_remove_ending_slashes() {
remove_ending_slashes(&mut original);
assert_eq!(&original, "C:/");
let mut original = "C:/roman/function/".to_string();
remove_ending_slashes(&mut original);
assert_eq!(&original, "C:/roman/function");
let mut original = "C:/staszek/without".to_string();
remove_ending_slashes(&mut original);
assert_eq!(&original, "C:/staszek/without");
let mut original = "C:\\\\\\\\\\".to_string();
remove_ending_slashes(&mut original);
assert_eq!(&original, "C:\\");

View File

@ -743,6 +743,24 @@ pub fn check_if_value_is_in_list_store(list_store: &ListStore, column: i32, valu
false
}
pub fn check_if_list_store_column_have_all_same_values(list_store: &ListStore, column: i32, value: bool) -> bool {
if let Some(iter) = list_store.iter_first() {
loop {
let list_store_value: bool = list_store.get::<bool>(&iter, column as i32);
if value != list_store_value {
return false;
}
if !list_store.iter_next(&iter) {
break;
}
}
return true;
}
false
}
#[cfg(test)]
mod test {
use gtk4::prelude::*;
@ -750,9 +768,44 @@ mod test {
use image::DynamicImage;
use crate::help_functions::{
change_dimension_to_krotka, check_if_value_is_in_list_store, get_all_boxes_from_widget, get_all_direct_children, get_max_file_name, get_pixbuf_from_dynamic_image,
change_dimension_to_krotka, check_if_list_store_column_have_all_same_values, check_if_value_is_in_list_store, get_all_boxes_from_widget, get_all_direct_children,
get_max_file_name, get_pixbuf_from_dynamic_image,
};
#[gtk4::test]
fn test_check_if_list_store_column_have_all_same_values() {
let columns_types: &[glib::types::Type] = &[glib::types::Type::BOOL];
let list_store = gtk4::ListStore::new(columns_types);
list_store.clear();
let values_to_add: &[(u32, &dyn ToValue)] = &[(0, &true), (0, &true), (0, &false)];
for i in values_to_add {
list_store.set(&list_store.append(), &[*i]);
}
assert!(!check_if_list_store_column_have_all_same_values(&list_store, 0, true));
assert!(!check_if_list_store_column_have_all_same_values(&list_store, 0, false));
list_store.clear();
let values_to_add: &[(u32, &dyn ToValue)] = &[(0, &true), (0, &true), (0, &true)];
for i in values_to_add {
list_store.set(&list_store.append(), &[*i]);
}
assert!(check_if_list_store_column_have_all_same_values(&list_store, 0, true));
assert!(!check_if_list_store_column_have_all_same_values(&list_store, 0, false));
list_store.clear();
let values_to_add: &[(u32, &dyn ToValue)] = &[(0, &false)];
for i in values_to_add {
list_store.set(&list_store.append(), &[*i]);
}
assert!(!check_if_list_store_column_have_all_same_values(&list_store, 0, true));
assert!(check_if_list_store_column_have_all_same_values(&list_store, 0, false));
list_store.clear();
assert!(!check_if_list_store_column_have_all_same_values(&list_store, 0, true));
assert!(!check_if_list_store_column_have_all_same_values(&list_store, 0, false));
}
#[gtk4::test]
fn test_check_if_value_is_in_list_store() {
let columns_types: &[glib::types::Type] = &[glib::types::Type::STRING];
@ -808,6 +861,7 @@ mod test {
get_pixbuf_from_dynamic_image(&dynamic_image).unwrap();
get_pixbuf_from_dynamic_image(&dynamic_image).unwrap();
}
#[test]
fn test_change_dimension_to_krotka() {
assert_eq!(change_dimension_to_krotka("50x50".to_string()), (50, 50));

View File

@ -153,7 +153,7 @@
<object class="GtkCheckButton" id="check_button_settings_hide_hard_links">
<property name="active">1</property>
<property name="focusable">1</property>
<property name="label" translatable="yes">Hide hard links(only Linux and MacOS)</property>
<property name="label" translatable="yes">Hide hard links(only Linux and macOS)</property>
</object>
</child>
<child>

View File

@ -19,7 +19,7 @@
</screenshot>
</screenshots>
<releases>
<release version="5.0.0" date="2021-07-28"/>
<release version="5.0.0" date="2022-07-28"/>
</releases>
<content_rating type="oars-1.0"/>
<developer_name>Rafał Mikrut</developer_name>

View File

@ -11,8 +11,8 @@ Support for heif images is optional and require to install libheif library.
| Program | Min | What for |
|---------|------|-------------------------------------------------------------------------------|
| Rust | 1.60 | Czkawka, aims to support the latest available version of Rust on Ubuntu 20.04 |
| GTK | 3.24 | Only for the `GTK` backend |
| Rust | 1.60 | Czkawka, aims to support the latest available version of Rust on Ubuntu 22.04 |
| GTK | 4.6 | Only for the `GTK` backend |
#### Debian / Ubuntu
```shell
@ -24,11 +24,11 @@ sudo apt install -y libgtk-4-dev
#### Fedora / CentOS / Rocky Linux
```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Download the latest stable Rust
sudo yum install gtk3-devel glib2-devel
sudo yum install gtk4-devel glib2-devel
```
#### macOS
You need to install Rust via Homebrew, GTK Libraries and optionally heif library(to have support for heic files, which are quite popular on mac)
You need to install Rust via Homebrew, GTK Libraries and optionally heif library(to have support for heic files, which are quite popular on Mac)
```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install rustup
@ -88,10 +88,9 @@ target/release/czkawka_gui
```
## Additional features
For now, finding broken audio files is temporarily disabled by default, because app crashes when audio libraries are not found on the computer.
I'm waiting for ability to disable audio playback feature, so after that I will be able to re-enable by default this feature (https://github.com/RustAudio/rodio/issues/349)
Currently, the only additional dependence is heif image support.
To enable checking for broken audio files, just add ` --all-features`
To enable checking for heif images, just add ` --all-features` or `--features heif`
```
cargo run --all-features --bin czkawka_cli -- broken -d /home/rafal/ -f "results.txt"
cargo run --features heif --bin czkawka_cli -- image -d /home/rafal/ -f "results.txt"
```

View File

@ -7,13 +7,16 @@ For Czkawka GUI the lowest supported version of GTK is `3.24` which is the only
In app exists Similar Video tool which require `FFmpeg` to work, but is completelly optional and without it, only warning would be printed when trying to use this tool without installed ffmpeg.
Broken files finder by default don't check for music files, but it is possible to enable this feature and that require to have alsa lib installed(on Ubuntu this is `libasound2-dev` package)
**Warning**
Main Czkawka GUI dependency - GTK 4 is only available on newer systems like e.g. Ubuntu 22.04, so if you have problems with running app on older systems, you may like to use older Czkawka versions, upgrade OS or install flatpak package which use its own set of dependencies.
#### Ubuntu/Debian/Linux Mint
```
sudo apt install libgtk-4-dev ffmpeg
```
#### Fedora/Rocky Linux
```
sudo yum install gtk3-devel glib2-devel
sudo yum install gtk4-devel glib2-devel
sudo dnf -y install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
sudo dnf -y install https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
sudo dnf -y install ffmpeg
@ -43,7 +46,7 @@ At the end execute it:
**Warning**
Prebuilt binaries are available only for x86_64, so if you use ARM machine like e.g. Mac M1, you need to compile manually app.
There is also a way to use x86_64 binaries on ARM, but this require to install special version of required libraries probably via:
There is also a way to use x86_64 binaries on ARM, but this requires to install special version of required libraries probably via:
```shell
arch -x86_64 /usr/local/bin/brew install gtk4 adwaita-icon-theme ffmpeg librsvg libheif
```