Self-hostable TikTok feed for your media
Make a TikTok feed with your own images and videos
Erin is a simple and self-hostable service that enables you to view your own images and video clips using TikTok's well-known vertical swipe feed. A request was made on Reddit for a self-hostable app that can show filtered videos using TikTok's interface, so I made it.
Erin has all these features implemented :
- Display your own images and videos using TikTok's swipe feed
- Tap anywhere to play / pause, and press and hold the left or right side of the screen to play at 5x speed
- Seek by tapping or dragging anywhere on the progress bar, or using your keyboard (arrows, and space to play / pause)
- Swipe left to see all your media in a grid, in the same order as your feed (like a TikTok profile), tap one to play it, and swipe right to go back
- Choose which feed (playlist) you want to play*
- Autoplay your feed without even swiping
- Show media and playlist metadata using TikTok's UI**
- Simple lazy-loading mechanism for your media
- Automatic media naming based on file name
- Simple and optional security using a master password
- Support for starting video playback from the start, middle, or random point in the video
- Support for custom styling***
- Support for Docker / proxy deployment
On top of these, please note that Erin is only a React app served by a small Node.js server listening on port 80. The server takes care of authentication, serving your media files, and serving the React app all at once. It only speaks plain HTTP: the recommended setup is to run Erin behind your own reverse proxy (nginx, Caddy, Traefik, ...) which takes care of HTTPS. See Deployment and Examples.
*: By default, Erin will create a random feed from all the media in your folder and its subdirectories. However, if you would like to create custom feeds (playlists), you can create subdirectories and organize your images and videos accordingly. For example:
http://my-server.tld/directory-awill create a feed from the media located in the/directory-adirectory, and it works with any path (so, nested folders are supported).
**: You can show a channel (with an avatar and name), a caption and a link for your media using an adjacent metadata file. It must match the associated media filename while replacing the extension with JSON:
my-video.mp4andmy-photo.jpgusemy-video.jsonandmy-photo.json. Videos supportvideo_title,video_caption, andvideo_link, as shown here. Images supportimage_title,image_caption, andimage_link, as shown here. Images ignorevideo_*fields and videos ignoreimage_*fields. Captions may contain trusted raw HTML for custom styling and effects. You may also define folder-level metadata in a file namedmetadata.json; its channel name and avatar are inherited by all media in that folder when item-specific channel metadata is absent.
***: You can inject your own stylesheet to customize the appearance of the app by doing two things: 1) set
USE_CUSTOM_SKINtotrue, and 2) mount acustom.cssfile onto/srv/custom.cssin your container.
For more information, read about Configuration.
Before proceeding, regardless of Docker, Docker Compose, or a standalone deployment, please make sure
that you have created a videos directory containing all your media files. Later on, this directory will
be made available to your instance of Erin (by binding a volume to your Docker container, or putting the directory
at /srv/videos for a standalone deployment).
The recommended setup is to run Erin behind your own reverse proxy, which takes care of HTTPS and forwards requests to Erin's port 80 over plain HTTP. Erin doesn't handle HTTPS itself.
Note : If you are upgrading from the previous Caddy-based image:
PUBLIC_URLis no longer used, Erin always listens on port 80 over HTTP, andAUTH_SECRETis now required when authentication is enabled.
For a standalone deployment, build the app with npm run build, copy the build directory's contents into /srv,
then run npm ci --omit=dev and node index.js (as root, to listen on port 80) from the server directory.
You can run Erin with Docker on the command line very quickly.
You can use the following commands :
# Create a .env file
touch .env
# Edit .env file ...
# Option 1 : Run Erin attached to the terminal (useful for debugging)
docker run --env-file .env -p <YOUR-PORT-MAPPING> -v ./videos:/srv/videos:ro ghcr.io/jimaek/erintok
# Option 2 : Run Erin as a daemon
docker run -d --env-file .env -p <YOUR-PORT-MAPPING> -v ./videos:/srv/videos:ro ghcr.io/jimaek/erintokNote : A
sample.envfile is located at the root of the repository to help you get started
Note : When using
docker run --env-file, make sure to remove the quotes aroundAUTH_ENABLEDandAUTH_SECRET, or else your container might crash due to unexpected interpolation and type conversions operated by Docker behind the scenes.
To help you get started quickly, a few example docker-compose files are located in the "examples/" directory.
Here's a description of every example :
-
docker-compose.proxy.yml(recommended): A setup with Erin running on port 80, behind a proxy listening on port 443 that takes care of HTTPS. -
docker-compose.simple.yml: Run Erin as a front-facing service on plain HTTP, port 80, with environment variables supplied in thedocker-composefile directly.
When your docker-compose file is on point, you can use the following commands :
# Run Erin in the current terminal (useful for debugging)
docker-compose up
# Run Erin in a detached terminal (most common)
docker-compose up -d
# Show the logs written by Erin (useful for debugging)
docker logs <NAME-OF-YOUR-CONTAINER>To run Erin, you will need to set the following environment variables in a .env file :
Note : Regular environment variables provided on the commandline work too
Note : A
sample.envfile is located at the root of the repository to help you get started
| Parameter | Type | Description | Default |
|---|---|---|---|
AUTH_ENABLED |
string |
Whether Basic Authentication should be enabled. (This parameter is case sensitive) (Possible values : true, false) | true |
AUTH_SECRET |
string |
The bcrypt hash of the password used to protect your instance of Erin. (Required when AUTH_ENABLED is true. Erin refuses to start with the hash of secure-password used by previous versions) |
None |
APP_TITLE |
string |
The custom title that you would like to display in the browser's tab. (Tip: You can use [VIDEO_TITLE] here if you want Erin to dynamically display the title of the current image or video.) |
Erin - TikTok feed for your own media |
AUTOPLAY_ENABLED |
boolean |
Whether autoplay should be enabled. (This parameter is case sensitive) (Possible values : true, false) | false |
IMAGE_DISPLAY_DURATION |
number |
How many seconds an image remains on screen before its timeline restarts or autoplay advances the feed. Positive decimals are supported. | 5 |
START_MUTED |
boolean |
Whether videos should be muted. There is no mute button in the app. (This parameter is case sensitive) (Possible values : true, false) | true |
PROGRESS_BAR_POSITION |
string |
Where the progress bar should be located on the screen. (This parameter is case sensitive) (Possible values : bottom, top) | bottom |
IGNORE_HIDDEN_PATHS |
boolean |
Whether all hidden files and directories (starting with a dot) should be ignored by Erin, and not loaded or scanned altogether | false |
USE_CUSTOM_SKIN |
boolean |
Whether a custom skin should be loaded on startup. (Possible values : true, false) | false |
VIDEO_START_POSITION |
string |
Where videos start playing. (Possible values : start, middle, random) | start |
Note : Browsers may block autoplay with sound before the user interacts with the page when
START_MUTEDis set tofalse: tap the screen to start playback.
Tip : To generate a secure hash for your instance, use the following command :
docker run --rm ghcr.io/jimaek/erintok npx bcrypt "your-new-password" 14Note : When using
docker-compose.ymlenvironment variables, if your password hash contains dollar signs: double them all, or else the app will crash. For example :$ab$cd$efxyzbecomes$$ab$$cd$$efxyz. This is due to caveats withdocker-composestring interpolation system.
Should you encounter any issue running Erin, please refer to the following common problems that may occur.
If none of these matches your case, feel free to open an issue.
Erin is served by a Node.js server that listens on port 80 over plain HTTP, for any hostname. It doesn't handle HTTPS: if you need it, put Erin behind a reverse proxy that terminates TLS.
As a result :
-
You may be able to better troubleshoot the issue by reading your container logs.
-
If Erin runs as a standalone application without proxy :
- Make sure your server / firewall accepts incoming connections on port 80 (or on the port you mapped with Docker).
- Make sure your DNS configuration is correct. (Usually, such record should suffice :
A erin XXX.XXX.XXX.XXXforhttp://erin.your-server-tld) - Make sure your
.envfile is well configured according to the Configuration section.
-
If Erin runs inside Docker / behind a proxy :
- Perform the previous (standalone) verifications first.
- Check your proxy forwarding rules (they should target Erin's port 80 over HTTP).
- Check your Docker networking setup.
For Erin to serve your media files, they must respect the following requirements :
- The file extension is one of
.mp4,.ogg,.webm,.jpg,.jpeg,.png, or.webp. - The files are located in
/srv/videoson your Docker container using a volume.
To make sure that your media are inside your Docker container and in the right place, you can :
- Run
docker exec -it <NAME-OF-YOUR-CONTAINER> sh - Inside the newly-opened shell, run :
ls /srv/videos - You should see your media files here. If not, then check your volume-binding.
If Erin is still unable to find your media despite everything being well-configured, please open an issue
including the output of your browser's Javascript console and network tab when the request goes to /media/.
It may have to do with browser-caching, invalid configuration, or invalid credentials.
Put your new image and video files into the videos directory that is mounted with Docker.
Erin will automatically pick up these new files, and when you refresh your browser you'll see them.
Erin will automatically translate each media filename into a title to display on the interface.
The conversion operated is as follows :
-becomes__becomes-
Here's a few examples to help you name your files :
Vegas-trip__Clip-1.mp4becomesVegas trip - Clip 1Spanish-language__Lesson-1.mp4becomesSpanish language - Lesson 1Spiderman-1.oggbecomesSpiderman 1
Erin randomly shuffles your media files on every browser refresh.
As a result, there is no specific order for your media to appear.
Erin will only attempt to retrieve media that has a supported extension.
Supported video extensions are .webm, .mp4, and .ogg. Supported image extensions are .jpg, .jpeg, .png, and .webp.
However, please note that Safari doesn't seem to support .ogg, hence these videos will be ignored for Safari users.
Should you have any advice or idea to support more extensions (especially for Safari users), please feel free to open an issue.
There seems to be a few caveats when using Docker / Docker Compose with bcrypt password hashes.
These are the rules you should follow :
- If you deployed Erin using the Docker CLI, via the command
docker run ... --env-file .env ..., then yourAUTH_SECRETshould have no quote at all, and all the dollar signs should stay as they are without escape or doubling - If you deployed Erin using Docker Compose, via a
docker-compose.ymlfile, then yourAUTH_SECRETshould have its dollar signs doubled. Example :i$am$grootbecomesi$$am$$groot.
That said, remember that your password hash must be a bcrypt hash (hashes previously generated with caddy hash-password keep working), such as one generated with the following command :
docker run --rm ghcr.io/jimaek/erintok npx bcrypt "your-new-password" 14Please feel free to open an issue, explaining what happens, and describing your environment.
Hey hey ! It's always a good idea to say thank you and mention the people and projects that help us move forward.
Big thanks to the individuals / teams behind these projects :
- tik-tok-clone : For the base TikTok UI and smooth interaction.
- The countless others!
And don't forget to mention Erin if you like it or if it helps you in any way!