{"id":16810098,"url":"https://github.com/antiguru/rahmen","last_synced_at":"2026-04-24T23:31:24.726Z","repository":{"id":48833288,"uuid":"323112197","full_name":"antiguru/rahmen","owner":"antiguru","description":"Rahmen is a lightweight tool to present a slideshow of images","archived":false,"fork":false,"pushed_at":"2021-08-29T17:25:12.000Z","size":314,"stargazers_count":2,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-19T22:08:07.719Z","etag":null,"topics":["image","raspberry-pi","rust","slideshow"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/antiguru.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-20T16:12:19.000Z","updated_at":"2021-08-29T17:25:15.000Z","dependencies_parsed_at":"2022-09-01T10:42:08.213Z","dependency_job_id":null,"html_url":"https://github.com/antiguru/rahmen","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/antiguru/rahmen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antiguru%2Frahmen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antiguru%2Frahmen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antiguru%2Frahmen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antiguru%2Frahmen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/antiguru","download_url":"https://codeload.github.com/antiguru/rahmen/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antiguru%2Frahmen/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32244999,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"ssl_error","status_checked_at":"2026-04-24T13:21:15.005Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["image","raspberry-pi","rust","slideshow"],"created_at":"2024-10-13T10:14:33.290Z","updated_at":"2026-04-24T23:31:24.709Z","avatar_url":"https://github.com/antiguru.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Rahmen: A lightweight image presenter\n\nRah·men [[ˈʁaːmən]](https://de.wiktionary.org/wiki/Rahmen) German: frame\n\nRahmen is a lightweight tool to present a slideshow of one or more JPEG images while consuming little resources. It\ntakes a list of files or a pattern, and periodically shows the next image. It's work in progress, but the code found\nhere should work.\n\nBelow the image, some information gathered from the image's metadata will be shown. This feature has to be configured in\nthe `rahmen.toml` configuration file. There, you can enter one or more metadata tags name known to\nthe [exiv2](https://exiv2.org/metadata.html) library to be displayed in the information line.\n\nAll the information items will be displayed on one line, with `\", \"` as (default, but read on)\nseparator. If this line is too long for the screen, some text will overflow and not be shown at the end of the line. Use\na wider screen or a narrower font to reduce the probability that this will happen. The font size is configurable using\nthe `--font_size` argument or the configuration file.\n\nBecause the data derived from the image's metadata tags is often difficult to read, ``rahmen``\noffers a wide range of tools to process the raw metadata.\n\nRahmen is not a soup.\n\n### Basic metadata processing\n\n#### Case conversion\n\nAs first step of the metadata processing chain, it is possible to convert the\ncase. [See below, where this setting is discussed in the context of the configuration file](#changing-the-case).\n\n#### Regular expressions for individual metadata\n\nFor each metadata entry, it's further possible to define pairs of\n[regular expressions and replacements](https://docs.rs/regex/) that will be applied to the metadata for each individual\ntag. Multiple regular expressions and replacements will be applied in the given\norder. [More details will be discussed in the context of the configuration file](#metadata).\n\nAfter this, the result will either be handed over to the [final processing step](#final-processing-step), or, before\nthat, undergo the advanced processing step.\n\n### Advanced processing using Python code\n\nIt is possible to include a Python script to process the string produced by the previous steps.\n\nAdd the following to the configuration file to call a script named ``postprocess.py`` in the same directory as\n``rahmen``:\n\n```toml\npy_postprocess = \"postprocess\"\npy_path = [\".\"]\n```\n\nThe Python code will be loaded once and executed for each new image. Be aware that this means that variables will be\nkept between images.\n\nThis Python code gets the line string and the separator string as positional arguments\n(in the order given here).\n\nThe main function of the Python code has to be named ``export``. It is required to return a callable taking the line\nstring and the separator string and returning a list of strings, representing the processed metadata items.\n\nOther than that, it is possible to flexibly process the incoming string and build the output accordingly. We have used a\npositional approach in our processing, which identifies a certain match in the metadata items list and then manipulates\nitems at a position relative to this match (see the ``postprocess.py`` example we have published).\n\nMore information can be\nfound [where this is discussed in the context of the configuration file](#advanced-metadata-processing-using-python).\n\n### Final processing step\n\nEmpty results for metadata tags will be dropped.\n\nMultiple occurrences of the same data will be reduced to one. It's possible to change this behaviour using\nthe ``uniquify`` entry in the configuration file.\n\nAfter this, the items will be joined using the default or configured separator.\n\nIt's also possible to construct the metadata output line yourself in Python. You will have to return it as a list of one\nitem, which will effectively prevent the final processing step.\n\n### Resource consumption\n\nRahmen is designed to run on low-power devices, such as the Raspberry Pi 1 (in fact it was specifically created to build\na digital picture frame out of an old monitor and an old Raspberry Pi 1 due to the lack of capable software). While it\nis not heavily optimized to consume little resources, some effort has been put into loading, pre-processing and\nrendering images.\n\n## Dependencies\n\nRahmen depends on various libraries, which should be available on most Linux distributions. Specifically, it needs:\n\n* `libgexiv2-dev`\n\nRahmen will run if there's no configuration file, but will use minimal defaults (see below), and no metadata will be\nshown.\n\n## Building\n\n`cargo build --bin rahmen`\n\n## Running\n\n```shell\n./rahmen --help\nRahmen client\n\nUSAGE:\nrahmen [OPTIONS] \u003cinput\u003e\n\nARGS:\n\u003cinput\u003e\n```\n\nThe input can either be a filename, a file pattern (`IMGP4*.jpg`), or a file containing a list of file names. If you'd\nlike to have a random image order, use the `find` and `shuf` commands to create a file list\n(see the provided shell script for an example).\n\n```shell\nFLAGS:\n-h, --help       Prints help information\n-V, --version    Prints version information\n\nOPTIONS:\n--buffer_max_size \u003cbuffer_max_size\u003e    [default: 16000000]\n```\n\nThe buffer size (in Bytes) determines the downscaling of images. All images that are larger than the buffer size in\nBytes will be scaled down to the buffer size. This should be larger than your monitor to avoid scaling\nartefacts/jaggies.\n\nRule of thumb: `long side of the monitor ^ 2 * 2`, e.g. for a 1600 * 1200 monitor: `1600 * 1600 * 2 = 5120000`.\n\n(Images smaller than your monitor will be scaled up to the monitor size and will possibly appear blurred. Avoid them if\nyou don't like this.)\n\n```shell\n-d, --display \u003cdisplay\u003e\nSelect the display provider [default: framebuffer] [possible values: framebuffer]\n```\n\n(If compiled with the FLTK option, the FLTK display provider will also be available, use `fltk` as value.)\n\n```shell\n        --font_size \u003cfont_size\u003e                \n```\n\nThe font size to use in px.\n\n```shell\n        --font \u003cfont\u003e\n            [default: /usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf]\n```\n\nRahmen will display information from the image's metadata (see above) in a single line below the image in the given\nfont. If the font is not found, the program exits. If you don't want to install lots of fonts, just point this option to\na TrueType font file.\n\n```shell\n    -o, --output \u003coutput\u003e                      \n    -t, --time \u003ctime\u003e                          [default: 90]\n```\n\nThe output points to the frame buffer to be used. Usually `/dev/fb0`.\n\nThe time (in seconds) defines the interval to change to the next slide. On the Raspberry Pi version 1, it takes several\nseconds to scale larger images. If the time given is shorter than what it takes to display the image, no images will be\nskipped, the image will be displayed to the next full second after it is fully loaded plus the time it takes to load the\nnext image. So on low-resource systems this should not be set too short, otherwise if the next image is very small, it\ncould lead to the image displaying for less than 1 second.\n\n```shell\n    -c, --config \u003cconfig file\u003e\n```\n\nIndicate the name and path of the configuration file to read. This takes precedence.\n\n### Shell script\n\nWe have added a basic bash script (in the ``utils`` directory) which creates a random image list from a given folder and\nstarts ``rahmen``. You could configure the machine to use autologin and call this script from the end of your\n``.bashrc`` to start a ``rahmen`` slideshow automatically after the system has started up. Of course, be sure to change\nto folders and paths to match your setup.\n\n## Configuration File (default name: rahmen.toml)\n\nRahmen will run without configuration file using the default settings given above, but no metadata will be displayed\nbelow the image. To show metadata, a configuration file must be used; an example file (`rahmen.toml`) can be found among\nthe sources.\n\nThe default lookup paths for the configuration file are either `~/.config/rahmen.toml` or `/etc/rahmen.toml`. If both\nare present, the file in the home directory takes precedence.\n\nThe configuration file has to be written in TOML and takes the following instructions:\n\n```toml\nfont_size = 24\ndelay = 90\n```\n\nValues for font size (px) and the interval before the next image (in s, see above, --time parameter). If command line\nparameters are given, they take precedence over the values in this file.\n\n### Displaying the time\n\nRahmen can optionally display the current time as part of the status line. To enable showing the current time, add the\nfollowing option to the configuration file:\n\n```toml\ndisplay_time = true\n```\n\nBy default, it uses a pattern of \"%H:%M:%S\" (\"14:28:22\"). The pattern can be replaced by a custom pattern in the\nconfiguration file:\n\n```toml\ntime_format = \"%H:%M\"\n```\n\nFor a reference of supported format specifiers,\nsee [Chrono's documentation](https://docs.rs/chrono/0.4.19/chrono/#formatting-and-parsing).\n\n### Metadata\n\n```toml\n[[status_line]]\nexif_tags = [\"Iptc.Application2.ObjectName\"]\n```\n\nEach `[[status_line]]` entry can contain one\n\n`exif-tags = [\"Some.Tag.Known.to.Exiv2\"]`\n\nentry, and optionally, one\n\n`replace = [{ regex = 'regex1', replace = 'repl1' }, { regex = '...', replace = '...' }, ... ]`\n\nentry, where one or more regular expressions and the replacements for the part they match could be supplied.\n\n[The regular expressions and replacements are documented here.](https://docs.rs/regex/)\n\nThe regular expression operations will be applied one after the other in the given order. For long expressions, or if\nyou wish to comment them, this could also be written like\n\n```toml\n[[status_line.replace]]\n# get named fields of the date\nregex = '(?P\u003cy\u003e\\d{4})[-:]0*(?P\u003cM\u003e\\d+)[-:]0*(?P\u003cd\u003e\\d+)\\s+0*(?P\u003ch\u003e\\d+:)0*(?P\u003cm\u003e\\d+):(?P\u003cs\u003e\\d{2})'\n## with time\n## replace = '$d.$M.$y, $h$m'\n# without time\nreplace = '$d.$M.$y'\n```\n\nThe [tag names that can be used are listed on the this exiv2 webpage](https://exiv2.org/metadata.html). This doesn't\nmean that all these are actually present in your image file. Use [exiftool](https://exiftool.org/)\nto show you the metadata in your file and see what is available.\n\n##### Changing the case\n\nBecause some of the tags we used were in ALL-CAPS which doesn't look nice, we offer case conversions that you can apply\nto the data _before_ they are processed by the regular expressions described above. The order in the configuration file\ndoesn't matter here. The [available case strings can be found here.](https://github.com/rutrum/convert-case#cases)\nSee the following example. The previous method of setting the `capitalize` variable is also still available.\n\n```toml\n# convert input from UPPER CASE to Title Case \ncase_conversion = { from = 'Upper', to = 'Title' }\n# this does the same, but only from UPPER to Title Case\ncapitalize = true\n```\n\n##### Custom separator\n\n```toml\nseparator = \"|\"\n```\n\nThat way it's possible to set a custom separator\n(the default is `\", \"`).\n\nThis ends the basic processing of the metadata. The information line produced by the rules given will be handed over to\nthe [final processing step](#final-processing-step), unless you decide to go further and process it using Python, which\nis described next, and after that, it will be shown below the image.\n\n#### Advanced metadata processing using Python\n\nIt's possible to use Python code that receives a list of the metadata tags, after they have been processed using all the\nindividual and per-line regex definitions, and process them there.\n\nAdd the following to the configuration file to call a script named ``postprocess.py`` in the same directory as\n``rahmen`` (the extension ``.py`` being quietly assumed):\n\n```toml\npy_postprocess = \"postprocess\"\npy_path = [\".\"]\n```\n\nThe main function of the Python code has to be named ``export``. It is required to return a callable taking the line\nstring and the separator string and returning a list of strings, representing the processed metadata items.\n\n``py_path`` defines where to look for the Python script. The value given here is prepended to\nthe [standard Python search path](https://docs.python.org/3/library/sys.html#sys.path), although the default search path\ndescribed there does not apply, because no regular script is called. To search the current directory, use ``\".\"``. Note:\nif you omit this entry and your script can be found neither via the ``$PYTHONPATH`` environment nor as a system module,\nit will not be possible to find the script, and the program will abort.\n\n##### Example script and test suite\n\nWe provide an example script (``postprocess.py``) where some processing is done for certain filters. To check the\nprocessing, we used ``pytest``. We provide a test script (``test.py``) matching the processing rules in the example\nscript. On our Debian system, invoking it with ``pytest-3 test.py`` runs the tests. It is strongly recommended to create\na test for every processing rule you create to ensure it is properly working.\n\nAfter the Python code has returned the list of processed entries, they will be handed over to\nthe [final processing step](#final-processing-step).\n\n##### How to get the tags\n\nThe human-readable location tags we use in the enclosed `rahmen.toml` example file are based on the information you can\ntell Adobe Lightroom to add when it finds a GPS location in the image metadata.\n\n## Bugs, Issues, Desiderata\n\n- Allow reacting to configuration file changes while running.\n- Allow for testing the whole text conversion chain, not only the Python part.\n- The font rendering is not really beautiful and sometimes, glyphs overlap.\n- The overflowing text is just not displayed.\n- The text bar might look better centered.\n\n## Compiling for the Raspberry Pi 1\n\nBecause some of the include C libraries wouldn't readily cross-compile, at this time we do not know of a way to\ncross-compile for the Raspberry Pi 1. Currently, we build Rahmen on a Raspberry Pi 4, and cross-compile to ARMv6 on this\nplatform- it works, although it's still a hack. At least compilation times are less than \"a night.\"\n\nOf course, building natively on a Pi 1 also works, but the term \"nightly build\" will have to be taken literally,\nespecially for the first run. Small changes to this source code only without the need to rebuild stuff depending on it\n(no new dependencies added) will take approximately 90...100 minutes.\n\n### Compiling on the Raspberry Pi 4\n\nUsing `cargo deb` to build a package for the Raspberry Pi 1 (armv6hf):\n\n```shell\nenv \\\n  PYO3_CROSS_LIB_DIR=\"/usr/lib\" \\\n  CFLAGS=\"-march=armv6\" \\\n  PKG_CONFIG_LIBDIR=/usr/lib/arm-linux-gnueabihf/pkgconfig/ \\\n  FREETYPE_CFLAGS=\"-I/usr/include/freetype2\" \\\n  FREETYPE_LIBS=\"-L/usr/lib -lfreetype\" \\\n  CC=gcc \\\n  AR=ar \\\n  PKG_CONFIG_ALLOW_CROSS=1 \\\n  cargo deb --target arm-unknown-linux-gnueabihf\n```\n\n### 4k on the Raspberry Pi 1\n\nThe Raspberry Pi 1 supports 4k resolution at reduced frame rates. The following configuration works on a screen we have\nat hands. It does not require overclocking, but limits the refresh rate to 15 frames per second, which seems to be fine\nwhen displaying mostly static content.\n\n```\ndisable_overscan=1\nhdmi_ignore_edid=0xa5000080\nhdmi_cvt=3840 2160 15\nframebuffer_width=3840\nframebuffer_height=2160\nhdmi_group=2\nhdmi_mode=87\nhdmi_pixel_freq_limit=400000000\nmax_framebuffer_width=3840\nmax_framebuffer_height=2160\n```\n\n### Previous attempts to cross-compile\n\nCross-compilation is a mess. The instructions below worked until we decided to include a dependency on `libgexiv2` to\nextract image metadata. This has some trouble cross-compiling and eventually, we decided not to spend more time on it.\n\nPreparation:\n\n1. Add the Rust toolchain:\n   ```\n   rustup target add arm-unknown-linux-gnueabihf\n   ```\n\n2. Set up the GCC toolchain. The first-generation Raspberry Pi had a BCM2835, supporting the ARMv6 instruction set.\n   Current ARM compilers on Debian only support armv7. For this reason, we need to use a different toolchain, for\n   example the one provided specifically for the Raspberry Pi\n   on [github.com/raspberrypi/tools](https://github.com/raspberrypi/tools). Export its `bin` directory to the local\n   path.\n\n   Tell Cargo to use the correct cross-compiler by adding the following content to `~/.cargo/config.toml`\n   or `.cargo/config.toml` in the project directory:\n\n   ```toml\n   [target.arm-unknown-linux-gnueabihf]\n   linker = \"arm-linux-gnueabihf-gcc\"\n   ar = \"arm-linux-gnueabihf-ar\"\n   ```\n\n   Add the toolchain to the current environment by adding it to the path:\n\n   ```shell\n   git clone https://github.com/raspberrypi/tools\n   export PATH=\"$PATH:$(pwd)/tools/arm-bcm2708/arm-linux-gnueabihf/bin/\"\n   ```\n\n3. Add the `armhf` target to Debian and install a dependency:\n\n   ```shell\n   dpkg --add-architecture armhf\n   apt install libgexiv2-dev:armhf libfontconfig1-dev:armhf\n   ```\n\nNow, issue the following command to cross-compile the binary.\n\n```shell\ncargo build --target arm-unknown-linux-gnueabihf --bin rahmen \\\n  --release\n```\n\nIf the build fails in `font-kit` with a message that the C compiler cannot produce executables, try to force CC and AR\nusing the following command line:\n\n```shell\nAR=arm-linux-gnueabihf-ar CC=arm-linux-gnueabihf-gcc cargo build \\\n  --target arm-unknown-linux-gnueabihf --bin rahmen \\\n  --release --no-default-features\n```\n\nFind the binary in `target/arm-unknown-linux-gnueabihf/release/rahmen`\n\n### Stripping the binary\n\nThe binary includes debug symbols, which consume a rather large amount of space. The `strip` tool can be used to remove\nthe debug symbols from the binary:\n\n`arm-linux-gnueabihf-strip target/arm-unknown-linux-gnueabihf/release/rahmen`\n\n## FLTK support\n\nThe FLTK renders a window on various platforms, which can be used for development.\n\nThe feature `fltk` is not enabled by default. Pass `--features fltk` to `cargo build` to enable.\n\n## License\n\nRahmen is licensed under the terms of the GNU General Public License version 3. See the [LICENSE](LICENSE) file for a\ncopy.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantiguru%2Frahmen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantiguru%2Frahmen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantiguru%2Frahmen/lists"}