diff --git a/GUIDE.md b/GUIDE.md index e6a1fd3..7d33254 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -34,6 +34,39 @@ for mention in client.mentions.list(entity_id=ramp.id, after="2026-09-01", befor A name where an id belongs raises `BadRequestError` with the code `id_required`. Its message names the parameter and the resolve call. +## Compare a speaker's comments over time + +Compare what Jensen Huang said about TSMC in August and September 2026. Resolve his name, filter by who is speaking, and retrieve dated passages to review side by side. + +```python +from urllib.parse import urljoin +from arcmira import Arcmira + +client = Arcmira() +match = client.entities.resolve( + q="Jensen Huang", + type="person", + context="NVIDIA CEO discussing chip suppliers", +) +if match.best is None: + raise LookupError(match.ask or match.suggested or match.note) + +for after, before in [("2026-08-01", "2026-09-01"), ("2026-09-01", "2026-10-01")]: + hits = client.transcripts.search(q="TSMC", by=match.best.id, after=after, before=before, limit=5) + print(hits.window, hits.search_index, hits.note) + for hit in hits.chunks: + print(hit.published_at, hit.text) + print(urljoin("https://arcmira.com", hit.watch_url)) +``` + +If the resolver returns a suggested match or several options, confirm the intended person before continuing. + +The `by` filter selects passages in which the resolved person speaks. Passages can include other speakers, so keep the speaker labels and read the surrounding exchange. Dates filter publication time, which may differ from when a recording was made. + +In a search on October 15, 2026, these windows returned comments about [wafer demand in August](https://arcmira.com/watch?v=JAg8MM5onsE&t=511) and [US manufacturing commitments in September](https://arcmira.com/watch?v=HjurAWAr_nY&t=5274). They also returned a shorter upload of the same August exchange. Treat repeated clips as one source when comparing statements. + +Use the reported `window`, `search_index`, and `note` to describe coverage. Speaker labels cover only part of the index, and results depend on your plan's date window. Review the passages before concluding that a stance changed. Replace the dates and topic to investigate another period or supplier. + ## Dates Every dated read takes `after` and `before`. The window is half-open, `[after, before)`. Each accepts an ISO date (`2026-09-01`) or a datetime with an offset (`2026-09-01T00:00:00Z`), read in UTC. Each dated read echoes the window it applied in `window`. diff --git a/README.md b/README.md index 51d14ad..a51827e 100644 --- a/README.md +++ b/README.md @@ -12,27 +12,31 @@ uv add arcmira `pip install arcmira` works too. Set `ARCMIRA_API_KEY` to your API key. Save an example as `example.py` and run `python example.py` in your environment. -## Find quotes with source links +## Search clips and quotes in YouTube videos -Search YouTube and podcast transcripts by topic. Keep the source link beside each quote so readers can check the original. +Find spoken passages in YouTube videos and podcasts, then jump to the matching moment. Search a topic, company, or phrase. ```python from urllib.parse import urljoin from arcmira import Arcmira client = Arcmira() -hits = client.transcripts.search(q="agent payments", limit=5) +hits = client.transcripts.search(q="TSMC", limit=5) for hit in hits.chunks: print(hit.text) print(urljoin("https://arcmira.com", hit.watch_url)) print(hits.note) ``` -Search covers indexed transcripts within your plan's date window. Check `window`, `search_index`, and `note` before treating an empty result as evidence that nobody said it. +For a research workflow, [compare what Jensen Huang said about TSMC across two months](https://github.com/arcmira/python/blob/master/GUIDE.md#compare-a-speakers-comments-over-time). Filter by speaker and date, then inspect the quoted passages in context. -## Pull a YouTube transcript +Search covers indexed transcripts within your plan's date window. Check `window`, `search_index`, and `note` before drawing conclusions from missing results. -Read available captions with timestamps. Replace the video id with the video you need. +## Transcribe a YouTube video + +Start free with 1,000 credits a month, enough for up to 250 caption transcripts of videos up to 15 minutes each. Captions use 4 credits per started 15 minutes from the same balance as search and alerts. [Plans and usage](https://arcmira.com/pricing). + +Pull an available YouTube caption track with timestamps. Replace the video id below with yours. For a new transcription of the audio with speaker labels, use Premium on a paid plan. ```python from arcmira import Arcmira @@ -44,11 +48,13 @@ if transcript.state == "ready": print(line.start, line.text) ``` -For a Premium transcript with speaker labels, first call `client.transcripts.quote(video_id)` for a free quote, then read with `quality="premium"`. A Premium read uses credits from your plan, then your on-demand budget. It may return `pending`; poll after `job.next_poll_seconds`. A `failed` result needs an explicit retry. [Premium states and polling](https://github.com/arcmira/python/blob/master/GUIDE.md#premium-transcripts). +Get a free cost estimate with `client.transcripts.quote(video_id)`, then read with `quality="premium"`. Premium uses credits from your plan, then your on-demand budget. If `pending`, poll after `job.next_poll_seconds`. [Premium states, polling, and retries](https://github.com/arcmira/python/blob/master/GUIDE.md#premium-transcripts). + +## Set up YouTube monitoring for your company -## Monitor company mentions +Follow 2 names and get up to 40 alert deliveries a month on a free account, if you use all 1,000 monthly credits for alerts. Each delivery uses 25 credits. Get alerts by email or [Slack](https://arcmira.com/docs/monitors#delivery), or use a [webhook](https://arcmira.com/docs/monitors#verify-webhooks) on a paid plan. -Follow an exact name in newly analyzed media, even before it is indexed. Replace `you@example.com` with your alert address. External recipients must confirm before delivery. +Follow your company or a competitor in newly analyzed YouTube videos and podcasts. Replace `Ramp` and `you@example.com` below. External email recipients must confirm before delivery. ```python from arcmira import Arcmira @@ -68,7 +74,7 @@ for result in added.results: print(result.tracker_id if result.attached else result.reason) ``` -This creates a monitor and tracker in your account. Creation is free; delivered alerts use credits from your plan, then your on-demand budget. Check each `attached` result because a name may already belong to another monitor. For retries of writes, reuse the same `idempotency_key`. +This creates a monitor and tracker in your account. Check each `attached` result. Alerts cover newly analyzed episodes published within the last 14 days. Multiple recipients or channels can use multiple deliveries per mention. For retries of writes, reuse the same `idempotency_key`. ## Choose the next task diff --git a/llms.txt b/llms.txt index a9d4f03..a783172 100644 --- a/llms.txt +++ b/llms.txt @@ -6,7 +6,7 @@ This repo is the `arcmira` Python SDK for the Arcmira API: synchronous and async ## Tasks -Use Arcmira to pull YouTube transcripts, find quotes with timestamps, research podcast sponsors, and monitor company mentions. Start with the README examples. Keep source links beside quotes, preserve the reported coverage window, and distinguish captions from Premium transcripts. For agent tasks, connect the MCP server; for an application, use this SDK. +Use Arcmira to pull YouTube transcripts, search clips and quotes in YouTube videos, compare a speaker's comments over time, research podcast sponsors, and set up YouTube monitoring for a company. Search returns passages with links to the matching moment. It does not export video clips. Start with the README examples. Keep source links beside quotes, preserve the reported coverage window, and distinguish captions from Premium transcripts. For agent tasks, connect the MCP server; for an application, use this SDK. ## Install diff --git a/tests/fixtures/readme-responses.json b/tests/fixtures/readme-responses.json index 4858739..6975508 100644 --- a/tests/fixtures/readme-responses.json +++ b/tests/fixtures/readme-responses.json @@ -39,7 +39,7 @@ "channel_id": "x", "source": "x", "published_at": "x", - "text": "agent payments on air", + "text": "TSMC on air", "start_seconds": 1, "watch_url": "https://arcmira.com/watch?v=dQw4w9WgXcQ&t=1", "score": 1 @@ -116,5 +116,35 @@ "attached": true } ] + }, + "resolve_entity": { + "query": "x", + "context": "x", + "confidence": "exact", + "best": { + "id": "ent_14", + "name": "Jensen Huang", + "slug": "x", + "type": "person", + "youtube_channel_id": "x", + "description": "x", + "page": "x", + "match": "exact" + }, + "suggested": null, + "ask": null, + "candidates": [ + { + "id": "x", + "name": "x", + "slug": "x", + "type": "x", + "youtube_channel_id": "x", + "description": "x", + "page": "x", + "match": "exact" + } + ], + "note": "x" } } diff --git a/tests/test_readme.py b/tests/test_readme.py index 8c8c12b..86ff4f4 100644 --- a/tests/test_readme.py +++ b/tests/test_readme.py @@ -34,12 +34,38 @@ def respond(request): for code in examples: exec(compile(code, 'README.md', 'exec'), {}) printed = output.getvalue().splitlines() - self.assertIn('agent payments on air', printed) + self.assertIn('TSMC on air', printed) self.assertIn('https://arcmira.com/watch?v=dQw4w9WgXcQ&t=1', printed) self.assertIn('0.0 Welcome back to the show.', printed) self.assertIn('trk_200', printed) self.assertEqual([(r.method, r.url.path) for r in requests], list(routes)) - self.assertEqual(dict(requests[0].url.params), dict(q='agent payments', limit='5')) + self.assertEqual(dict(requests[0].url.params), dict(q='TSMC', limit='5')) self.assertEqual(json.loads(requests[2].content), dict(name='Company watch', notify_frequency='daily', notify_emails=['you@example.com'])) self.assertEqual(json.loads(requests[3].content), dict(names=[dict(name='Ramp', type='organization')])) self.assertTrue(all(r.headers['Authorization'] == 'Bearer fixture-key' for r in requests)) + + def test_supplier_comparison_resolves_speaker_and_searches_both_windows(self): + requests = [] + def respond(request): + requests.append(request) + name = 'resolve_entity' if request.url.path == '/v1/entities/resolve' else 'search' + return httpx.Response(200, json=RESPONSES[name]) + section = (ROOT / 'GUIDE.md').read_text().split("## Compare a speaker's comments over time\n")[1].split('\n## ')[0] + code = re.search(r'```python\n(.*?)```', section, re.S).group(1) + output = io.StringIO() + with httpx.Client(transport=httpx.MockTransport(respond)) as http: + client = Arcmira(api_key='fixture-key', httpx_client=http, max_retries=0) + with patch('arcmira.Arcmira', return_value=client), contextlib.redirect_stdout(output): + exec(compile(code, 'GUIDE.md', 'exec'), {}) + self.assertEqual([(r.method, r.url.path) for r in requests], [ + ('GET', '/v1/entities/resolve'), ('GET', '/v1/search'), ('GET', '/v1/search'), + ]) + self.assertEqual(dict(requests[0].url.params), dict( + q='Jensen Huang', type='person', context='NVIDIA CEO discussing chip suppliers', + )) + self.assertEqual([dict(r.url.params) for r in requests[1:]], [ + dict(q='TSMC', by='ent_14', after='2026-08-01', before='2026-09-01', limit='5'), + dict(q='TSMC', by='ent_14', after='2026-09-01', before='2026-10-01', limit='5'), + ]) + self.assertEqual(output.getvalue().count('TSMC on air'), 2) + self.assertEqual(output.getvalue().count('https://arcmira.com/watch?v=dQw4w9WgXcQ&t=1'), 2)