feat: torrent streaming with hls transcoding (#1)

* feat: add ffmpeg for hls streaming

* feat: torrent streaming with hls transcoding

- add nyaa.si torrent search client
- add streaming service using anacrolix/torrent
- add hls transcoding via ffmpeg for browser playback
- add watch page with episode selection
- add socks5 proxy support via TORRENT_PROXY env
- switch to modernc.org/sqlite (pure go, no cgo conflicts)
- update dockerfile with ffmpeg
This commit is contained in:
2026-04-07 13:23:08 +02:00
committed by GitHub
parent 579b194eb9
commit a25e8f1655
27 changed files with 3744 additions and 329 deletions

View File

@@ -93,7 +93,7 @@ func (q *Queries) DeleteWatchListEntry(ctx context.Context, arg DeleteWatchListE
}
const getAnime = `-- name: GetAnime :one
SELECT id, title_original, image_url, created_at, title_english, title_japanese, airing FROM anime WHERE id = ? LIMIT 1
SELECT id, title_original, image_url, created_at, title_english, title_japanese, airing, magnet_link, torrent_hash FROM anime WHERE id = ? LIMIT 1
`
func (q *Queries) GetAnime(ctx context.Context, id int64) (Anime, error) {
@@ -107,6 +107,8 @@ func (q *Queries) GetAnime(ctx context.Context, id int64) (Anime, error) {
&i.TitleEnglish,
&i.TitleJapanese,
&i.Airing,
&i.MagnetLink,
&i.TorrentHash,
)
return i, err
}
@@ -166,7 +168,9 @@ SELECT
a.title_english,
a.title_japanese,
a.image_url,
a.airing
a.airing,
a.magnet_link,
a.torrent_hash
FROM watch_list_entry e
JOIN anime a ON e.anime_id = a.id
WHERE e.user_id = ?
@@ -185,6 +189,8 @@ type GetUserWatchListRow struct {
TitleJapanese sql.NullString `json:"title_japanese"`
ImageUrl string `json:"image_url"`
Airing sql.NullBool `json:"airing"`
MagnetLink sql.NullString `json:"magnet_link"`
TorrentHash sql.NullString `json:"torrent_hash"`
}
func (q *Queries) GetUserWatchList(ctx context.Context, userID string) ([]GetUserWatchListRow, error) {
@@ -208,6 +214,8 @@ func (q *Queries) GetUserWatchList(ctx context.Context, userID string) ([]GetUse
&i.TitleJapanese,
&i.ImageUrl,
&i.Airing,
&i.MagnetLink,
&i.TorrentHash,
); err != nil {
return nil, err
}
@@ -247,15 +255,17 @@ func (q *Queries) GetWatchListEntry(ctx context.Context, arg GetWatchListEntryPa
}
const upsertAnime = `-- name: UpsertAnime :one
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url, airing)
VALUES (?, ?, ?, ?, ?, ?)
INSERT INTO anime (id, title_original, title_english, title_japanese, image_url, airing, magnet_link, torrent_hash)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE SET
title_original = excluded.title_original,
title_english = excluded.title_english,
title_japanese = excluded.title_japanese,
image_url = excluded.image_url,
airing = excluded.airing
RETURNING id, title_original, image_url, created_at, title_english, title_japanese, airing
airing = excluded.airing,
magnet_link = excluded.magnet_link,
torrent_hash = excluded.torrent_hash
RETURNING id, title_original, image_url, created_at, title_english, title_japanese, airing, magnet_link, torrent_hash
`
type UpsertAnimeParams struct {
@@ -265,6 +275,8 @@ type UpsertAnimeParams struct {
TitleJapanese sql.NullString `json:"title_japanese"`
ImageUrl string `json:"image_url"`
Airing sql.NullBool `json:"airing"`
MagnetLink sql.NullString `json:"magnet_link"`
TorrentHash sql.NullString `json:"torrent_hash"`
}
func (q *Queries) UpsertAnime(ctx context.Context, arg UpsertAnimeParams) (Anime, error) {
@@ -275,6 +287,8 @@ func (q *Queries) UpsertAnime(ctx context.Context, arg UpsertAnimeParams) (Anime
arg.TitleJapanese,
arg.ImageUrl,
arg.Airing,
arg.MagnetLink,
arg.TorrentHash,
)
var i Anime
err := row.Scan(
@@ -285,6 +299,8 @@ func (q *Queries) UpsertAnime(ctx context.Context, arg UpsertAnimeParams) (Anime
&i.TitleEnglish,
&i.TitleJapanese,
&i.Airing,
&i.MagnetLink,
&i.TorrentHash,
)
return i, err
}