improve docs

pull/2715/head
Mikael Finstad 6 months ago
parent 260107d910
commit 260107c3c2
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -14,7 +14,7 @@ body:
required: true
- label: Try with the [newest version from GitHub](https://github.com/mifi/lossless-cut/releases/latest)
required: true
- label: Read the [documentation](https://github.com/mifi/lossless-cut) and [FAQ, Known issues, Troubleshooting](https://github.com/mifi/lossless-cut/blob/master/issues.md)
- label: Read the [documentation](https://github.com/mifi/lossless-cut) and [FAQ, Known issues, Troubleshooting](https://github.com/mifi/lossless-cut/blob/master/docs/index.md)
required: true
- label: Search for your problem under [Issues](https://github.com/mifi/lossless-cut/issues) or [Discussions](https://github.com/mifi/lossless-cut/discussions)
required: true

@ -8,7 +8,7 @@ body:
options:
- label: Try with the [newest version from GitHub](https://github.com/mifi/lossless-cut/releases/latest) (also it might have already been fixed in latest [nightly build](https://github.com/mifi/lossless-cut#nightly-builds-))
required: true
- label: Read the [documentation](https://github.com/mifi/lossless-cut) and [FAQ, Known issues, Troubleshooting](https://github.com/mifi/lossless-cut/blob/master/issues.md)
- label: Read the [documentation](https://github.com/mifi/lossless-cut) and [FAQ, Known issues, Troubleshooting](https://github.com/mifi/lossless-cut/blob/master/docs/index.md)
required: true
- label: Search for your problem under [Issues](https://github.com/mifi/lossless-cut/issues) or [Discussions](https://github.com/mifi/lossless-cut/discussions)
required: true

@ -10,7 +10,7 @@ body:
required: true
- label: Try with the [newest version from GitHub](https://github.com/mifi/lossless-cut/releases/latest)
required: true
- label: Read the [documentation](https://github.com/mifi/lossless-cut) and [FAQ, Known issues, Troubleshooting](https://github.com/mifi/lossless-cut/blob/master/issues.md) to see if the feature already exists
- label: Read the [documentation](https://github.com/mifi/lossless-cut) and [FAQ, Known issues, Troubleshooting](https://github.com/mifi/lossless-cut/blob/master/docs/index.md) to see if the feature already exists
required: true
- label: See if someone already suggested this under [Issues](https://github.com/mifi/lossless-cut/issues) or [Discussions](https://github.com/mifi/lossless-cut/discussions)
required: true

@ -28,4 +28,12 @@ jobs:
- run: yarn tsc
- run: yarn lint
- run: yarn build
- run: yarn generate-licenses
- name: Check docs
run: |
yarn generate-docs
git diff --quiet HEAD
git reset --hard HEAD
- name: Generate licenses
run: |
yarn generate-licenses
git reset --hard HEAD

@ -1,6 +1,6 @@
# Contributing
## [Translation](translation.md)
## [Translations](docs/translation.md)
## Development environment setup
@ -115,11 +115,11 @@ For per-platform build/signing setup, see [this article](https://mifi.no/blog/au
Find the [latest PR](https://github.com/mifi/lossless-cut/pulls) from Weblate and **rebase+merge** it.
**Warning:** Do not squash and merge (see [here why](translation.md#weblate))!
**Warning:** Do not squash and merge (see [here why](docs/translation.md#weblate))!
## Minimum OS version
See [requirements](./requirements.md).
See [requirements](docs/requirements.md).
### MacOS [`LSMinimumSystemVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/lsminimumsystemversion)

@ -39,7 +39,7 @@ The main feature is lossless trimming and cutting of video and audio files, whic
- [Example lossless use cases](#example-lossless-use-cases)
- [Download](#download)
- [Supported formats](#supported-formats)
- [Documentation & getting started](#documentation)
- [Documentation, usage & getting started](#documentation)
- [Video demos](#video-demos)
- [Featured](#featured)
- [Attributions](#attributions)
@ -75,14 +75,14 @@ The main feature is lossless trimming and cutting of video and audio files, whic
- Undo/redo
- Advanced segment query and mutation JS-based expression language.
- Give labels to cut segments, annotate with tags
- [Import/export](docs.md#import--export-projects) segments: MP4/MKV chapter marks, Text file, YouTube, CSV, CUE, XML (DaVinci, Final Cut Pro) and more
- [Import/export](docs/index.md#importexport-projects) segments: MP4/MKV chapter marks, Text file, YouTube, CSV, CUE, XML (DaVinci, Final Cut Pro) and more
- MKV/MP4 embedded chapters marks editor
- View subtitles
- Customizable keyboard hotkeys
- Black scene detection, silent audio detection, and scene change detection
- Divide timeline into segments of length L, size (X MB), N number of segments or even randomized segments!
- Speed up / slow down video or audio file ([changing FPS](https://github.com/mifi/lossless-cut/issues/1712))
- Basic [CLI](cli.md) and [HTTP API](api.md)
- Basic [CLI](docs/cli.md) and [HTTP API](docs/api.md)
- Show (DJI) embedded GPS track on a map
- Losslessly Download videos over HTTP (e.g. HLS `.m3u8`)
@ -110,7 +110,7 @@ The main feature is lossless trimming and cutting of video and audio files, whic
- Split video into segments to for example respect Twitter's 140 second limit.
- Annotate each segment with one or more tags, then use those tags to organize your segments or use it to create an output folder structure or hierarchy for your segments.
See also [Recipe cookbook](./recipes.md)
See also [Recipe cookbook](docs/recipes.md)
## Download
@ -122,15 +122,15 @@ For Linux these are some alternatives:
<a href="https://snapcraft.io/losslesscut"><img src="https://github.com/mifi/lossless-cut/raw/master/snap-store-black.svg?sanitize=true" alt="Snapcraft" height="50"/></a> <a href="https://flathub.org/apps/details/no.mifi.losslesscut"><img src="https://github.com/mifi/lossless-cut/raw/master/flathub-badge.svg?sanitize=true" alt="Flathub" height="50"/></a>
If you prefer to download the executables manually, this will of course always be free (see also [supported operating systems](./requirements.md)):
If you prefer to download the executables manually, this will of course always be free (see also [supported operating systems](docs/requirements.md)):
- macOS: [Intel](https://github.com/mifi/lossless-cut/releases/latest/download/LosslessCut-mac-x64.dmg) / [Apple Silicon](https://github.com/mifi/lossless-cut/releases/latest/download/LosslessCut-mac-arm64.dmg) DMG (note that PKG does **not** work)
- Windows: [7zip](https://github.com/mifi/lossless-cut/releases/latest/download/LosslessCut-win-x64.7z) (Windows 7, 8 and 8.1 is [no longer supported after v3.50.0](./requirements.md))
- Windows: [7zip](https://github.com/mifi/lossless-cut/releases/latest/download/LosslessCut-win-x64.7z) (Windows 7, 8 and 8.1 is [no longer supported after v3.50.0](docs/requirements.md))
- Linux: [x64 tar.bz2](https://github.com/mifi/lossless-cut/releases/latest/download/LosslessCut-linux-x64.tar.bz2) / [x64 AppImage](https://github.com/mifi/lossless-cut/releases/latest/download/LosslessCut-linux-x86_64.AppImage) / [arm64 tar.bz2](https://github.com/mifi/lossless-cut/releases/latest/download/LosslessCut-linux-arm64.tar.bz2) / [Raspberry Pi armv7l](https://github.com/mifi/lossless-cut/releases/latest/download/LosslessCut-linux-armv7l.tar.bz2)
- [More releases](https://github.com/mifi/lossless-cut/releases) - Note that APPX (Windows) and PKG (macOS) do **not** work)
- [Latest nightly builds 🧪](https://mifi.no/llc/nightly/)
Difference between App Stores and GitHub download? [Please see FAQ](issues.md#app-stores-and-github-difference).
Difference between App Stores and GitHub download? [Please see FAQ](docs/index.md#faq).
[![](./donate.svg)](https://mifi.no/thanks)
@ -140,12 +140,12 @@ LosslessCut is maintained by me alone and will always remain free and open sourc
## Supported formats
LosslessCut uses the Chromium browser's HTML5 video player, and not all formats/codecs are [natively supported](https://www.chromium.org/audio-video). Generally, the following file formats work: `MP4`, `MOV`, `WebM`, `Matroska`, `OGG` and `WAV`. Audio codecs: `FLAC`, `MP3`, `Opus`, `PCM`, `Vorbis` and `AAC`. Video codecs: `H264`, `AV1`, `Theora`, `VP8`, `VP9` and `H265` (with hardware decoder). Learn the [difference between a codec and a format](docs.md#primer-video--audio-formats-vs-codecs). Codecs and formats not listed above can still be converted to a supported format/codec from the `File` menu (try the option *Fastest: FFmpeg-assisted playback* first). A low quality version of the file will then be created and opened in the player. Note that the actual cut/export operation will still be performed on the original file, so it will be lossless. This allows for potentially opening any file that FFmpeg is able to decode.
LosslessCut uses the Chromium browser's HTML5 video player, and not all formats/codecs are [natively supported](https://www.chromium.org/audio-video). Generally, the following file formats work: `MP4`, `MOV`, `WebM`, `Matroska`, `OGG` and `WAV`. Audio codecs: `FLAC`, `MP3`, `Opus`, `PCM`, `Vorbis` and `AAC`. Video codecs: `H264`, `AV1`, `Theora`, `VP8`, `VP9` and `H265` (with hardware decoder). Learn the [difference between a codec and a format](docs/index.md#primer-videoaudio-codecs-vs-formats). Codecs and formats not listed above can still be converted to a supported format/codec from the `File` menu (try the option *Fastest: FFmpeg-assisted playback* first). A low quality version of the file will then be created and opened in the player. Note that the actual cut/export operation will still be performed on the original file, so it will be lossless. This allows for potentially opening any file that FFmpeg is able to decode.
## Documentation
- **[Getting started and documentation](docs.md)**
- [FAQ, known issues, limitations and troubleshooting](issues.md)
- **[Getting started, FAQ and usage documentation](docs/index.md)**
- [Troubleshooting and known issues and limitations](docs/troubleshooting.md)
- [Contributing](CONTRIBUTING.md)
### Video demos
@ -169,7 +169,7 @@ LosslessCut uses the Chromium browser's HTML5 video player, and not all formats/
- App icon made by [Dimi Kazak](http://www.flaticon.com/authors/dimi-kazak "Dimi Kazak") from [www.flaticon.com](http://www.flaticon.com "Flaticon") is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/ "Creative Commons BY 3.0").
- [Lottie animation by Chris Gannon](https://lottiefiles.com/7077-magic-flow).
- Thanks to Adi Abinun and [@abdul-alhasany](https://github.com/mifi/lossless-cut/issues/2561) for UI work.
- Thanks to translators who helped translate the app. [You can too!](translation.md)
- Thanks to translators who helped translate the app. [You can too!](docs/translation.md)
- [Thanks to everyone for supporting](https://mifi.no/thanks/) my open source work 🙌
---

@ -1,121 +1,7 @@
# Documentation
## Typical workflow
- **Drag and drop** a video file into player or use <kbd></kbd>/<kbd>CTRL</kbd> + <kbd>O</kbd>.
- <kbd>SPACE</kbd> to play/pause or <kbd></kbd> <kbd></kbd> <kbd>,</kbd> <kbd>.</kbd> or mouse/trackpad wheel to seek back/forth.
- Set the start/end times of the current segment by first moving the timeline cursor and then pressing <kbd>I</kbd> to set start time and <kbd>O</kbd> to set end time.
- <kbd>+</kbd> to create a new segment.
- <kbd>B</kbd> to split the segment at the timeline cursor.
- <kbd>BACKSPACE</kbd> to remove cutpoint/segment.
- If you create segments without an end time, it is a [marker](#markers) instead of a segment.
- Note that when exporting, all segments you create will be **preserved** and exported as new files. You can change this behavior with the **Yin Yang** symbol ☯️, in which case the behaviour is inverted and LosslessCut will instead **skip** all selected segments and export the parts **between** segments as files.
- Also note that start times will not be accurate, see [Known issues](issues.md).
- *(optional)* <kbd>+</kbd> to add another segment at the current cursor time. Then select the segment end time with <kbd>O</kbd>.
- *(optional)* If you want to merge all the selected segments into one file after cutting, change the `Export mode` from `Separate files` to `Merge cuts`.
- *(optional)* If you want to export to a certain output folder, press the `Working dir unset` button (defaults to same folder as source file).
- *(optional)* If you want to change orientation, press the **rotation** button.
- *(optional)* By default, most audio, video and subtitle tracks from the input file will be cut and exported. Press the `Tracks` button to customise and/or add new tracks from other files.
- *(optional)* Select a new output format (remux).
- *(optional)* In the right-hand segments panel, right click a segment for options, or drag-drop to reorder. Segments will appear in this order in the merged output.
- **When done, press the `Export` button (or <kbd>E</kbd>) to show an overview with export options.**
- *(optional)* Adjust any export options.
- **Then press `Export` again to confirm the export**
- Press the **Camera** button (or <kbd>C</kbd>) if you want to take a JPEG/PNG snapshot from the current time.
- If you want to move the original file to trash, press the **trash** button.
- For best results you may need to trial and error with another output format (Matroska can hold nearly everything), change keyframe cut mode or disable some tracks (see [known issues](issues.md)).
- Press <kbd>SHIFT</kbd> + <kbd>/</kbd> to view and edit all keyboard & mouse shortcuts.
- **Note:** The original video file will not be modified. Instead, a file is created file in the same directory as the original file with from/to timestamps in the file name.
- See Keyboard shortcuts dialog for more custom actions. (<kbd>SHIFT</kbd> + <kbd>/</kbd>)
## Primer: Video/audio codecs vs. formats
Here's a little primer about video and audio formats for those not familiar. A common mistake when dealing with audio and video files, is to confuse *formats*, *codecs*, and *file names*. In short: A file's media format is a *container* that holds one or more *codecs* (audio/video/subtitle) inside of it. For example `.mov` is a *container format*, and `H265`/`HEVC` is a *codec*. Some formats support only a few kinds of codecs inside of them (e.g. `.wav`), while others support almost all codecs (e.g. Matroska). The most common formats are MP4/MOV (often `.mp4`,`.mov`,`.m4a`) and Matroska (often `.mkv`,`.mka`). Example: If you have a file named `My video.mp4`, this file most likely (but not necessarily) has the *format* `MP4`. Note that the extension of a file (in this case `.mp4`) doesn't really mean anything, and the file could in reality for example have the `MOV` format, or the extension could be `.txt`. Inside `My video.mp4` there are multiple tracks/streams, each with their own *codec*. In this example, let's say that it contains one `H264` track and one `AAC` track. In LosslessCut you can view and add/delete/modify these tracks.
**Remuxing**: If you change the output format in LosslessCut and export a file, you are *remuxing* the tracks/codecs into a different container format. When you do this, the operation is in theory lossless, meaning you will not lose any codec data and the different tracks will remain exactly the same, even though the format is now different (but some format metadata might get lost due to incompatibilities between container formats). There are limitations: Some popular codecs like VP8 or VP9 are not supported in popular formats like MP4, and some popular formats like Matroska (`.mkv`) are not natively supported in popular video players like iPhone or QuickTime.
If you want to reduce the size of a file using LosslessCut you have to "get rid of" something, either:
- Reduce the duration of the file (cut off start/end)
- Remove one or more tracks/streams (e.g. remove an audio track that you don't need)
Other than that you it's not possible convert a file losslessly to reduce its size, unless you re-encode and lose quality. For that, I recommend using a different tool like e.g. [HandBrake](https://handbrake.fr/).
Here is a great introduction to audio/video: [howvideo.works](https://howvideo.works/).
## Segments
Segments are the first class citizens of LosslessCut. A segment is a time-slice of your source media file, defined by a *start time* and an *end time*. When a segment has no *end time*, it's called a *[marker](#markers)*.
Segments have a segment number (their export order), and can optionally have a label and tags. Segments are be the basis of what gets exported.
### Markers
A segment that has no *end time* is called a *marker*. It has no length and will be excluded from exports, but behaves similar to segments. Markers are distinctively visualized on the timeline with a vertical line and a number on top. You can convert markers to segments by setting their out-point (<kbd>O</kbd>). This can be done manually or automated with one of the many tools in LosslessCut. For example you can invert all segments on the timeline to convert all markers into segments.
## Tracks
The LosslessCut tracks panel is used to selectively enable/disable individual tracks for export and edit track or file metadata. It can also be used to change [content disposition.](https://github.com/mifi/lossless-cut/discussions/2291)
Moved to [docs](docs/index.md).
## Custom exported file names
When exporting segments as files, LosslessCut offers you the ability to specify how the output files will be named in sequence using a *template*. The template is evaluated as a [JavaScript template string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals), so you can use JavaScript syntax inside of it.
The following variables are available in the template to customize the filenames:
| Avail. for merge files? | Avail. for cut+merge? | Variable | Type | Output |
| - | - | - | - | - |
| ✅ | ✅ | `${FILENAME}` | `string` | The original filename *without the extension* (e.g. `Beach Trip` for a file named `Beach Trip.mp4`). When merging files it's the *first* original file name. |
| ✅ | ✅ | `${EXT}` | `string` | The extension of the file (e.g.: `.mp4`, `.mkv`). |
| ✅ | ✅ | `${EPOCH_MS}` | `number` | Number of milliseconds since epoch (e.g. `1680852771465`). Useful to generate a unique file name on every export to prevent accidental overwrite. |
| ✅ | ✅ | `${EXPORT_COUNT}` | `number` | Number of exports done since last LosslessCut launch (starts at 1). |
| | ✅ | `${FILE_EXPORT_COUNT}` | `number` | Number of exports done since last file was opened (starts at 1). |
| ✅ | ✅ | `${SEG_LABEL}` | `string` / `string[]` | The label of the segment (e.g. `Getting Lunch`). In cut+merge mode, this will be an `Array`, and you can use e.g. this code to combine all labels with a comma between: `${SEG_LABEL.filter(label => label).join(',')}`. When merging files it's each original merged file's name. |
| | | `${SEG_NUM}` | `string` | Segment index, padded string (e.g. `01`, `02` or `42`). |
| | | `${SEG_NUM_INT}` | `number` | 🤓 Segment index, as an integer (e.g. `1`, `2` or `42`). Can be used with numeric arithmetics JavaScript expressions, e.g. `${SEG_NUM_INT+100}`. |
| | | `${SELECTED_SEG_NUM}` | `string` | Same as `SEG_NUM`, but it counts only selected segments. |
| | | `${SELECTED_SEG_NUM_INT}` | `number` | 🤓 Same as `SEG_NUM_INT`, but it counts only selected segments. |
| | | `${SEG_SUFFIX}` | `string` | If a label exists for this segment, the label will be used, prepended by `-`. Otherwise, the segment index prepended by `-seg` will be used (e.g. `-Getting_Lunch`, `-seg1`). |
| | | `${CUT_FROM}` | `string` | The timestamp for the beginning of the segment in `hh.mm.ss.sss` format (e.g. `00.00.27.184`). |
| | | `${CUT_FROM_NUM}` | `number` | 🤓 Same as `${CUT_FROM}`, but numeric, meaning it can be used with arithmetics. |
| | | `${CUT_TO}` | `string` | The timestamp for the ending of the segment in `hh.mm.ss.sss` format (e.g. `00.00.28.000`). |
| | | `${CUT_TO_NUM}` | `number` | 🤓 See `${CUT_FROM_NUM}`. |
| | | `${CUT_DURATION}` | `string` | The duration of the segment (`CUT_TO-CUT_FROM`) in `hh.mm.ss.sss` format (e.g. `00.00.28.000`). |
| | | `${SEG_TAGS.XX}` | `object` | Allows you to retrieve the tags for a given segment by name. If a tag is called foo, it can be accessed with `${SEG_TAGS.foo}`. Note that if the tag does not exist, it will yield the text `undefined`. You can work around it with this JavaScript expression: `${SEG_TAGS.foo ?? ''}` |
- 🤓 = Advanced variables (for nerds) involving JavaScript expressions.
- 🧪 = Experimental.
Your files must always include at least one unique identifer (such as `${SEG_NUM}` or `${CUT_FROM}`), and it should end in `${EXT}` (or else players might not recognise the files). For instance, to achieve a filename sequence of `Beach Trip - 1.mp4`, `Beach Trip - 2.mp4`, `Beach Trip - 3.mp4`, your format should read `${FILENAME} - ${SEG_NUM}${EXT}`. If your template gives at least two duplicate output file names, LosslessCut will revert to using the default template instead. You can ask AI to help you create a template string by referring it to this page.
## Padding numbers
If you need to pad a number, you can use this JavaScript code around the variable. For example to pad the `FILE_EXPORT_COUNT` variable to 2 digits with leading zeros, we convert it to a `String` and then call `padStart` on the string: `${String(FILE_EXPORT_COUNT).padStart(2, '0')}`
If you need more help, you can ask an AI to help you with this, e.g. "How to pad a number with JavaScript?"
## Import / export projects
LosslessCut default project file `.llc` is in a JSON5 format. It contains information about the segments in your timeline. LosslessCut also allows importing/exporting your project (segments) in a variety of file formats. See [list of supported formats](https://github.com/mifi/lossless-cut/issues/1340).
### CSV files
- The CSV export/import function takes CSV files with one cut segment on each line. Each line contains three columns: `segment start`, `segment end`, `label`.
- `segment start` and `segment end` are expressed in seconds. `segment end` may be empty, in that case it's a marker.
- Use comma `,` to separate the fields (**not** semicolon `;`)
#### Example `.csv` file
```csv
,56.9568,First segment starting at 0
70,842.33,"Another quoted label"
1234,,Last marker starting at 1234 seconds
```
### TSV files
Same as CSV but `<tab>` instead.
## More
- [❓ FAQ, known issues, limitations and troubleshooting](issues.md)
- [💻 Command line interface (CLI)](cli.md)
- [🕸️ HTTP API](api.md)
- [📝 Recipe cookbook](./recipes.md)
Moved to [Export file name template documentation](docs/file-name-template.md).

@ -1,10 +1,10 @@
# HTTP API
# HTTP API 🧪
LosslessCut can be controlled via a HTTP API, if it is being run with the command line option `--http-api`. See also [CLI](./cli.md). **Note that the HTTP API is experimental and may change at any time.**
LosslessCut can be controlled via a HTTP API, if it is being run with the command line option `--http-api`. See also [CLI](cli.md). **Note that the HTTP API is experimental and may change at any time.**
## Programmatically opening a file
This must be done with [the CLI](./cli.md).
This must be done with [the CLI](cli.md).
## Enabling the API
@ -18,7 +18,7 @@ LosslessCut --http-api
Execute a keyboard shortcut `action`, similar to the `--keyboard-action` CLI option. This is different from the CLI in that most of the actions will wait for the action to finish before responding to the HTTP request (but not all).
#### [Available keyboard actions](./cli.md#available-keyboard-actions)
#### [Available keyboard actions](cli.md#available-keyboard-actions)
#### Example

@ -1,6 +1,6 @@
# Command line interface (CLI)
LosslessCut has basic support for automation through the CLI. See also [HTTP API](./api.md).
LosslessCut has basic support for automation through the CLI. See also [HTTP API](api.md).
```bash
LosslessCut [options] [files]
@ -27,7 +27,7 @@ Note that some users have reported that the Windows Store version of LosslessCut
LosslessCut file1.mp4 file2.mkv
```
## Override settings (experimental)
## Override settings (experimental) 🧪
See [available settings](https://github.com/mifi/lossless-cut/blob/master/src/main/configStore.ts). Note that this is subject to change in newer versions. ⚠️ If you specify incorrect values it could corrupt your configuration file. You may use JSON or JSON5. Example:
```bash
LosslessCut --settings-json '{captureFormat:"jpeg", "keyframeCut":true}'
@ -35,19 +35,19 @@ LosslessCut --settings-json '{captureFormat:"jpeg", "keyframeCut":true}'
## Other options
- `--locales-path` Customise path to locales (useful for [translators](./translation.md)).
- `--locales-path` Customise path to locales (useful for [translators](translation.md)).
- `--disable-networking` Turn off all network requests (see [#1418](https://github.com/mifi/lossless-cut/issues/1418)).
- `--http-api` Start the [HTTP server with an API](./api.md) to control LosslessCut, optionally specifying a port (default `8080`).
- `--http-api` Start the [HTTP server with an API](api.md) to control LosslessCut, optionally specifying a port (default `8080`).
- `--keyboard-action` Run a keyboard action (see below.)
- `--config-dir` Path to a directory where the `config.json` file will be stored and loaded from. Note: don't include `config.json` in the path (only the directory containing it).
## Controlling a running instance (experimental)
## Controlling a running instance (experimental) 🧪
If you have the "Allow multiple instances" setting enabled, you can control a running instance of LosslessCut from the outside, using for example a command line. You do this by issuing messages to it through the `LosslessCut` command. Currently only keyboard actions are supported, and you can open files. *Note that this is considered experimental and the API may change at any time.*
### Keyboard actions, `--keyboard-action`
Simulate a keyboard press action in an already running instance of LosslessCut. Note that the command will return immediately, so if you want to run multiple actions in a sequence, you have to `sleep` for a few seconds between the commands. Alternatively if you want to wait until an action has finished processing, you can use the [HTTP API](./api.md) instead. Note that the HTTP API does not support opening files, and it is currently not possible to wait for a file to have finished opening.
Simulate a keyboard press action in an already running instance of LosslessCut. Note that the command will return immediately, so if you want to run multiple actions in a sequence, you have to `sleep` for a few seconds between the commands. Alternatively if you want to wait until an action has finished processing, you can use the [HTTP API](api.md) instead. Note that the HTTP API does not support opening files, and it is currently not possible to wait for a file to have finished opening.
### Available keyboard actions

@ -0,0 +1,23 @@
# JavaScript expressions
LosslessCut has support for JavaScript expressions in certain dialogs. This is a base JavaScript environment with [core JavaScript functionality](https://developer.mozilla.org/en-US/docs/Web/JavaScript) available for you to use.
## Select segments by expression
You will be given a global variable called `segment` (type [`Segment`](generated/types.md#segment)) and you can create an expression that returns `true` or `false` based on custom logic. For example to select all segments with a duration of less than 5 seconds use this expression:
```js
segment.duration < 5
```
## Edit segments by expression
LosslessCut has support for normal JavaScript expressions. You will be given a variable `segment` (type [`Segment`](generated/types.md#segment)) for each selected segment and can return a new segment with modified properties.
See more examples in-app.
## Output name template
You can also use JavaScript expressions when referring to variables inside `${}` in output file name templates, e.g. `${FILENAME.toLowerCase()}`.
See also [Export file name template](file-name-template.md).

@ -0,0 +1,37 @@
# Export file name template
When exporting segments as files, LosslessCut offers you the ability to specify how the output files will be named in sequence using a *template*. The template is evaluated as a [JavaScript template string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals), so you can use JavaScript syntax inside of it. For a technical description of all variables, see [`FileNameTemplateContext`](generated/types.md#filenametemplatecontext).
The following variables are available in the template to customize the filenames:
| Avail. for merge files? | Avail. for cut+merge? | Variable | Type | Output |
| - | - | - | - | - |
| ✅ | ✅ | `${FILENAME}` | `string` | The original filename *without the extension* (e.g. `Beach Trip` for a file named `Beach Trip.mp4`). When merging files it's the *first* original file name. |
| ✅ | ✅ | `${FILES}` | `SourceFile[]` | 🤓🧪 The original file(s) with various properties. An array of [`SourceFile`](generated/types.md#sourcefile) objects that can be queried using a JavaScript expression. Example: `${new Date(FILES[0].ctime).toISOString().replaceAll(':', '.').replaceAll('T', ' ')}` |
| ✅ | ✅ | `${EXT}` | `string` | The extension of the file (e.g.: `.mp4`, `.mkv`). |
| ✅ | ✅ | `${EPOCH_MS}` | `number` | Number of milliseconds since epoch (e.g. `1680852771465`). Useful to generate a unique file name on every export to prevent accidental overwrite. |
| ✅ | ✅ | `${EXPORT_COUNT}` | `number` | Number of exports done since last LosslessCut launch (starts at 1). |
| | ✅ | `${FILE_EXPORT_COUNT}` | `number` | Number of exports done since last file was opened (starts at 1). |
| ✅ | ✅ | `${SEG_LABEL}` | `string` / `string[]` | The label of the segment (e.g. `Getting Lunch`). In cut+merge mode, this will be an `Array`, and you can use e.g. this code to combine all labels with a comma between: `${SEG_LABEL.filter(label => label).join(',')}`. When merging files it's each original merged file's name. |
| | | `${SEG_NUM}` | `string` | Segment index, padded string (e.g. `01`, `02` or `42`). |
| | | `${SEG_NUM_INT}` | `number` | 🤓 Segment index, as an integer (e.g. `1`, `2` or `42`). Can be used with numeric arithmetics JavaScript expressions, e.g. `${SEG_NUM_INT+100}`. |
| | | `${SELECTED_SEG_NUM}` | `string` | Same as `SEG_NUM`, but it counts only selected segments. |
| | | `${SELECTED_SEG_NUM_INT}` | `number` | 🤓 Same as `SEG_NUM_INT`, but it counts only selected segments. |
| | | `${SEG_SUFFIX}` | `string` | If a label exists for this segment, the label will be used, prepended by `-`. Otherwise, the segment index prepended by `-seg` will be used (e.g. `-Getting_Lunch`, `-seg1`). |
| | | `${CUT_FROM}` | `string` | The timestamp for the beginning of the segment in `hh.mm.ss.sss` format (e.g. `00.00.27.184`). |
| | | `${CUT_FROM_NUM}` | `number` | 🤓 Same as `${CUT_FROM}`, but numeric, meaning it can be used with arithmetics. |
| | | `${CUT_TO}` | `string` | The timestamp for the ending of the segment in `hh.mm.ss.sss` format (e.g. `00.00.28.000`). |
| | | `${CUT_TO_NUM}` | `number` | 🤓 See `${CUT_FROM_NUM}`. |
| | | `${CUT_DURATION}` | `string` | The duration of the segment (`CUT_TO-CUT_FROM`) in `hh.mm.ss.sss` format (e.g. `00.00.28.000`). |
| | | `${SEG_TAGS.XX}` | `object` | Allows you to retrieve the tags for a given segment by name. If a tag is called foo, it can be accessed with `${SEG_TAGS.foo}`. Note that if the tag does not exist, it will yield the text `undefined`. You can work around it with this JavaScript expression: `${SEG_TAGS.foo ?? ''}` |
- 🤓 = Advanced variables (for nerds) involving JavaScript expressions.
- 🧪 = Experimental.
Your files must always include at least one unique identifer (such as `${SEG_NUM}` or `${CUT_FROM}`), and it should end in `${EXT}` (or else players might not recognise the files). For instance, to achieve a filename sequence of `Beach Trip - 1.mp4`, `Beach Trip - 2.mp4`, `Beach Trip - 3.mp4`, your format should read `${FILENAME} - ${SEG_NUM}${EXT}`. If your template gives at least two duplicate output file names, LosslessCut will revert to using the default template instead. You can ask AI to help you create a template string by referring it to this page.
## Padding numbers
If you need to pad a number, you can use this JavaScript code around the variable. For example to pad the `FILE_EXPORT_COUNT` variable to 2 digits with leading zeros, we convert it to a `String` and then call `padStart` on the string: `${String(FILE_EXPORT_COUNT).padStart(2, '0')}`
If you need more help, you can ask an AI to help you with this, e.g. "How to pad a number with JavaScript?"

@ -0,0 +1,78 @@
# Public types
## SourceFile
```ts
/**
* Properties of a source file made available to the user in the export file name template.
* All times are expressed in milliseconds since the POSIX Epoch.
*/
interface SourceFile {
/** The file's name without the file system path. */
name: string;
/** The full filesystem path of the file. */
path: string;
/** File size in bytes. */
size?: number | bigint | undefined;
/** The last time this file was accessed. */
atime?: number | undefined;
/** The last time this file was modified. */
mtime?: number | undefined;
/** The last time the file status was changed. */
ctime?: number | undefined;
/** The creation time of this file. */
birthtime?: number | undefined;
}
```
## FileNameTemplateContext
```ts
/**
* The global context made available to the user in the export file name template.
* See docs/file-name-template.md documentation for details.
*/
interface FileNameTemplateContext {
FILENAME: string;
FILES: SourceFile[];
SEG_SUFFIX?: string | undefined;
EXT: string;
SEG_NUM_INT?: number | undefined;
SEG_NUM?: string | undefined;
SELECTED_SEG_NUM_INT?: number | undefined;
SELECTED_SEG_NUM?: string | undefined;
SEG_LABEL?: string | string[] | undefined;
EPOCH_MS: number;
CUT_FROM?: string | undefined;
CUT_FROM_NUM?: number | undefined;
CUT_TO?: string | undefined;
CUT_TO_NUM?: number | undefined;
CUT_DURATION?: string | undefined;
SEG_TAGS?: Record<string, string> | undefined;
FILE_EXPORT_COUNT?: number | undefined;
EXPORT_COUNT?: number | undefined;
}
```
## Segment
```ts
/**
* See https://github.com/mifi/lossless-cut/blob/master/docs/expressions.md
*/
interface Segment {
/** Index of the segment in the segment list, starting with 0 */
index: number;
/** Name of the segment */
label: string;
/** Segment start time in seconds */
start: number;
/** Segment end time in seconds, or undefined for markers */
end?: number | undefined;
/** Duration in seconds (effectively `end` minus `start` or 0 for markers) */
duration: number;
/** Tags associated with this segment */
tags: Record<string, string>;
}
```

@ -0,0 +1,124 @@
# Documentation
Please read the documentation before creating an issue. Thank you 🙏
## FAQ
- **Q:** Is there a keyboard shortcut to do X?
- **A:** First check the Keyboard shortcuts dialog. If you cannot find your shortcut there, [see this issue.](https://github.com/mifi/lossless-cut/issues/254)
- **Q:** Can LosslessCut be automated using a CLI or API or do external post-processing?
- **A:** While it was never designed for advanced batching/automation, it does have a [basic CLI and a HTTP API](cli.md). More info: [#980](https://github.com/mifi/lossless-cut/issues/980) [#868](https://github.com/mifi/lossless-cut/issues/868).
- **Q:** How to *cut away* a middle part of a video?
- **A:** Enable "advanced view" and then click the Yin Yang symbol. It will invert the segments.
- **Q** What's the difference between the app in the Apple/Microsoft App Store vs. GitHub releases?
- **A** LosslessCut version in the App Stores is often a few versions behind the latest GitHub version, because I want to be sure that the new versions work perfectly before releasing in the App Stores. The GitHub version will contain new, untested features and may contain some bugs (even in existing functionality). I consider the newest GitHub versions to be a public "beta" test. Then, once I'm sure that the new version works well, I will release it in the App Stores as well to give a frictionless as possible experience for customers. They have exactly the same in-app features, except for a few platform limitations: Apple doesn't allow opening VOB files with App Store apps. Apple App Store apps run in a sandbox, and therefore need to prompt for output directory before allowing writing files.
## Commonly requested features
- **Q:** Can LosslessCut **crop, resize, stretch, mirror/flip, overlay text/images, watermark, blur, redact, reduce quality/re-encode, create GIF, slideshow, burn subtitles, color grading, fade/transition between video clips, fade/combine/mix/merge audio tracks, mute audio channels or change audio volume**?
- **A:** No, these are all lossy operations (meaning you *have* to re-encode the file), but in the future I may start to implement such features. [See #372](https://github.com/mifi/lossless-cut/issues/372). Related: [#643](https://github.com/mifi/lossless-cut/issues/643).
- **Q:** When will you implement feature X?
- **A:** I have limited time and I have a lot of projects to work on, so I cannot promise any timeline. I will usually prioritize the issues with the most likes, [see here for a list of the most popular issues](https://github.com/mifi/lossless-cut/issues/691).
- **Q:** Can LosslessCut do the same batch conversion operation on multiple files?
- **A:** Probably not, but [you can probably do it yourself!](batch.md)
- **Q:** Is LosslessCut a portable app? Where is application data, settings and temp files stored?
- **A:** See LosslessCut is *not* a [portable app](https://github.com/mifi/lossless-cut/issues/645). See [Installation and files](installation.md).
- **Q:** Can I export and replace the input file in-place?
- **A:** No, but you can export and automatically delete the input file.
- **Q:** Can you publish through [winget](https://github.com/mifi/lossless-cut/issues/1279), [Flatpak](https://github.com/mifi/lossless-cut/pull/1813), [Docker](https://github.com/mifi/lossless-cut/issues/1086) or other software mangers?
- **A:** In general I don't want to maintain more build systems, but I could be open to linking to externally maintained build systems.
- **Q:** How to sync/shift audio/video tracks?
- **A:** This is not natively supported but it can be done with a workaround, see [#216](https://github.com/mifi/lossless-cut/issues/216).
## Troubleshooting
If you have a problem with the app or with a file, please see the [troubleshooting guide](troubleshooting.md).
## Usage: Typical workflow
- **Drag and drop** a video file into player or use <kbd></kbd>/<kbd>CTRL</kbd> + <kbd>O</kbd>.
- <kbd>SPACE</kbd> to play/pause or <kbd></kbd> <kbd></kbd> <kbd>,</kbd> <kbd>.</kbd> or mouse/trackpad wheel to seek back/forth.
- Set the start/end times of the current segment by first moving the timeline cursor and then pressing <kbd>I</kbd> to set start time and <kbd>O</kbd> to set end time.
- <kbd>+</kbd> to create a new segment.
- <kbd>B</kbd> to split the segment at the timeline cursor.
- <kbd>BACKSPACE</kbd> to remove cutpoint/segment.
- If you create segments without an end time, it is a [marker](#markers) instead of a segment.
- Note that when exporting, all segments you create will be **preserved** and exported as new files. You can change this behavior with the **Yin Yang** symbol ☯️, in which case the behaviour is inverted and LosslessCut will instead **skip** all selected segments and export the parts **between** segments as files.
- Also note that start times will not be accurate, see [Known issues](troubleshooting.md).
- *(optional)* <kbd>+</kbd> to add another segment at the current cursor time. Then select the segment end time with <kbd>O</kbd>.
- *(optional)* If you want to merge all the selected segments into one file after cutting, change the `Export mode` from `Separate files` to `Merge cuts`.
- *(optional)* If you want to export to a certain output folder, press the `Working dir unset` button (defaults to same folder as source file).
- *(optional)* If you want to change orientation, press the **rotation** button.
- *(optional)* By default, most audio, video and subtitle tracks from the input file will be cut and exported. Press the `Tracks` button to customise and/or add new tracks from other files.
- *(optional)* Select a new output format (remux).
- *(optional)* In the right-hand segments panel, right click a segment for options, or drag-drop to reorder. Segments will appear in this order in the merged output.
- **When done, press the `Export` button (or <kbd>E</kbd>) to show an overview with export options.**
- *(optional)* Adjust any export options.
- *(optional)* Change the [Output file name template](file-name-template.md).
- **Then press `Export` again to confirm the export**
- Press the **Camera** button (or <kbd>C</kbd>) if you want to take a JPEG/PNG snapshot from the current time.
- If you want to move the original file to trash, press the **trash** button.
- For best results you may need to trial and error with another output format (Matroska can hold nearly everything), change keyframe cut mode or disable some tracks (see [known issues](troubleshooting.md)).
- Press <kbd>SHIFT</kbd> + <kbd>/</kbd> to view and edit all keyboard & mouse shortcuts.
- **Note:** The original video file will not be modified. Instead, a file is created file in the same directory as the original file with from/to timestamps in the file name.
- See Keyboard shortcuts dialog for more custom actions. (<kbd>SHIFT</kbd> + <kbd>/</kbd>)
## Primer: Video/audio codecs vs. formats
Here's a little primer about video and audio formats for those not familiar. A common mistake when dealing with audio and video files, is to confuse *formats*, *codecs*, and *file names*. In short: A file's media format is a *container* that holds one or more *codecs* (audio/video/subtitle) inside of it. For example `.mov` is a *container format*, and `H265`/`HEVC` is a *codec*. Some formats support only a few kinds of codecs inside of them (e.g. `.wav`), while others support almost all codecs (e.g. Matroska). The most common formats are MP4/MOV (often `.mp4`,`.mov`,`.m4a`) and Matroska (often `.mkv`,`.mka`). Example: If you have a file named `My video.mp4`, this file most likely (but not necessarily) has the *format* `MP4`. Note that the extension of a file (in this case `.mp4`) doesn't really mean anything, and the file could in reality for example have the `MOV` format, or the extension could be `.txt`. Inside `My video.mp4` there are multiple tracks/streams, each with their own *codec*. In this example, let's say that it contains one `H264` track and one `AAC` track. In LosslessCut you can view and add/delete/modify these tracks.
**Remuxing**: If you change the output format in LosslessCut and export a file, you are *remuxing* the tracks/codecs into a different container format. When you do this, the operation is in theory lossless, meaning you will not lose any codec data and the different tracks will remain exactly the same, even though the format is now different (but some format metadata might get lost due to incompatibilities between container formats). There are limitations: Some popular codecs like VP8 or VP9 are not supported in popular formats like MP4, and some popular formats like Matroska (`.mkv`) are not natively supported in popular video players like iPhone or QuickTime.
If you want to reduce the size of a file using LosslessCut you have to "get rid of" something, either:
- Reduce the duration of the file (cut off start/end)
- Remove one or more tracks/streams (e.g. remove an audio track that you don't need)
Other than that you it's not possible convert a file losslessly to reduce its size, unless you re-encode and lose quality. For that, I recommend using a different tool like e.g. [HandBrake](https://handbrake.fr/).
Here is a great introduction to audio/video: [howvideo.works](https://howvideo.works/).
## Segments
Segments are the first class citizens of LosslessCut. A segment is a time-slice of your source media file, defined by a *start time* and an *end time*. When a segment has no *end time*, it's called a *[marker](#markers)*.
Segments have a segment number (their export order), and can optionally have a label and tags. Segments are be the basis of what gets exported.
### Markers
A segment that has no *end time* is called a *marker*. It has no length and will be excluded from exports, but behaves similar to segments. Markers are distinctively visualized on the timeline with a vertical line and a number on top. You can convert markers to segments by setting their out-point (<kbd>O</kbd>). This can be done manually or automated with one of the many tools in LosslessCut. For example you can invert all segments on the timeline to convert all markers into segments.
## Tracks
Tracks are different from segments, in that the tracks run in parallel. For example most videos have one video track and one audio track. When cutting, LosslessCut will cut all tracks equally, although there are some tracks that [cannot be cut](troubleshooting.md). The LosslessCut tracks panel is used to selectively enable/disable individual tracks for export and edit track or file metadata. It can also be used to change [content disposition.](https://github.com/mifi/lossless-cut/discussions/2291)
## Import/export projects
LosslessCut default project file `.llc` is in a JSON5 format. It contains information about the segments in your timeline. LosslessCut also allows importing/exporting your project (segments) in a variety of file formats. See [list of supported formats](https://github.com/mifi/lossless-cut/issues/1340).
### CSV files
- The CSV export/import function takes CSV files with one cut segment on each line. Each line contains three columns: `segment start`, `segment end`, `label`.
- `segment start` and `segment end` are expressed in seconds. `segment end` may be empty, in that case it's a marker.
- Use comma `,` to separate the fields (**not** semicolon `;`)
#### Example `.csv` file
```csv
,56.9568,First segment starting at 0
70,842.33,"Another quoted label"
1234,,Last marker starting at 1234 seconds
```
### TSV files
Same as CSV but `<tab>` instead.
## More
- [🤔 Troubleshooting, known issues and limitations](troubleshooting.md)
- [📝 Recipe cookbook](recipes.md)
- [📲 Installation](installation.md)
- [✅ Requirements](requirements.md)
- [👨‍💻 JavaScript expressions](expressions.md)
- [🦾 Batch processing](batch.md)
- [📄 Export file name template documentation](file-name-template.md).
- [💻 Command line interface (CLI)](cli.md)
- [🕸️ HTTP API](api.md)

@ -27,7 +27,7 @@ App settings and keyboard shortcuts are stored inside the `config.json` file ins
### Custom `config.json` path
On Windows, if you create a `config.json` file with the contents `{}` next to the `LosslessCut.exe` file, LosslessCut will read/store settings from this file instead of the one inside `appData`. Note that other temporary files will still be stored in `appData`. Alternatively you can specify a custom path to a folder containing `config.json` by using the [CLI option](./cli.md) `--config-dir`. See also [#645](https://github.com/mifi/lossless-cut/issues/645).
On Windows, if you create a `config.json` file with the contents `{}` next to the `LosslessCut.exe` file, LosslessCut will read/store settings from this file instead of the one inside `appData`. Note that other temporary files will still be stored in `appData`. Alternatively you can specify a custom path to a folder containing `config.json` by using the [CLI option](cli.md) `--config-dir`. See also [#645](https://github.com/mifi/lossless-cut/issues/645).
## How to uninstall

@ -1,37 +1,6 @@
# FAQ
# Troubleshooting, known issues and limitation
Please read this first:
- **Q:** Can LosslessCut **crop, resize, stretch, mirror/flip, overlay text/images, watermark, blur, redact, reduce quality/re-encode, create GIF, slideshow, burn subtitles, color grading, fade/transition between video clips, fade/combine/mix/merge audio tracks, mute audio channels or change audio volume**?
- **A:** No, these are all lossy operations (meaning you *have* to re-encode the file), but in the future I may start to implement such features. [See #372](https://github.com/mifi/lossless-cut/issues/372). Related: [#643](https://github.com/mifi/lossless-cut/issues/643).
- **Q:** Is there a keyboard shortcut to do X?
- **A:** First check the Keyboard shortcuts dialog. If you cannot find your shortcut there, [see this issue.](https://github.com/mifi/lossless-cut/issues/254)
- **Q:** Can LosslessCut do the same batch conversion operation on multiple files?
- [**A:** Probably not, but you can probably do it! See here.](./batch.md)
- **Q:** Can LosslessCut be automated using a CLI or API or do external post-processing?
- **A:** While it was never designed for advanced batching/automation, it does have a [basic CLI and a HTTP API](./cli.md). More info: [#980](https://github.com/mifi/lossless-cut/issues/980) [#868](https://github.com/mifi/lossless-cut/issues/868).
- **Q:** How to *cut away* a middle part of a video?
- **A:** Enable "advanced view" and then click the Yin Yang symbol. It will invert the segments.
- **Q:** When will you implement feature X?
- **A:** I have limited time and I have a lot of projects to work on, so I cannot promise any timeline. I will usually prioritize the issues with the most likes, [see here for a list of the most popular issues](https://github.com/mifi/lossless-cut/issues/691).
- **Q:** Is LosslessCut a portable app? Where is application data, settings and temp files stored?
- **A:** See LosslessCut is *not* a [portable app](https://github.com/mifi/lossless-cut/issues/645). See [Installation and files](./installation.md).
- **Q:** Can I export and replace the input file in-place?
- **A:** No, but you can export and automatically delete the input file.
- **Q:** Can you publish through [winget](https://github.com/mifi/lossless-cut/issues/1279), [Flatpak](https://github.com/mifi/lossless-cut/pull/1813), [Docker](https://github.com/mifi/lossless-cut/issues/1086) or other software mangers?
- **A:** In general I don't want to maintain more build systems, but I could be open to linking to externally maintained build systems.
- **Q:** How to sync/shift audio/video tracks?
- **A:** This is not natively supported but it can be done with a workaround, see [#216](https://github.com/mifi/lossless-cut/issues/216).
## App Stores and GitHub difference
LosslessCut version in the App Stores is often a few versions behind the latest GitHub version, because I want to be sure that the new versions work perfectly before releasing in the App Stores. The GitHub version will contain new, untested features and may contain some bugs (even in existing functionality). I consider the newest GitHub versions to be a public "beta" test. Then, once I'm sure that the new version works well, I will release it in the App Stores as well to give a frictionless as possible experience for customers. They have exactly the same in-app features, except for a few platform limitations: Apple doesn't allow opening VOB files with App Store apps. Apple App Store apps run in a sandbox, and therefore need to prompt for output directory before allowing writing files.
# Primer: Video & audio formats vs. codecs
Learn the [difference between a codec and a format](docs.md#primer-video--audio-formats-vs-codecs).
# Common / known issues & troubleshooting
When troubleshooting or trying to understand a problem, it can be beneficial to first read the [documentation](index.md) so that you get a deep understanding of how the app works. In particular, the [difference between a codec and a format](index.md#primer-videoaudio-codecs-vs-formats).
## The exported video has a problem
@ -109,12 +78,11 @@ If you have an issue with the Snap or Flatpak version of LosslessCut, please try
- I get an error/crash or completely white/blank LosslessCut window immediately after app launch
- Try not to move the LosslessCut folder after installation. Make sure you run LosslessCut from `C:` drive (not network drive or similar) [#2215](https://github.com/mifi/lossless-cut/issues/2215#issuecomment-2925706575) [#2215](https://github.com/mifi/lossless-cut/issues/2215).
- Make sure LosslessCut folder is **not** inside a folder hierarchy that contains special characters (like Chinese or Japanese or emoji ⚠️).
- Try to run LosslessCut from the [command line](./cli.md) with `--disable-gpu` and/or `--disable-features=VizDisplayCompositor` (see [#781](https://github.com/mifi/lossless-cut/issues/781))
- Try to run LosslessCut from the [command line](cli.md) with `--disable-gpu` and/or `--disable-features=VizDisplayCompositor` (see [#781](https://github.com/mifi/lossless-cut/issues/781))
- Try [removing the AppContainer restrictions](https://github.com/mifi/lossless-cut/discussions/2043#discussioncomment-14909957) on LosslessCut.
- Disable your anti-virus or whitelist LosslessCut. See [#18](https://github.com/mifi/lossless-cut/issues/18), [#1114](https://github.com/mifi/lossless-cut/issues/1114).
- How to uninstall LosslessCut?
- There is no installer. Just delete the folder. [More info](./installation.md).
- There is no installer. Just delete the folder. [More info](installation.md).
- Preview is completely black/blank, corrupted or just won't play back?
- This probably means that Chromium (which LosslessCut uses for playback) doesn't support your particular file (maybe it's 10-bit). [#2228](https://github.com/mifi/lossless-cut/discussions/2228), [#1767](https://github.com/mifi/lossless-cut/discussions/1767), [#2307](https://github.com/mifi/lossless-cut/issues/2307).
1. If H265/HEVC file, go to settings and disable "Hardware HEVC decoding".

@ -1,21 +1,11 @@
# Expressions
LosslessCut has support for JavaScript expressions in certain dialogs. This is a base JavaScript environment with [core JavaScript functionality](https://developer.mozilla.org/en-US/docs/Web/JavaScript) available for you to use.
This page has moved to [docs/expressions.md](docs/expressions.md)
## Select segments by expression
You will be given a variable `segment` and can create an expression that returns `true` or `false`. For example to select all segments with a duration of less than 5 seconds use this expression:
```js
segment.duration < 5
```
Moved to [Select segments by expression](docs/expressions.md#select-segments-by-expression)
## Edit segments by expression
LosslessCut has support for normal JavaScript expressions. You will be given a variable `segment` for each selected segment and can return a new segment with modified properties.
See more examples in-app.
## Output name template
You can also use JavaScript expressions when referring to variables inside `${}` in output file name templates, e.g. `${FILENAME.toLowerCase()}`.
Moved to [Edit segments by expression](docs/expressions.md#edit-segments-by-expression)

@ -1,3 +0,0 @@
# Export
This has been moved to the [documentation (Custom exported file names)](docs.md#custom-exported-file-names).

@ -26,7 +26,8 @@
"pack-linux": "yarn build && electron-builder --linux",
"scan-i18n": "i18next-cli -c i18next.config.scan.ts extract",
"generate-icon": "mkdirp icon-build build-resources/appx && tsx script/generateIcon.mts",
"generate-licenses": "yarn licenses generate-disclaimer > licenses.txt && echo '\n\nffmpeg is licensed under GPL v2+:\n\nhttp://www.gnu.org/licenses/old-licenses/gpl-2.0.html' >> licenses.txt"
"generate-licenses": "yarn licenses generate-disclaimer > licenses.txt && echo '\n\nffmpeg is licensed under GPL v2+:\n\nhttp://www.gnu.org/licenses/old-licenses/gpl-2.0.html' >> licenses.txt",
"generate-docs": "tsx script/generateDocs.mts"
},
"author": {
"name": "Mikael Finstad",
@ -129,6 +130,7 @@
"sweetalert2": "^11.22.4",
"sweetalert2-react-content": "^5.0.7",
"tiny-invariant": "^1.3.3",
"ts-morph": "^27.0.2",
"tsx": "^4.7.1",
"type-fest": "^4.23.0",
"typescript": "^5.9.3",

@ -26,4 +26,4 @@ memoryFile.getInterfaces().forEach((iface) => {
md += '\n```\n\n';
});
fs.writeFileSync('docs/types.md', md);
fs.writeFileSync('docs/generated/types.md', md);

@ -9,6 +9,8 @@ export const discussionsUrl = 'https://github.com/mifi/lossless-cut/discussions'
export const usageUrl = 'https://mifi.no/losslesscut/usage';
export const faqUrl = 'https://mifi.no/losslesscut/faq';
export const troubleshootingUrl = 'https://mifi.no/losslesscut/troubleshooting';
export const exportedFileNameTemplateHelpUrl = 'https://github.com/mifi/lossless-cut/blob/master/docs.md#custom-exported-file-names';
export const jsExpressionHelpUrl = 'https://github.com/mifi/lossless-cut/blob/master/expressions.md';
// Note: https://github.com/mifi/lossless-cut/blob/master/docs/file-name-template.md used to be https://github.com/mifi/lossless-cut/blob/master/docs.md#custom-exported-file-names
export const exportedFileNameTemplateHelpUrl = 'https://github.com/mifi/lossless-cut/blob/master/docs/file-name-template.md';
export const selectSegmentByExpressionHelpUrl = 'https://github.com/mifi/lossless-cut/blob/master/docs/expressions.md#select-segments-by-expression';
export const editSegmentByExpressionHelpUrl = 'https://github.com/mifi/lossless-cut/blob/master/docs/expressions.md#edit-segments-by-expression';
export const supportEmail = 'losslesscut@mifi.no';

@ -110,13 +110,3 @@ export interface ApiActionRequest {
}
export type Html5ifyMode = 'fastest' | 'fast-audio-remux' | 'fast-audio' | 'fast' | 'slow' | 'slow-audio' | 'slowest';
// This is the contract with the user, see https://github.com/mifi/lossless-cut/blob/master/expressions.md
export interface ScopeSegment {
index: number,
label: string,
start: number,
end?: number | undefined,
duration: number,
tags: Record<string, string>,
}

@ -2,6 +2,7 @@
// ‼️ DO NOT change these types without considering the user impact!
// See https://github.com/mifi/lossless-cut/blob/master/expressions.md
// https://github.com/mifi/lossless-cut/blob/master/docs.md#custom-exported-file-names
// JSDoc comments get converted to user documentation.
/**
* Properties of a source file made available to the user in the export file name template.
@ -26,6 +27,7 @@ export interface SourceFile {
/**
* The global context made available to the user in the export file name template.
* See docs/file-name-template.md documentation for details.
*/
export interface FileNameTemplateContext {
FILENAME: string;
@ -47,3 +49,21 @@ export interface FileNameTemplateContext {
FILE_EXPORT_COUNT?: number | undefined;
EXPORT_COUNT?: number | undefined;
}
/**
* See https://github.com/mifi/lossless-cut/blob/master/docs/expressions.md
*/
export interface Segment {
/** Index of the segment in the segment list, starting with 0 */
index: number,
/** Name of the segment */
label: string,
/** Segment start time in seconds */
start: number,
/** Segment end time in seconds, or undefined for markers */
end?: number | undefined,
/** Duration in seconds (effectively `end` minus `start` or 0 for markers) */
duration: number,
/** Tags associated with this segment */
tags: Record<string, string>,
}

@ -17,7 +17,6 @@ import { parameters as allFfmpegParameters, FfmpegDialog, getHint, getLabel } fr
import { maxSegmentsAllowed } from '../util/constants';
import { DefiniteSegmentBase, ParseTimecode, SegmentBase, segmentTagsSchema, SegmentToExport, StateSegment, UpdateSegAtIndex } from '../types';
import safeishEval from '../worker/eval';
import { ScopeSegment } from '../../../common/types';
import { FFprobeFormat, FFprobeStream } from '../../../common/ffprobe';
import { HandleError } from '../contexts';
import { ShowGenericDialog, useGenericDialogContext } from '../components/GenericDialog';
@ -26,7 +25,8 @@ import Button, { DialogButton } from '../components/Button';
import { ButtonRow } from '../components/Dialog';
import * as Dialog from '../components/Dialog';
import { UserFacingError } from '../../errors';
import { jsExpressionHelpUrl } from '../../../common/constants';
import { editSegmentByExpressionHelpUrl, selectSegmentByExpressionHelpUrl } from '../../../common/constants';
import { Segment as ScopeSegment } from '../../../common/userTypes';
const remote = window.require('@electron/remote');
const { shell } = remote;
@ -801,7 +801,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
{ name: i18n.t('Markers'), code: 'segment.end == null' },
]}
title={i18n.t('Select segments by expression')}
description={<Trans>Enter a JavaScript expression which will be evaluated for each segment. Segments for which the expression evaluates to &quot;true&quot; will be selected. <button type="button" className="link-button" onClick={() => shell.openExternal(jsExpressionHelpUrl)}>View available syntax.</button></Trans>}
description={<Trans>Enter a JavaScript expression which will be evaluated for each segment. Segments for which the expression evaluates to &quot;true&quot; will be selected. <button type="button" className="link-button" onClick={() => shell.openExternal(selectSegmentByExpressionHelpUrl)}>View available syntax.</button></Trans>}
variables={['segment.index', 'segment.label', 'segment.start', 'segment.end', 'segment.duration', 'segment.tags.*']}
/>
),
@ -871,7 +871,7 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea
{ name: i18n.t('Convert markers to segments'), code: '{ ...(segment.end == null && { end: segment.start + 5 }) }' },
]}
title={i18n.t('Edit segments by expression')}
description={<Trans>Enter a JavaScript expression which will be evaluated for each selected segment. Returned properties will be edited. <button type="button" className="link-button" onClick={() => shell.openExternal(jsExpressionHelpUrl)}>View available syntax.</button></Trans>}
description={<Trans>Enter a JavaScript expression which will be evaluated for each selected segment. Returned properties will be edited. <button type="button" className="link-button" onClick={() => shell.openExternal(editSegmentByExpressionHelpUrl)}>View available syntax.</button></Trans>}
variables={['segment.index', 'segment.label', 'segment.start', 'segment.end', 'segment.tags.*']}
/>
),

@ -2856,6 +2856,17 @@ __metadata:
languageName: node
linkType: hard
"@ts-morph/common@npm:~0.28.1":
version: 0.28.1
resolution: "@ts-morph/common@npm:0.28.1"
dependencies:
minimatch: "npm:^10.0.1"
path-browserify: "npm:^1.0.1"
tinyglobby: "npm:^0.2.14"
checksum: 10/d5c6ed11cf046c186c7263c28c7e9b5fbefb61c65b99f66cfe6a3b249f70f3fbf116b5aace2980602a7ceabecdc399065d5a7f14aabe5475eb43fd573a3cc665
languageName: node
linkType: hard
"@tsconfig/node18@npm:^18.2.2":
version: 18.2.2
resolution: "@tsconfig/node18@npm:18.2.2"
@ -4882,6 +4893,13 @@ __metadata:
languageName: node
linkType: hard
"code-block-writer@npm:^13.0.3":
version: 13.0.3
resolution: "code-block-writer@npm:13.0.3"
checksum: 10/771546224f38610eecee0598e83c9e0f86dcd600ea316dbf27c2cfebaab4fed51b042325aa460b8e0f131fac5c1de208f6610a1ddbffe4b22e76f9b5256707cb
languageName: node
linkType: hard
"color-convert@npm:^1.9.3":
version: 1.9.3
resolution: "color-convert@npm:1.9.3"
@ -6938,6 +6956,18 @@ __metadata:
languageName: node
linkType: hard
"fdir@npm:^6.5.0":
version: 6.5.0
resolution: "fdir@npm:6.5.0"
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
checksum: 10/14ca1c9f0a0e8f4f2e9bf4e8551065a164a09545dae548c12a18d238b72e51e5a7b39bd8e5494b56463a0877672d0a6c1ef62c6fa0677db1b0c847773be939b1
languageName: node
linkType: hard
"fecha@npm:^4.2.0":
version: 4.2.3
resolution: "fecha@npm:4.2.3"
@ -9174,6 +9204,7 @@ __metadata:
sweetalert2: "npm:^11.22.4"
sweetalert2-react-content: "npm:^5.0.7"
tiny-invariant: "npm:^1.3.3"
ts-morph: "npm:^27.0.2"
tsx: "npm:^4.7.1"
type-fest: "npm:^4.23.0"
typescript: "npm:^5.9.3"
@ -9803,7 +9834,7 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:10.1.1, minimatch@npm:^10.0.0, minimatch@npm:^10.0.3":
"minimatch@npm:10.1.1, minimatch@npm:^10.0.0, minimatch@npm:^10.0.1, minimatch@npm:^10.0.3":
version: 10.1.1
resolution: "minimatch@npm:10.1.1"
dependencies:
@ -10595,6 +10626,13 @@ __metadata:
languageName: node
linkType: hard
"path-browserify@npm:^1.0.1":
version: 1.0.1
resolution: "path-browserify@npm:1.0.1"
checksum: 10/7e7368a5207e7c6b9051ef045711d0dc3c2b6203e96057e408e6e74d09f383061010d2be95cb8593fe6258a767c3e9fc6b2bfc7ce8d48ae8c3d9f6994cca9ad8
languageName: node
linkType: hard
"path-exists@npm:^3.0.0":
version: 3.0.0
resolution: "path-exists@npm:3.0.0"
@ -10734,6 +10772,13 @@ __metadata:
languageName: node
linkType: hard
"picomatch@npm:^4.0.3":
version: 4.0.3
resolution: "picomatch@npm:4.0.3"
checksum: 10/57b99055f40b16798f2802916d9c17e9744e620a0db136554af01d19598b96e45e2f00014c91d1b8b13874b80caa8c295b3d589a3f72373ec4aaf54baa5962d5
languageName: node
linkType: hard
"pkg-up@npm:^3.0.1":
version: 3.1.0
resolution: "pkg-up@npm:3.1.0"
@ -12844,6 +12889,16 @@ __metadata:
languageName: node
linkType: hard
"tinyglobby@npm:^0.2.14":
version: 0.2.15
resolution: "tinyglobby@npm:0.2.15"
dependencies:
fdir: "npm:^6.5.0"
picomatch: "npm:^4.0.3"
checksum: 10/d72bd826a8b0fa5fa3929e7fe5ba48fceb2ae495df3a231b6c5408cd7d8c00b58ab5a9c2a76ba56a62ee9b5e083626f1f33599734bed1ffc4b792406408f0ca2
languageName: node
linkType: hard
"tinypool@npm:^1.0.2":
version: 1.0.2
resolution: "tinypool@npm:1.0.2"
@ -12987,6 +13042,16 @@ __metadata:
languageName: node
linkType: hard
"ts-morph@npm:^27.0.2":
version: 27.0.2
resolution: "ts-morph@npm:27.0.2"
dependencies:
"@ts-morph/common": "npm:~0.28.1"
code-block-writer: "npm:^13.0.3"
checksum: 10/b9bd8ed86d4b76ca23446d3f808787cfe95a3bcb2316769a2f3fa262ea9ab4043bb76bf8aef677f9be0d758576f074df2926e701a535f84fd21946fdbf465148
languageName: node
linkType: hard
"tsconfig-paths@npm:^3.15.0":
version: 3.15.0
resolution: "tsconfig-paths@npm:3.15.0"
@ -13925,11 +13990,11 @@ __metadata:
linkType: hard
"yaml@npm:^2.7.1":
version: 2.8.1
resolution: "yaml@npm:2.8.1"
version: 2.8.2
resolution: "yaml@npm:2.8.2"
bin:
yaml: bin.mjs
checksum: 10/eae07b3947d405012672ec17ce27348aea7d1fa0534143355d24a43a58f5e05652157ea2182c4fe0604f0540be71f99f1173f9d61018379404507790dff17665
checksum: 10/4eab0074da6bc5a5bffd25b9b359cf7061b771b95d1b3b571852098380db3b1b8f96e0f1f354b56cc7216aa97cea25163377ccbc33a2e9ce00316fe8d02f4539
languageName: node
linkType: hard

Loading…
Cancel
Save