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 http://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/ http://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