From: Michael Niedermayer via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Cc: Michael Niedermayer <michael@niedermayer.cc>
Subject: [FFmpeg-devel] Re: [RFC] Issue tracker
Date: Sun, 28 Sep 2025 11:18:40 +0200
Message-ID: <20250928091840.GC29660@pb2> (raw)
In-Reply-To: <4679850.LvFx2qVVIh@basile.remlab.net>
[-- Attachment #1.1.1: Type: text/plain, Size: 1644 bytes --]
Hi Remi
On Sun, Sep 28, 2025 at 10:54:14AM +0300, Rémi Denis-Courmont via ffmpeg-devel wrote:
> Le maanantaina 15. syyskuuta 2025, 0.23.17 Itä-Euroopan kesäaika Michael
> Niedermayer via ffmpeg-devel a écrit :
[...]
> > we can have custom ticket states and custom
> > workflow on tickets.
>
> We can have custom labels for tickets and MRs.
>
> However before FFmpeg can consider migrating from Trac to Forgejo, we have to
> consider the (anti)spam implications. I suppose there is not much spam yet
> because Gitlab is vastly more popular, but that's probably only a matter of
> time.
>
> If code.ffmpeg.org needs to be locked down with manual approval like
> FreeDesktop and VideoLAN's Gitlab instances, then migrating the bug reporting
> there may not be such a great idea. Unless we specifically want to restrict bug
> reporting to trusted community members.
I agree, anti-spam is a big deal. trac has quite extensive anti spam capabilities.
>
> Otherwise, I don't really see any benefit to Trac. Forgejo would allow us to
> link issues and MRs, and presumably also automatically close issues. We just
> need to agree whether to migrate existing issues or not (and if we do, find
> someone to do the SQL wizardry).
AI generated migration script attacht.
This is untested and is very unlikely to work as is, but parts of it could
be usefull i guess or it could serve as a starting point.
thx
[...]
--
Michael GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB
During times of universal deceit, telling the truth becomes a
revolutionary act. -- George Orwell
[-- Attachment #1.1.2: trac_to_forgejo_migrator.py --]
[-- Type: text/x-python, Size: 24662 bytes --]
#!/usr/bin/env python3
"""
Trac → Forgejo issue migrator (tickets, comments, labels, milestones, attachments-as-links)
✅ Features
- Migrates Trac tickets (title, body, metadata) to Forgejo issues
- Preserves reporter/owner via user mapping
- Converts common Trac wiki markup → Markdown (lightweight, optional)
- Replays comments with original author & timestamp banners
- Recreates labels (component, type, priority, status/resolution) if missing
- Recreates milestones (by name) if missing and assigns to issues
- Closes issues that were closed in Trac and adds a resolution label
- Adds links for attachments (keeps them downloadable from your Trac site)
- Idempotent: keeps a trac_id ↔ forgejo_issue index mapping file
- Dry‑run mode
⚙️ Requirements
- Python 3.8+
- pip install requests pyyaml
🧰 Usage
1) Create a config YAML (example below) as config.yaml
2) Run:
python3 trac_to_forgejo_migrator.py --config config.yaml --dry-run # preview
python3 trac_to_forgejo_migrator.py --config config.yaml # migrate
Example config.yaml
-------------------
forgejo:
base_url: "https://forgejo.example.org" # no trailing slash
owner: "your-org"
repo: "your-repo"
token: "<personal-access-token>" # needs repo:write, admin:repo_hook for labels/milestones
rate_limit_sleep_sec: 0.2 # politeness pause between API calls
trac:
# SQLite DB path of your Trac instance (read-only). For MySQL/PostgreSQL, see notes below.
sqlite_path: "/var/lib/trac/project/db/trac.db"
# Base URL of your Trac site (used to link back to original ticket & attachments)
base_url: "https://trac.example.org"
migrate:
# restrict to a subset (omit or set to [] for all)
include_ticket_ids: []
exclude_ticket_ids: []
# when True, prefix titles with "[Trac #ID]" for easy cross-reference
prefix_trac_id_in_title: true
# If a Forgejo issue already exists for a Trac id (mapping file), it will be skipped
dry_run: false
mapping_files:
# JSON file created/updated by this script to remember Trac→Forgejo mapping
id_map_path: "./trac_forgejo_id_map.json"
users:
# Map Trac usernames → Forgejo usernames (for assignees); reporter shown in body regardless
# Unmapped users are left as plain text.
"michael": "michael-niedermayer"
"alice": "alice"
labels:
# Optional color overrides (6-hex without #). Missing labels get a stable hash color.
colors:
"component/avcodec": "0366d6"
"resolution/fixed": "2ea043"
markdown:
# If you have pandoc installed and want better conversion, set to path like "/usr/bin/pandoc"
pandoc_path: null
# Or use the built-in lightweight converter (recommended default)
use_lightweight_converter: true
notes:
# For MySQL/PostgreSQL backends, you can export SQLite first (or extend the code to use PyMySQL/psycopg2)
# Attachments: this script links to Trac's attachment URLs; to *upload* into Forgejo later, see TODOs in code.
"""
from __future__ import annotations
import argparse
import dataclasses
import datetime as dt
import hashlib
import json
import os
import re
import sqlite3
import subprocess
import sys
import time
from typing import Any, Dict, Iterable, List, Optional, Tuple
import requests
try:
import yaml # type: ignore
except Exception: # pragma: no cover
print("ERROR: pyyaml is required. pip install pyyaml", file=sys.stderr)
sys.exit(2)
ISO = "%Y-%m-%d %H:%M:%S%z"
# ---------------------------- Utilities ----------------------------
def ts_from_trac(microseconds_since_epoch: int) -> dt.datetime:
"""Trac stores timestamps as microseconds since epoch."""
# Trac uses microseconds epoch (int). Interpret as UTC.
sec = microseconds_since_epoch / 1_000_000.0
return dt.datetime.fromtimestamp(sec, tz=dt.timezone.utc)
def banner(author: str, when: dt.datetime) -> str:
return f"_Originally posted by **{author or 'unknown'}** on {when.strftime('%Y-%m-%d %H:%M:%S %Z')}_\n\n"
def stable_color(name: str) -> str:
"""Derive a 6-hex color from a name (no leading '#')."""
h = hashlib.sha1(name.encode("utf-8")).hexdigest()
return h[:6]
# ----------------------- Lightweight Trac→MD -----------------------
TRIPLE_SINGLE = re.compile(r"'''(.*?)'''", re.DOTALL)
DOUBLE_SINGLE = re.compile(r"''(.*?)''", re.DOTALL)
HEADING = re.compile(r"^(=+)[ \t]*(.*?)[ \t]*=+$", re.MULTILINE)
WIKI_LINK = re.compile(r"\[(?:wiki:)?([^\] ]+)(?:\s+([^\]]+))?\]")
HTTP_LINK = re.compile(r"\[(https?://[^\] ]+)(?:\s+([^\]]+))?\]")
BR = re.compile(r"\[\[BR\]\]")
def trac_to_markdown(text: str) -> str:
"""Very small subset of Trac wiki → Markdown conversions.
Keeps unknown markup as-is to avoid data loss.
"""
if not text:
return ""
out = text
# line breaks
out = BR.sub(" \n", out)
# bold/italic
out = TRIPLE_SINGLE.sub(r"**\1**", out)
out = DOUBLE_SINGLE.sub(r"*\1*", out)
# headings: '== Title ==' → '## Title'
def _h(m: re.Match) -> str:
level = len(m.group(1))
level = min(max(level, 1), 6)
return f"{'#' * level} {m.group(2).strip()}"
out = HEADING.sub(_h, out)
# external links [http://url label]
out = HTTP_LINK.sub(lambda m: f"[{m.group(2) or m.group(1)}]({m.group(1)})", out)
# wiki-ish links [wiki:Page Label] or [Page Label] → [Label](Page)
def _wiki(m: re.Match) -> str:
target = m.group(1)
label = m.group(2) or target
# naive: leave as code-ish if it looks like CamelCase wiki page
return f"[{label}]({target})"
out = WIKI_LINK.sub(_wiki, out)
return out
def maybe_pandoc(text: str, pandoc_path: Optional[str]) -> str:
if not text:
return ""
if not pandoc_path:
return text
try:
p = subprocess.run(
[pandoc_path, "-f", "trac", "-t", "gfm"],
input=text.encode("utf-8"),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
return p.stdout.decode("utf-8")
except Exception as e:
# fall back silently
return text
# ----------------------------- Trac DB -----------------------------
@dataclasses.dataclass
class TracTicket:
id: int
type: str
time: dt.datetime
changetime: dt.datetime
component: Optional[str]
severity: Optional[str]
priority: Optional[str]
owner: Optional[str]
reporter: Optional[str]
cc: Optional[str]
version: Optional[str]
milestone: Optional[str]
status: str
resolution: Optional[str]
summary: str
description: str
@dataclasses.dataclass
class TracChange:
ticket: int
time: dt.datetime
author: Optional[str]
field: str
oldvalue: Optional[str]
newvalue: Optional[str]
@dataclasses.dataclass
class TracAttachment:
ticket: int
filename: str
author: Optional[str]
time: dt.datetime
description: Optional[str]
size: Optional[int]
class Trac:
def __init__(self, sqlite_path: str):
self.sqlite_path = sqlite_path
self.conn = sqlite3.connect(sqlite_path)
self.conn.row_factory = sqlite3.Row
def fetch_ticket_ids(self) -> List[int]:
cur = self.conn.execute("SELECT id FROM ticket ORDER BY id ASC")
return [row[0] for row in cur.fetchall()]
def fetch_ticket(self, tid: int) -> TracTicket:
row = self.conn.execute(
"SELECT id, type, time, changetime, component, severity, priority, owner, reporter, cc, version, milestone, status, resolution, summary, description FROM ticket WHERE id=?",
(tid,),
).fetchone()
if not row:
raise KeyError(f"Ticket {tid} not found")
return TracTicket(
id=row["id"],
type=row["type"] or "",
time=ts_from_trac(row["time"]),
changetime=ts_from_trac(row["changetime"]),
component=row["component"],
severity=row["severity"],
priority=row["priority"],
owner=row["owner"],
reporter=row["reporter"],
cc=row["cc"],
version=row["version"],
milestone=row["milestone"],
status=row["status"] or "new",
resolution=row["resolution"],
summary=row["summary"] or "",
description=row["description"] or "",
)
def fetch_changes(self, tid: int) -> List[TracChange]:
cur = self.conn.execute(
"SELECT ticket, time, author, field, oldvalue, newvalue FROM ticket_change WHERE ticket=? ORDER BY time ASC, rowid ASC",
(tid,),
)
out: List[TracChange] = []
for r in cur.fetchall():
out.append(
TracChange(
ticket=r["ticket"],
time=ts_from_trac(r["time"]),
author=r["author"],
field=r["field"],
oldvalue=r["oldvalue"],
newvalue=r["newvalue"],
)
)
return out
def fetch_attachments(self, tid: int) -> List[TracAttachment]:
# Trac 'attachment' table schema: type, id, filename, size, time, description, author, ipnr
cur = self.conn.execute(
"SELECT filename, size, time, description, author FROM attachment WHERE type='ticket' AND id=? ORDER BY time ASC",
(str(tid),),
)
out: List[TracAttachment] = []
for r in cur.fetchall():
out.append(
TracAttachment(
ticket=tid,
filename=r["filename"],
author=r["author"],
time=ts_from_trac(r["time"]),
description=r["description"],
size=r["size"],
)
)
return out
# --------------------------- Forgejo Client ---------------------------
class Forgejo:
def __init__(self, base_url: str, owner: str, repo: str, token: str, rate_sleep: float = 0.0):
self.base = base_url.rstrip('/')
self.owner = owner
self.repo = repo
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'token {token}',
'Accept': 'application/json',
})
self.rate_sleep = rate_sleep
# ---- helpers ----
def _url(self, path: str) -> str:
return f"{self.base}/api/v1/repos/{self.owner}/{self.repo}{path}"
def _req(self, method: str, url: str, **kw) -> requests.Response:
r = self.session.request(method, url, timeout=60, **kw)
if self.rate_sleep:
time.sleep(self.rate_sleep)
if r.status_code >= 400:
raise RuntimeError(f"HTTP {r.status_code} for {url}: {r.text[:400]}")
return r
# ---- labels ----
def list_labels(self) -> Dict[str, Dict[str, Any]]:
url = self._url("/labels")
r = self._req('GET', url)
items = r.json()
return {it['name']: it for it in items}
def ensure_label(self, name: str, color: Optional[str] = None, description: str = "") -> int:
existing = self.list_labels()
if name in existing:
return existing[name]['id']
url = self._url("/labels")
payload = {
'name': name,
'color': f"#{(color or stable_color(name)).lstrip('#')}",
'description': description,
}
r = self._req('POST', url, json=payload)
return r.json()['id']
# ---- milestones ----
def list_milestones(self) -> Dict[str, Dict[str, Any]]:
url = self._url("/milestones")
r = self._req('GET', url)
items = r.json()
return {it['title']: it for it in items}
def ensure_milestone(self, title: str) -> Optional[int]:
if not title:
return None
existing = self.list_milestones()
if title in existing:
return existing[title]['id']
url = self._url("/milestones")
r = self._req('POST', url, json={'title': title})
return r.json()['id']
# ---- issues ----
def create_issue(self, title: str, body: str, labels: List[str], assignees: List[str], milestone_id: Optional[int]) -> Dict[str, Any]:
# Ensure labels exist and collect ids
label_ids: List[int] = []
for ln in labels:
if not ln:
continue
lid = self.ensure_label(ln)
label_ids.append(lid)
payload: Dict[str, Any] = {
'title': title,
'body': body,
'assignees': assignees or [],
'labels': label_ids,
}
if milestone_id:
payload['milestone'] = milestone_id
url = self._url('/issues')
r = self._req('POST', url, json=payload)
return r.json()
def close_issue(self, index: int) -> None:
url = self._url(f'/issues/{index}')
self._req('PATCH', url, json={'state': 'closed'})
def comment(self, index: int, body: str) -> Dict[str, Any]:
url = self._url(f'/issues/{index}/comments')
r = self._req('POST', url, json={'body': body})
return r.json()
# ----------------------------- Migration -----------------------------
@dataclasses.dataclass
class Config:
forgejo_base: str
forgejo_owner: str
forgejo_repo: str
forgejo_token: str
rate_sleep: float
trac_sqlite: str
trac_base_url: str
include_ids: List[int]
exclude_ids: List[int]
prefix_trac_id_in_title: bool
id_map_path: str
user_map: Dict[str, str]
label_colors: Dict[str, str]
pandoc_path: Optional[str]
use_light_conv: bool
dry_run: bool
@staticmethod
def from_yaml(d: Dict[str, Any]) -> 'Config':
forgejo = d.get('forgejo', {})
trac = d.get('trac', {})
migrate = d.get('migrate', {})
mapping_files = d.get('mapping_files', {})
users = d.get('users', {})
labels = d.get('labels', {})
markdown = d.get('markdown', {})
return Config(
forgejo_base=forgejo['base_url'],
forgejo_owner=forgejo['owner'],
forgejo_repo=forgejo['repo'],
forgejo_token=forgejo['token'],
rate_sleep=float(forgejo.get('rate_limit_sleep_sec', 0) or 0),
trac_sqlite=trac['sqlite_path'],
trac_base_url=trac['base_url'].rstrip('/'),
include_ids=list(migrate.get('include_ticket_ids') or []),
exclude_ids=list(migrate.get('exclude_ticket_ids') or []),
prefix_trac_id_in_title=bool(migrate.get('prefix_trac_id_in_title', True)),
id_map_path=mapping_files.get('id_map_path', './trac_forgejo_id_map.json'),
user_map={str(k): str(v) for k, v in (users or {}).items()},
label_colors=labels.get('colors', {}),
pandoc_path=markdown.get('pandoc_path'),
use_light_conv=bool(markdown.get('use_lightweight_converter', True)),
dry_run=bool(migrate.get('dry_run', False)),
)
class Migrator:
def __init__(self, cfg: Config):
self.cfg = cfg
self.trac = Trac(cfg.trac_sqlite)
self.forge = Forgejo(cfg.forgejo_base, cfg.forgejo_owner, cfg.forgejo_repo, cfg.forgejo_token, rate_sleep=cfg.rate_sleep)
self.id_map = self._load_id_map(cfg.id_map_path)
# ---------- mapping persistence ----------
def _load_id_map(self, path: str) -> Dict[str, Any]:
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
return {}
def _save_id_map(self) -> None:
tmp = self.cfg.id_map_path + ".tmp"
with open(tmp, 'w', encoding='utf-8') as f:
json.dump(self.id_map, f, indent=2, ensure_ascii=False)
os.replace(tmp, self.cfg.id_map_path)
def _map_set(self, trac_id: int, issue_index: int) -> None:
self.id_map[str(trac_id)] = {'issue_index': issue_index}
self._save_id_map()
def _map_get(self, trac_id: int) -> Optional[int]:
entry = self.id_map.get(str(trac_id))
if entry:
return int(entry['issue_index'])
return None
# ---------- conversion helpers ----------
def _convert_body(self, t: TracTicket) -> str:
header = [
f"**Trac ticket #{t.id}**",
f"Reporter: `{t.reporter or ''}`",
f"Owner: `{t.owner or ''}`",
f"Created: {t.time.strftime('%Y-%m-%d %H:%M:%S %Z')}",
f"Last change: {t.changetime.strftime('%Y-%m-%d %H:%M:%S %Z')}",
f"Status: {t.status}"
+ (f" ({t.resolution})" if t.resolution else ""),
f"Original: {self.cfg.trac_base_url}/ticket/{t.id}",
"",
"---",
"",
]
body_raw = t.description or ""
if self.cfg.pandoc_path:
body_md = maybe_pandoc(body_raw, self.cfg.pandoc_path)
elif self.cfg.use_light_conv:
body_md = trac_to_markdown(body_raw)
else:
body_md = body_raw
return "\n".join(header) + body_md
def _labels_for(self, t: TracTicket) -> List[str]:
labs: List[str] = []
if t.component:
labs.append(f"component/{t.component}")
if t.type:
labs.append(f"type/{t.type}")
if t.priority:
labs.append(f"priority/{t.priority}")
if t.severity:
labs.append(f"severity/{t.severity}")
if t.version:
labs.append(f"version/{t.version}")
for kw in (t.keywords or '').replace(',', ' ').split():
labs.append(f"keyword/{kw}")
# status/resolution as labels (state handled separately)
labs.append(f"status/{t.status}")
if t.resolution:
labs.append(f"resolution/{t.resolution}")
return labs
def _ensure_labels(self, labels: Iterable[str]) -> None:
for ln in labels:
if not ln:
continue
color = self.cfg.label_colors.get(ln)
desc = "Imported from Trac"
if self.cfg.dry_run:
print(f"DRY-RUN: ensure label {ln} ({color or stable_color(ln)})")
else:
self.forge.ensure_label(ln, color=color, description=desc)
def _ensure_milestone(self, t: TracTicket) -> Optional[int]:
if not t.milestone:
return None
if self.cfg.dry_run:
print(f"DRY-RUN: ensure milestone {t.milestone}")
return None
return self.forge.ensure_milestone(t.milestone)
def _assignees(self, t: TracTicket) -> List[str]:
owner = (t.owner or '').strip()
if not owner:
return []
mapped = self.cfg.user_map.get(owner)
return [mapped] if mapped else []
# ---------- comments & attachments ----------
def _emit_comments(self, idx: int, t: TracTicket, changes: List[TracChange]) -> None:
# Only 'comment' field entries produce issue comments; other changes are summarized.
# We'll also emit non-empty status/owner changes to preserve history.
for ch in changes:
if ch.field == 'comment' and (ch.newvalue or '').strip():
text = ch.newvalue or ''
if self.cfg.pandoc_path:
text_md = maybe_pandoc(text, self.cfg.pandoc_path)
elif self.cfg.use_light_conv:
text_md = trac_to_markdown(text)
else:
text_md = text
body = banner(ch.author or 'unknown', ch.time) + text_md
if self.cfg.dry_run:
print(f"DRY-RUN: add comment to #{idx}: {body[:80]!r}...")
else:
self.forge.comment(idx, body)
elif ch.field in {"status", "owner", "milestone", "priority", "component", "resolution"}:
# summarize as small audit trail comment
oldv = ch.oldvalue or ""
newv = ch.newvalue or ""
if oldv == newv:
continue
body = banner(ch.author or 'unknown', ch.time) + f"**Change**: `{ch.field}` → `{newv}` (was `{oldv}`)"
if self.cfg.dry_run:
print(f"DRY-RUN: add audit comment to #{idx}: {body[:80]!r}...")
else:
self.forge.comment(idx, body)
def _emit_attachments(self, idx: int, t: TracTicket, atts: List[TracAttachment]) -> None:
if not atts:
return
for a in atts:
url = f"{self.cfg.trac_base_url}/attachment/ticket/{t.id}/{a.filename}"
meta = f"Attachment: **{a.filename}** ({a.size or '?'} bytes) — {url}"
body = banner(a.author or 'unknown', a.time) + meta
if self.cfg.dry_run:
print(f"DRY-RUN: add attachment note to #{idx}: {body}")
else:
self.forge.comment(idx, body)
# ---------- main unit of work ----------
def migrate_one(self, tid: int) -> None:
if self.cfg.exclude_ids and tid in self.cfg.exclude_ids:
print(f"Skip ticket {tid} (excluded)")
return
mapped = self._map_get(tid)
if mapped:
print(f"Ticket {tid} already migrated as issue #{mapped}")
return
t = self.trac.fetch_ticket(tid)
labels = self._labels_for(t)
self._ensure_labels(labels)
ms_id = self._ensure_milestone(t)
title = t.summary
if self.cfg.prefix_trac_id_in_title:
title = f"[Trac #{t.id}] {title}"
body = self._convert_body(t)
assignees = self._assignees(t)
if self.cfg.dry_run:
print(f"DRY-RUN: would create issue for Trac {tid}: title={title!r}, labels={labels}, assignees={assignees}, milestone={ms_id}")
idx = -1
else:
issue = self.forge.create_issue(title, body, labels, assignees, ms_id)
idx = int(issue['number']) if 'number' in issue else int(issue['index'])
self._map_set(tid, idx)
print(f"Created Forgejo issue #{idx} for Trac {tid}")
# comments & attachments
changes = self.trac.fetch_changes(tid)
atts = self.trac.fetch_attachments(tid)
if self.cfg.dry_run:
print(f"DRY-RUN: would add {sum(1 for c in changes if c.field=='comment' and (c.newvalue or '').strip())} comments and {len(atts)} attachments to issue #{idx}")
else:
self._emit_comments(idx, t, changes)
self._emit_attachments(idx, t, atts)
# close if needed
if t.status.lower() == 'closed':
if self.cfg.dry_run:
print(f"DRY-RUN: would close issue #{idx}")
else:
self.forge.close_issue(idx)
print(f"Closed issue #{idx} (resolution={t.resolution})")
def run(self) -> None:
if self.cfg.include_ids:
ids = self.cfg.include_ids
else:
ids = self.trac.fetch_ticket_ids()
for tid in ids:
try:
self.migrate_one(tid)
except Exception as e:
print(f"ERROR migrating ticket {tid}: {e}", file=sys.stderr)
# ----------------------------- CLI entry -----------------------------
def load_config(path: str) -> Config:
with open(path, 'r', encoding='utf-8') as f:
d = yaml.safe_load(f)
return Config.from_yaml(d)
def main() -> None:
ap = argparse.ArgumentParser(description="Migrate Trac tickets to Forgejo issues")
ap.add_argument('--config', required=True, help='Path to YAML config')
ap.add_argument('--dry-run', action='store_true', help='Force dry-run (overrides config)')
args = ap.parse_args()
cfg = load_config(args.config)
if args.dry_run:
cfg.dry_run = True
mig = Migrator(cfg)
mig.run()
if __name__ == '__main__':
main()
[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]
[-- Attachment #2: Type: text/plain, Size: 163 bytes --]
_______________________________________________
ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org
next prev parent reply other threads:[~2025-09-28 9:19 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-14 21:23 [FFmpeg-devel] " Michael Niedermayer via ffmpeg-devel
2025-09-15 11:09 ` [FFmpeg-devel] " Michael Niedermayer via ffmpeg-devel
2025-09-15 11:37 ` Michael Niedermayer via ffmpeg-devel
2025-09-15 12:06 ` Timo Rothenpieler via ffmpeg-devel
2025-09-15 12:30 ` Michael Niedermayer via ffmpeg-devel
2025-09-15 12:47 ` Timo Rothenpieler via ffmpeg-devel
2025-09-15 12:57 ` Michael Niedermayer via ffmpeg-devel
2025-09-15 13:05 ` Michael Niedermayer via ffmpeg-devel
2025-09-15 17:19 ` Timo Rothenpieler via ffmpeg-devel
2025-09-15 18:26 ` Michael Niedermayer via ffmpeg-devel
2025-09-15 18:35 ` Timo Rothenpieler via ffmpeg-devel
2025-09-15 19:09 ` Gyan Doshi via ffmpeg-devel
2025-09-15 21:46 ` Michael Niedermayer via ffmpeg-devel
2025-09-16 4:39 ` Gyan Doshi via ffmpeg-devel
2025-09-23 21:50 ` [FFmpeg-devel] trac ticket statistics Was: " Michael Niedermayer via ffmpeg-devel
2025-09-24 10:56 ` [FFmpeg-devel] " Gyan Doshi via ffmpeg-devel
2025-09-28 0:44 ` Michael Niedermayer via ffmpeg-devel
2025-09-28 6:13 ` Gyan Doshi via ffmpeg-devel
2025-09-23 22:33 ` [FFmpeg-devel] " Michael Niedermayer via ffmpeg-devel
2025-09-15 22:36 ` Michael Niedermayer via ffmpeg-devel
2025-09-28 7:54 ` Rémi Denis-Courmont via ffmpeg-devel
2025-09-28 8:51 ` Michael Niedermayer via ffmpeg-devel
2025-09-28 11:32 ` Rémi Denis-Courmont via ffmpeg-devel
2025-09-28 9:18 ` Michael Niedermayer via ffmpeg-devel [this message]
2025-09-28 9:59 ` Jacob Lifshay via ffmpeg-devel
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250928091840.GC29660@pb2 \
--to=ffmpeg-devel@ffmpeg.org \
--cc=michael@niedermayer.cc \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git