Python だけで ソラカメ の MPEG-DASH を再生してみた

目次

SORACOM Advent Calendar 2023

この記事は SORACOM Advent Calendar 2023 の11日目の記事です。

qiita.com

今回はソラカメの API を利用して、ストリーミング映像を取得する際に利用する MPEG-DASH (Dynamic Adaptive Streaming over HTTP) を、FFmpeg といった外部のツールを利用せずに Python だけで再生できるか試してみました。

sora-cam.com

users.soracom.io

Python だけで MPEG-DASH の操作ができると、AI / ML との連携やローカル環境で自前のモデルでいろいろな試行錯誤が行えるようになると思います。

あと毎回のお約束ですが、すべての内容は個人で検証したもの になり、保証や確証はないのでもし利用する場合は必ずご自身で確認の上でお願いします。文中の現時点は2023年12月10日を指しています。

MPEG-DASH とは?

MPEG-DASH はストリーミング方式の1つで、DASH は「Dynamic Adaptive Streaming over HTTP」の略です。標準化されており、動画を小さな単位に分割し、異なる品質でエンコードできます。これにより、さまざまな品質の動画をストリーミングしたり、動画の途中で品質を切り替えることができます。

詳細は以下を参照してください。

en.wikipedia.org

https://www.cloudflare.com/ja-jp/learning/video/what-is-mpeg-dash/www.cloudflare.com

ソラカメとは?

ソラカメ (Soracom Cloud Camera Services) は、クラウド環境への録画機能があるクラウド型カメラです。クラウドに保存した映像をAPI やアプリを使って確認できます。ソラカメは価格の面や設定面で導入が容易であるため、これまで活用が難しかった部分に設置して試してみることが可能となります。

詳細は以下を参照してください。

sora-cam.com

soracom.jp

ソラカメの API を利用して、ストリーミング映像を取得する場合、現時点では MPEG-DASH となります。

ソラカメの MPEG-DASH を再生するには?

ソラカメのストリーミング映像は、MPEG-DASH 方式で配信されています。そのためソラカメの映像を再生するには、スマホアプリ、SORACOMユーザーコンソールなど、すでに用意されたものを活用する方が手間がなく便利です。

users.soracom.io

その他の方法だと VLC media playerdash.js の Reference Client で再生したり、FFmpeg を利用することで、単純な再生だけでなく、再エンコード処理をはじめとした他の操作を行うこともできます。

www.videolan.org

dashif.org

reference.dashif.org

ffmpeg.org

しかし、状況によってはこれらの便利なツールが利用できない場合や、再エンコードや配信のために利用するわけではなく、AI / ML のインプットとして単純に映像ストリームを利用したい場合があるかもしれないです。そんな時のために、今回は Python だけを利用して MPEG-DASH の映像ストリームを取得して再生してみたいと思います。

MPEG-DASH の MPD ファイルとは?

MPEG-DASH の MPD ファイル(Media Presentation Description)は、DASH(Dynamic Adaptive Streaming over HTTP)プロトコルで使用される XML フォーマットのファイルです。ファイルには、動画ストリーミングのための重要な情報が含まれていて、DASH 対応のプレーヤーが動画のダウンロードと再生を行うために必要なファイルです。

実際にソラカメの API を利用して、ストリーム映像の取得を行うと URL が取得できますが、この取得できる URL は MPD ファイルの URL (https://xxxxxx.kinesisvideo.ap-northeast-1.amazonaws.com/dash/v1/getDASHManifest.mpd?SessionToken=xxxxx)となっています。通常は、この MPD ファイルの URL を DASH 対応のプレーヤーに与えることで、内部的には MPD ファイルを読み解いて必要な動画のダウンロードと再生が実行されています。

users.soracom.io

この動作は、比較的簡単に確認することができます。例えばソラカメの API で取得したストリーミング用の URL(MPD ファイルの URL)を、dash.js のReference Client で再生します。再生を実行している Web ブラウザのデベロッパーツールを立ち上げて、Network パネルを確認してみると、定期的に動画を取得している様子が確認できるかと思います。

tech.nri-net.com

ということで、MPD ファイルを読み解いて動画をダウンロードできれば Python でも同じことはできそうな気がしてきましたね。

MPEG-DASH の MPD ファイル を Python で parse してみる

まず初めに、MPD ファイルを parse していきます。MPD ファイルは XML なのでそんなに考えなくても対応できそうです。

実際の MPD ファイルは以下のようなファイルです。

<?xml version='1.0' encoding='UTF-8'?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:kvs="urn:aws:kinesisvideo:mpd:2019" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" type="static" availabilityStartTime="2023-12-09T07:58:21.212Z" mediaPresentationDuration="PT2M20.934S" minBufferTime="PT1S">
  <Period id="0" start="PT0S" duration="PT2M20.934S">
    <AdaptationSet segmentAlignment="true" mimeType="video/mp4" startWithSAP="1">
      <SegmentTemplate media="getMP4MediaFragment.mp4?SessionToken=xxxxx&amp;SequenceNumber=$Number$&amp;TrackNumber=1" initialization="getMP4InitFragment.mp4?SessionToken=xxxxx&amp;TrackNumber=1" timescale="1000" startNumber="1">
        <SegmentTimeline>
          <S t="0" d="1001" kvs:ts="2023-12-09T07:58:21.212Z" kvs:fn="91343852333215703524729639126345842704639415673"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:22.210Z" kvs:fn="91343852333215703529681399283487364038145840473"/>
          <S d="996" kvs:ts="2023-12-09T07:58:23.213Z" kvs:fn="91343852333215703534633159440628885421286199850"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:24.211Z" kvs:fn="91343852333215703539584919597770406769204217591"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:25.210Z" kvs:fn="91343852333215703544536679754911928154339861911"/>
          <S d="996" kvs:ts="2023-12-09T07:58:26.213Z" kvs:fn="91343852333215703549488439912053449515427341534"/>
          <S d="1002" kvs:ts="2023-12-09T07:58:27.210Z" kvs:fn="91343852333215703554440200069194970873551962088"/>
          <S d="998" kvs:ts="2023-12-09T07:58:28.210Z" kvs:fn="91343852333215703559391960226336492251454191872"/>
          <S d="997" kvs:ts="2023-12-09T07:58:29.211Z" kvs:fn="91343852333215703564343720383478013608587928635"/>
          <S d="1004" kvs:ts="2023-12-09T07:58:30.208Z" kvs:fn="91343852333215703569295480540619534972485677069"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:31.209Z" kvs:fn="91343852333215703574247240697761056341755407385"/>
          <S d="997" kvs:ts="2023-12-09T07:58:32.210Z" kvs:fn="91343852333215703579199000854902577713961791561"/>
          <S d="1002" kvs:ts="2023-12-09T07:58:33.208Z" kvs:fn="91343852333215703584150761012044099076278831377"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:34.208Z" kvs:fn="91343852333215703589102521169185620452031428557"/>
          <S d="997" kvs:ts="2023-12-09T07:58:35.209Z" kvs:fn="91343852333215703594054281326327141822373531240"/>
          <S d="1003" kvs:ts="2023-12-09T07:58:36.207Z" kvs:fn="91343852333215703599006041483468663184031284812"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:37.207Z" kvs:fn="91343852333215703603957801640610184559497023988"/>
          <S d="998" kvs:ts="2023-12-09T07:58:38.208Z" kvs:fn="91343852333215703608909561797751705925074656222"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:39.206Z" kvs:fn="91343852333215703613861321954893227288743741351"/>
          <S d="998" kvs:ts="2023-12-09T07:58:40.207Z" kvs:fn="91343852333215703618813082112034748659902149052"/>
          <S d="997" kvs:ts="2023-12-09T07:58:41.207Z" kvs:fn="91343852333215703623764842269176270031831648400"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:42.205Z" kvs:fn="91343852333215703628716602426317791388143694876"/>
          <S d="998" kvs:ts="2023-12-09T07:58:43.206Z" kvs:fn="91343852333215703633668362583459312764630836931"/>
          <S d="998" kvs:ts="2023-12-09T07:58:44.206Z" kvs:fn="91343852333215703638620122740600834126611228269"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:45.204Z" kvs:fn="91343852333215703643571882897742355500545349018"/>
          <S d="998" kvs:ts="2023-12-09T07:58:46.205Z" kvs:fn="91343852333215703648523643054883876869407937734"/>
          <S d="997" kvs:ts="2023-12-09T07:58:47.206Z" kvs:fn="91343852333215703653475403212025398237883696543"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:48.204Z" kvs:fn="91343852333215703658427163369166919596413601907"/>
          <S d="998" kvs:ts="2023-12-09T07:58:49.204Z" kvs:fn="91343852333215703663378923526308441007759581867"/>
          <S d="998" kvs:ts="2023-12-09T07:58:50.204Z" kvs:fn="91343852333215703668330683683449962347049124213"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:51.203Z" kvs:fn="91343852333215703673282443840591483699803353577"/>
          <S d="998" kvs:ts="2023-12-09T07:58:52.203Z" kvs:fn="91343852333215703678234203997733005072327464618"/>
          <S d="998" kvs:ts="2023-12-09T07:58:53.203Z" kvs:fn="91343852333215703683185964154874526465491531013"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:54.202Z" kvs:fn="91343852333215703688137724312016047804996799635"/>
          <S d="998" kvs:ts="2023-12-09T07:58:55.202Z" kvs:fn="91343852333215703693089484469157569198675301585"/>
          <S d="998" kvs:ts="2023-12-09T07:58:56.202Z" kvs:fn="91343852333215703698041244626299090566624458401"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:57.201Z" kvs:fn="91343852333215703702993004783440611913201513403"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:58.200Z" kvs:fn="91343852333215703707944764940582133301407551807"/>
          <S d="997" kvs:ts="2023-12-09T07:58:59.202Z" kvs:fn="91343852333215703712896525097723654664921362969"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:00.200Z" kvs:fn="91343852333215703717848285254865176006879908031"/>
          <S d="997" kvs:ts="2023-12-09T07:59:01.201Z" kvs:fn="91343852333215703722800045412006697426114573620"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:02.200Z" kvs:fn="91343852333215703727751805569148218755816554703"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:03.199Z" kvs:fn="91343852333215703732703565726289740119145777387"/>
          <S d="998" kvs:ts="2023-12-09T07:59:04.199Z" kvs:fn="91343852333215703737655325883431261489097116733"/>
          <S d="998" kvs:ts="2023-12-09T07:59:05.200Z" kvs:fn="91343852333215703742607086040572782864226764134"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:06.198Z" kvs:fn="91343852333215703747558846197714304218227347320"/>
          <S d="996" kvs:ts="2023-12-09T07:59:07.201Z" kvs:fn="91343852333215703752510606354855825600711209580"/>
          <S d="998" kvs:ts="2023-12-09T07:59:08.200Z" kvs:fn="91343852333215703757462366511997346960412678389"/>
          <S d="996" kvs:ts="2023-12-09T07:59:09.202Z" kvs:fn="91343852333215703762414126669138868328263710307"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:10.197Z" kvs:fn="91343852333215703767365886826280389704922436148"/>
          <S d="998" kvs:ts="2023-12-09T07:59:11.198Z" kvs:fn="91343852333215703772317646983421911059565925392"/>
          <S d="1005" kvs:ts="2023-12-09T07:59:12.196Z" kvs:fn="91343852333215703777269407140563432429892186724"/>
          <S d="997" kvs:ts="2023-12-09T07:59:13.198Z" kvs:fn="91343852333215703782221167297704953813368133151"/>
          <S d="995" kvs:ts="2023-12-09T07:59:14.200Z" kvs:fn="91343852333215703787172927454846475169089211094"/>
          <S d="1004" kvs:ts="2023-12-09T07:59:15.195Z" kvs:fn="91343852333215703792124687611987996530657499624"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:16.195Z" kvs:fn="91343852333215703797076447769129517922095635738"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:17.196Z" kvs:fn="91343852333215703802028207926271039278930289553"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:18.196Z" kvs:fn="91343852333215703806979968083412560639125380573"/>
          <S d="998" kvs:ts="2023-12-09T07:59:19.195Z" kvs:fn="91343852333215703811931728240554082029047690586"/>
          <S d="997" kvs:ts="2023-12-09T07:59:20.196Z" kvs:fn="91343852333215703816883488397695603387750476174"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:21.194Z" kvs:fn="91343852333215703821835248554837124737404800217"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:22.193Z" kvs:fn="91343852333215703826787008711978646109146001286"/>
          <S d="997" kvs:ts="2023-12-09T07:59:23.195Z" kvs:fn="91343852333215703831738768869120167474017114838"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:24.193Z" kvs:fn="91343852333215703836690529026261688834698383107"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:25.193Z" kvs:fn="91343852333215703841642289183403210216519737681"/>
          <S d="998" kvs:ts="2023-12-09T07:59:26.193Z" kvs:fn="91343852333215703846594049340544731565600600899"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:27.192Z" kvs:fn="91343852333215703851545809497686252940163647626"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:28.193Z" kvs:fn="91343852333215703856497569654827774310034023472"/>
          <S d="998" kvs:ts="2023-12-09T07:59:29.192Z" kvs:fn="91343852333215703861449329811969295683212863901"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:30.191Z" kvs:fn="91343852333215703866401089969110817048856151526"/>
          <S d="998" kvs:ts="2023-12-09T07:59:31.192Z" kvs:fn="91343852333215703871352850126252338450170327197"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:32.191Z" kvs:fn="91343852333215703876304610283393859788926569260"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:33.190Z" kvs:fn="91343852333215703881256370440535381156069529009"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:34.190Z" kvs:fn="91343852333215703886208130597676902529391842243"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:35.191Z" kvs:fn="91343852333215703891159890754818423902612351553"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:36.190Z" kvs:fn="91343852333215703896111650911959945261759082449"/>
          <S d="1003" kvs:ts="2023-12-09T07:59:37.189Z" kvs:fn="91343852333215703901063411069101466647617216149"/>
          <S d="998" kvs:ts="2023-12-09T07:59:38.190Z" kvs:fn="91343852333215703906015171226242988017449587601"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:39.188Z" kvs:fn="91343852333215703910966931383384509351344036949"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:40.188Z" kvs:fn="91343852333215703915918691540526030723932802908"/>
          <S d="998" kvs:ts="2023-12-09T07:59:41.189Z" kvs:fn="91343852333215703920870451697667552086417156042"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:42.187Z" kvs:fn="91343852333215703925822211854809073454287366705"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:43.187Z" kvs:fn="91343852333215703930773972011950594827839141141"/>
          <S d="996" kvs:ts="2023-12-09T07:59:44.190Z" kvs:fn="91343852333215703935725732169092116194411814209"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:45.186Z" kvs:fn="91343852333215703940677492326233637557677318663"/>
          <S d="998" kvs:ts="2023-12-09T07:59:46.187Z" kvs:fn="91343852333215703945629252483375158940826714326"/>
          <S d="998" kvs:ts="2023-12-09T07:59:47.187Z" kvs:fn="91343852333215703950581012640516680294209549768"/>
          <S d="1003" kvs:ts="2023-12-09T07:59:48.186Z" kvs:fn="91343852333215703955532772797658201665798537520"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:49.186Z" kvs:fn="91343852333215703960484532954799723041304309232"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:50.186Z" kvs:fn="91343852333215703965436293111941244400480811698"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:51.185Z" kvs:fn="91343852333215703970388053269082765787997272409"/>
          <S d="997" kvs:ts="2023-12-09T07:59:52.186Z" kvs:fn="91343852333215703975339813426224287150493119161"/>
          <S d="997" kvs:ts="2023-12-09T07:59:53.186Z" kvs:fn="91343852333215703980291573583365808529427439091"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:54.184Z" kvs:fn="91343852333215703985243333740507329872396810594"/>
          <S d="998" kvs:ts="2023-12-09T07:59:55.184Z" kvs:fn="91343852333215703990195093897648851249556434180"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:56.184Z" kvs:fn="91343852333215703995146854054790372604023498907"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:57.183Z" kvs:fn="91343852333215704000098614211931893984032624967"/>
          <S d="998" kvs:ts="2023-12-09T07:59:58.184Z" kvs:fn="91343852333215704005050374369073415341558035381"/>
          <S d="997" kvs:ts="2023-12-09T07:59:59.184Z" kvs:fn="91343852333215704010002134526214936707567304371"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:00.182Z" kvs:fn="91343852333215704014953894683356458084266234021"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:01.182Z" kvs:fn="91343852333215704019905654840497979454853954346"/>
          <S d="997" kvs:ts="2023-12-09T08:00:02.183Z" kvs:fn="91343852333215704024857414997639500815572031422"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:03.182Z" kvs:fn="91343852333215704029809175154781022186570848159"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:04.181Z" kvs:fn="91343852333215704034760935311922543563899855522"/>
          <S d="998" kvs:ts="2023-12-09T08:00:05.182Z" kvs:fn="91343852333215704039712695469064064921908990389"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:06.180Z" kvs:fn="91343852333215704044664455626205586295211281904"/>
          <S d="998" kvs:ts="2023-12-09T08:00:07.180Z" kvs:fn="91343852333215704049616215783347107670076647589"/>
          <S d="997" kvs:ts="2023-12-09T08:00:08.182Z" kvs:fn="91343852333215704054567975940488629031317371241"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:09.179Z" kvs:fn="91343852333215704059519736097630150399754433787"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:10.180Z" kvs:fn="91343852333215704064471496254771671781924511250"/>
          <S d="997" kvs:ts="2023-12-09T08:00:11.181Z" kvs:fn="91343852333215704069423256411913193131811709641"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:12.178Z" kvs:fn="91343852333215704074375016569054714505589391101"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:13.178Z" kvs:fn="91343852333215704079326776726196235878240959274"/>
          <S d="998" kvs:ts="2023-12-09T08:00:14.179Z" kvs:fn="91343852333215704084278536883337757236853824140"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:15.178Z" kvs:fn="91343852333215704089230297040479278608994999830"/>
          <S d="998" kvs:ts="2023-12-09T08:00:16.178Z" kvs:fn="91343852333215704094182057197620799981135040031"/>
          <S d="997" kvs:ts="2023-12-09T08:00:17.179Z" kvs:fn="91343852333215704099133817354762321330080620038"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:18.177Z" kvs:fn="91343852333215704104085577511903842701180337552"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:19.177Z" kvs:fn="91343852333215704109037337669045364075483794638"/>
          <S d="998" kvs:ts="2023-12-09T08:00:20.177Z" kvs:fn="91343852333215704113989097826186885442173482996"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:21.176Z" kvs:fn="91343852333215704118940857983328406807819366983"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:22.175Z" kvs:fn="91343852333215704123892618140469928185224537147"/>
          <S d="997" kvs:ts="2023-12-09T08:00:23.177Z" kvs:fn="91343852333215704128844378297611449541890474942"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:24.175Z" kvs:fn="91343852333215704133796138454752970912981491389"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:25.175Z" kvs:fn="91343852333215704138747898611894492288343747538"/>
          <S d="998" kvs:ts="2023-12-09T08:00:26.176Z" kvs:fn="91343852333215704143699658769036013653896960550"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:27.174Z" kvs:fn="91343852333215704148651418926177535024101316267"/>
          <S d="998" kvs:ts="2023-12-09T08:00:28.175Z" kvs:fn="91343852333215704153603179083319056394402943758"/>
          <S d="998" kvs:ts="2023-12-09T08:00:29.175Z" kvs:fn="91343852333215704158554939240460577761622848457"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:30.173Z" kvs:fn="91343852333215704163506699397602099127324849081"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:31.174Z" kvs:fn="91343852333215704168458459554743620513526051737"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:32.174Z" kvs:fn="91343852333215704173410219711885141863712428816"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:33.173Z" kvs:fn="91343852333215704178361979869026663236153824465"/>
          <S d="998" kvs:ts="2023-12-09T08:00:34.173Z" kvs:fn="91343852333215704183313740026168184616203150113"/>
          <S d="998" kvs:ts="2023-12-09T08:00:35.173Z" kvs:fn="91343852333215704188265500183309705966971460559"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:36.172Z" kvs:fn="91343852333215704193217260340451227332516294301"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:37.171Z" kvs:fn="91343852333215704198169020497592748714842021214"/>
          <S d="997" kvs:ts="2023-12-09T08:00:38.173Z" kvs:fn="91343852333215704203120780654734270073652079153"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:39.170Z" kvs:fn="91343852333215704208072540811875791437991841660"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:40.170Z" kvs:fn="91343852333215704213024300969017312839347172304"/>
          <S d="995" kvs:ts="2023-12-09T08:00:41.174Z" kvs:fn="91343852333215704217976061126158834177854986059"/>
        </SegmentTimeline>
      </SegmentTemplate>
      <Representation id="video" bandwidth="977582" width="1920" height="1080" frameRate="19.98" codecs="avc1.4d0029" scanType="progressive"/>
    </AdaptationSet>
    <AdaptationSet segmentAlignment="true" mimeType="audio/mp4" startWithSAP="1">
      <SegmentTemplate media="getMP4MediaFragment.mp4?SessionToken=xxxxx&amp;SequenceNumber=$Number$&amp;TrackNumber=2" initialization="getMP4InitFragment.mp4?SessionToken=xxxxx&amp;TrackNumber=2" timescale="1000" startNumber="1">
        <SegmentTimeline>
          <S t="0" d="1001" kvs:ts="2023-12-09T07:58:21.212Z" kvs:fn="91343852333215703524729639126345842704639415673"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:22.210Z" kvs:fn="91343852333215703529681399283487364038145840473"/>
          <S d="996" kvs:ts="2023-12-09T07:58:23.213Z" kvs:fn="91343852333215703534633159440628885421286199850"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:24.211Z" kvs:fn="91343852333215703539584919597770406769204217591"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:25.210Z" kvs:fn="91343852333215703544536679754911928154339861911"/>
          <S d="996" kvs:ts="2023-12-09T07:58:26.213Z" kvs:fn="91343852333215703549488439912053449515427341534"/>
          <S d="1002" kvs:ts="2023-12-09T07:58:27.210Z" kvs:fn="91343852333215703554440200069194970873551962088"/>
          <S d="998" kvs:ts="2023-12-09T07:58:28.210Z" kvs:fn="91343852333215703559391960226336492251454191872"/>
          <S d="997" kvs:ts="2023-12-09T07:58:29.211Z" kvs:fn="91343852333215703564343720383478013608587928635"/>
          <S d="1004" kvs:ts="2023-12-09T07:58:30.208Z" kvs:fn="91343852333215703569295480540619534972485677069"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:31.209Z" kvs:fn="91343852333215703574247240697761056341755407385"/>
          <S d="997" kvs:ts="2023-12-09T07:58:32.210Z" kvs:fn="91343852333215703579199000854902577713961791561"/>
          <S d="1002" kvs:ts="2023-12-09T07:58:33.208Z" kvs:fn="91343852333215703584150761012044099076278831377"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:34.208Z" kvs:fn="91343852333215703589102521169185620452031428557"/>
          <S d="997" kvs:ts="2023-12-09T07:58:35.209Z" kvs:fn="91343852333215703594054281326327141822373531240"/>
          <S d="1003" kvs:ts="2023-12-09T07:58:36.207Z" kvs:fn="91343852333215703599006041483468663184031284812"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:37.207Z" kvs:fn="91343852333215703603957801640610184559497023988"/>
          <S d="998" kvs:ts="2023-12-09T07:58:38.208Z" kvs:fn="91343852333215703608909561797751705925074656222"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:39.206Z" kvs:fn="91343852333215703613861321954893227288743741351"/>
          <S d="998" kvs:ts="2023-12-09T07:58:40.207Z" kvs:fn="91343852333215703618813082112034748659902149052"/>
          <S d="997" kvs:ts="2023-12-09T07:58:41.207Z" kvs:fn="91343852333215703623764842269176270031831648400"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:42.205Z" kvs:fn="91343852333215703628716602426317791388143694876"/>
          <S d="998" kvs:ts="2023-12-09T07:58:43.206Z" kvs:fn="91343852333215703633668362583459312764630836931"/>
          <S d="998" kvs:ts="2023-12-09T07:58:44.206Z" kvs:fn="91343852333215703638620122740600834126611228269"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:45.204Z" kvs:fn="91343852333215703643571882897742355500545349018"/>
          <S d="998" kvs:ts="2023-12-09T07:58:46.205Z" kvs:fn="91343852333215703648523643054883876869407937734"/>
          <S d="997" kvs:ts="2023-12-09T07:58:47.206Z" kvs:fn="91343852333215703653475403212025398237883696543"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:48.204Z" kvs:fn="91343852333215703658427163369166919596413601907"/>
          <S d="998" kvs:ts="2023-12-09T07:58:49.204Z" kvs:fn="91343852333215703663378923526308441007759581867"/>
          <S d="998" kvs:ts="2023-12-09T07:58:50.204Z" kvs:fn="91343852333215703668330683683449962347049124213"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:51.203Z" kvs:fn="91343852333215703673282443840591483699803353577"/>
          <S d="998" kvs:ts="2023-12-09T07:58:52.203Z" kvs:fn="91343852333215703678234203997733005072327464618"/>
          <S d="998" kvs:ts="2023-12-09T07:58:53.203Z" kvs:fn="91343852333215703683185964154874526465491531013"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:54.202Z" kvs:fn="91343852333215703688137724312016047804996799635"/>
          <S d="998" kvs:ts="2023-12-09T07:58:55.202Z" kvs:fn="91343852333215703693089484469157569198675301585"/>
          <S d="998" kvs:ts="2023-12-09T07:58:56.202Z" kvs:fn="91343852333215703698041244626299090566624458401"/>
          <S d="1001" kvs:ts="2023-12-09T07:58:57.201Z" kvs:fn="91343852333215703702993004783440611913201513403"/>
          <S d="1000" kvs:ts="2023-12-09T07:58:58.200Z" kvs:fn="91343852333215703707944764940582133301407551807"/>
          <S d="997" kvs:ts="2023-12-09T07:58:59.202Z" kvs:fn="91343852333215703712896525097723654664921362969"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:00.200Z" kvs:fn="91343852333215703717848285254865176006879908031"/>
          <S d="997" kvs:ts="2023-12-09T07:59:01.201Z" kvs:fn="91343852333215703722800045412006697426114573620"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:02.200Z" kvs:fn="91343852333215703727751805569148218755816554703"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:03.199Z" kvs:fn="91343852333215703732703565726289740119145777387"/>
          <S d="998" kvs:ts="2023-12-09T07:59:04.199Z" kvs:fn="91343852333215703737655325883431261489097116733"/>
          <S d="998" kvs:ts="2023-12-09T07:59:05.200Z" kvs:fn="91343852333215703742607086040572782864226764134"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:06.198Z" kvs:fn="91343852333215703747558846197714304218227347320"/>
          <S d="996" kvs:ts="2023-12-09T07:59:07.201Z" kvs:fn="91343852333215703752510606354855825600711209580"/>
          <S d="998" kvs:ts="2023-12-09T07:59:08.200Z" kvs:fn="91343852333215703757462366511997346960412678389"/>
          <S d="996" kvs:ts="2023-12-09T07:59:09.202Z" kvs:fn="91343852333215703762414126669138868328263710307"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:10.197Z" kvs:fn="91343852333215703767365886826280389704922436148"/>
          <S d="998" kvs:ts="2023-12-09T07:59:11.198Z" kvs:fn="91343852333215703772317646983421911059565925392"/>
          <S d="1005" kvs:ts="2023-12-09T07:59:12.196Z" kvs:fn="91343852333215703777269407140563432429892186724"/>
          <S d="997" kvs:ts="2023-12-09T07:59:13.198Z" kvs:fn="91343852333215703782221167297704953813368133151"/>
          <S d="995" kvs:ts="2023-12-09T07:59:14.200Z" kvs:fn="91343852333215703787172927454846475169089211094"/>
          <S d="1004" kvs:ts="2023-12-09T07:59:15.195Z" kvs:fn="91343852333215703792124687611987996530657499624"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:16.195Z" kvs:fn="91343852333215703797076447769129517922095635738"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:17.196Z" kvs:fn="91343852333215703802028207926271039278930289553"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:18.196Z" kvs:fn="91343852333215703806979968083412560639125380573"/>
          <S d="998" kvs:ts="2023-12-09T07:59:19.195Z" kvs:fn="91343852333215703811931728240554082029047690586"/>
          <S d="997" kvs:ts="2023-12-09T07:59:20.196Z" kvs:fn="91343852333215703816883488397695603387750476174"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:21.194Z" kvs:fn="91343852333215703821835248554837124737404800217"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:22.193Z" kvs:fn="91343852333215703826787008711978646109146001286"/>
          <S d="997" kvs:ts="2023-12-09T07:59:23.195Z" kvs:fn="91343852333215703831738768869120167474017114838"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:24.193Z" kvs:fn="91343852333215703836690529026261688834698383107"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:25.193Z" kvs:fn="91343852333215703841642289183403210216519737681"/>
          <S d="998" kvs:ts="2023-12-09T07:59:26.193Z" kvs:fn="91343852333215703846594049340544731565600600899"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:27.192Z" kvs:fn="91343852333215703851545809497686252940163647626"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:28.193Z" kvs:fn="91343852333215703856497569654827774310034023472"/>
          <S d="998" kvs:ts="2023-12-09T07:59:29.192Z" kvs:fn="91343852333215703861449329811969295683212863901"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:30.191Z" kvs:fn="91343852333215703866401089969110817048856151526"/>
          <S d="998" kvs:ts="2023-12-09T07:59:31.192Z" kvs:fn="91343852333215703871352850126252338450170327197"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:32.191Z" kvs:fn="91343852333215703876304610283393859788926569260"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:33.190Z" kvs:fn="91343852333215703881256370440535381156069529009"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:34.190Z" kvs:fn="91343852333215703886208130597676902529391842243"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:35.191Z" kvs:fn="91343852333215703891159890754818423902612351553"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:36.190Z" kvs:fn="91343852333215703896111650911959945261759082449"/>
          <S d="1003" kvs:ts="2023-12-09T07:59:37.189Z" kvs:fn="91343852333215703901063411069101466647617216149"/>
          <S d="998" kvs:ts="2023-12-09T07:59:38.190Z" kvs:fn="91343852333215703906015171226242988017449587601"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:39.188Z" kvs:fn="91343852333215703910966931383384509351344036949"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:40.188Z" kvs:fn="91343852333215703915918691540526030723932802908"/>
          <S d="998" kvs:ts="2023-12-09T07:59:41.189Z" kvs:fn="91343852333215703920870451697667552086417156042"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:42.187Z" kvs:fn="91343852333215703925822211854809073454287366705"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:43.187Z" kvs:fn="91343852333215703930773972011950594827839141141"/>
          <S d="996" kvs:ts="2023-12-09T07:59:44.190Z" kvs:fn="91343852333215703935725732169092116194411814209"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:45.186Z" kvs:fn="91343852333215703940677492326233637557677318663"/>
          <S d="998" kvs:ts="2023-12-09T07:59:46.187Z" kvs:fn="91343852333215703945629252483375158940826714326"/>
          <S d="998" kvs:ts="2023-12-09T07:59:47.187Z" kvs:fn="91343852333215703950581012640516680294209549768"/>
          <S d="1003" kvs:ts="2023-12-09T07:59:48.186Z" kvs:fn="91343852333215703955532772797658201665798537520"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:49.186Z" kvs:fn="91343852333215703960484532954799723041304309232"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:50.186Z" kvs:fn="91343852333215703965436293111941244400480811698"/>
          <S d="1002" kvs:ts="2023-12-09T07:59:51.185Z" kvs:fn="91343852333215703970388053269082765787997272409"/>
          <S d="997" kvs:ts="2023-12-09T07:59:52.186Z" kvs:fn="91343852333215703975339813426224287150493119161"/>
          <S d="997" kvs:ts="2023-12-09T07:59:53.186Z" kvs:fn="91343852333215703980291573583365808529427439091"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:54.184Z" kvs:fn="91343852333215703985243333740507329872396810594"/>
          <S d="998" kvs:ts="2023-12-09T07:59:55.184Z" kvs:fn="91343852333215703990195093897648851249556434180"/>
          <S d="1000" kvs:ts="2023-12-09T07:59:56.184Z" kvs:fn="91343852333215703995146854054790372604023498907"/>
          <S d="1001" kvs:ts="2023-12-09T07:59:57.183Z" kvs:fn="91343852333215704000098614211931893984032624967"/>
          <S d="998" kvs:ts="2023-12-09T07:59:58.184Z" kvs:fn="91343852333215704005050374369073415341558035381"/>
          <S d="997" kvs:ts="2023-12-09T07:59:59.184Z" kvs:fn="91343852333215704010002134526214936707567304371"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:00.182Z" kvs:fn="91343852333215704014953894683356458084266234021"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:01.182Z" kvs:fn="91343852333215704019905654840497979454853954346"/>
          <S d="997" kvs:ts="2023-12-09T08:00:02.183Z" kvs:fn="91343852333215704024857414997639500815572031422"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:03.182Z" kvs:fn="91343852333215704029809175154781022186570848159"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:04.181Z" kvs:fn="91343852333215704034760935311922543563899855522"/>
          <S d="998" kvs:ts="2023-12-09T08:00:05.182Z" kvs:fn="91343852333215704039712695469064064921908990389"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:06.180Z" kvs:fn="91343852333215704044664455626205586295211281904"/>
          <S d="998" kvs:ts="2023-12-09T08:00:07.180Z" kvs:fn="91343852333215704049616215783347107670076647589"/>
          <S d="997" kvs:ts="2023-12-09T08:00:08.182Z" kvs:fn="91343852333215704054567975940488629031317371241"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:09.179Z" kvs:fn="91343852333215704059519736097630150399754433787"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:10.180Z" kvs:fn="91343852333215704064471496254771671781924511250"/>
          <S d="997" kvs:ts="2023-12-09T08:00:11.181Z" kvs:fn="91343852333215704069423256411913193131811709641"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:12.178Z" kvs:fn="91343852333215704074375016569054714505589391101"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:13.178Z" kvs:fn="91343852333215704079326776726196235878240959274"/>
          <S d="998" kvs:ts="2023-12-09T08:00:14.179Z" kvs:fn="91343852333215704084278536883337757236853824140"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:15.178Z" kvs:fn="91343852333215704089230297040479278608994999830"/>
          <S d="998" kvs:ts="2023-12-09T08:00:16.178Z" kvs:fn="91343852333215704094182057197620799981135040031"/>
          <S d="997" kvs:ts="2023-12-09T08:00:17.179Z" kvs:fn="91343852333215704099133817354762321330080620038"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:18.177Z" kvs:fn="91343852333215704104085577511903842701180337552"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:19.177Z" kvs:fn="91343852333215704109037337669045364075483794638"/>
          <S d="998" kvs:ts="2023-12-09T08:00:20.177Z" kvs:fn="91343852333215704113989097826186885442173482996"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:21.176Z" kvs:fn="91343852333215704118940857983328406807819366983"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:22.175Z" kvs:fn="91343852333215704123892618140469928185224537147"/>
          <S d="997" kvs:ts="2023-12-09T08:00:23.177Z" kvs:fn="91343852333215704128844378297611449541890474942"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:24.175Z" kvs:fn="91343852333215704133796138454752970912981491389"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:25.175Z" kvs:fn="91343852333215704138747898611894492288343747538"/>
          <S d="998" kvs:ts="2023-12-09T08:00:26.176Z" kvs:fn="91343852333215704143699658769036013653896960550"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:27.174Z" kvs:fn="91343852333215704148651418926177535024101316267"/>
          <S d="998" kvs:ts="2023-12-09T08:00:28.175Z" kvs:fn="91343852333215704153603179083319056394402943758"/>
          <S d="998" kvs:ts="2023-12-09T08:00:29.175Z" kvs:fn="91343852333215704158554939240460577761622848457"/>
          <S d="1003" kvs:ts="2023-12-09T08:00:30.173Z" kvs:fn="91343852333215704163506699397602099127324849081"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:31.174Z" kvs:fn="91343852333215704168458459554743620513526051737"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:32.174Z" kvs:fn="91343852333215704173410219711885141863712428816"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:33.173Z" kvs:fn="91343852333215704178361979869026663236153824465"/>
          <S d="998" kvs:ts="2023-12-09T08:00:34.173Z" kvs:fn="91343852333215704183313740026168184616203150113"/>
          <S d="998" kvs:ts="2023-12-09T08:00:35.173Z" kvs:fn="91343852333215704188265500183309705966971460559"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:36.172Z" kvs:fn="91343852333215704193217260340451227332516294301"/>
          <S d="1001" kvs:ts="2023-12-09T08:00:37.171Z" kvs:fn="91343852333215704198169020497592748714842021214"/>
          <S d="997" kvs:ts="2023-12-09T08:00:38.173Z" kvs:fn="91343852333215704203120780654734270073652079153"/>
          <S d="1002" kvs:ts="2023-12-09T08:00:39.170Z" kvs:fn="91343852333215704208072540811875791437991841660"/>
          <S d="1000" kvs:ts="2023-12-09T08:00:40.170Z" kvs:fn="91343852333215704213024300969017312839347172304"/>
          <S d="995" kvs:ts="2023-12-09T08:00:41.174Z" kvs:fn="91343852333215704217976061126158834177854986059"/>
        </SegmentTimeline>
      </SegmentTemplate>
      <Representation id="audio" bandwidth="64000" audioSamplingRate="8000" codecs="alaw">
        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"/>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>

この XML を parse するために、今回は以下のコードを使用しました。

import xml.etree.ElementTree as ET

def parse_mpd(mpd_url, mpd_content):
    root = ET.fromstring(mpd_content)
    base_url = mpd_url.rsplit('/', 1)[0] + '/'  
    segment_urls = []
    frame_rate = 0

    for adaptation_set in root.iter('{urn:mpeg:dash:schema:mpd:2011}AdaptationSet'):
        re_presentation = adaptation_set.find('{urn:mpeg:dash:schema:mpd:2011}Representation')
        if re_presentation is not None:
            media_type = re_presentation.attrib['id']
            if media_type != "video":
                continue
            
            frame_rate = float(re_presentation.attrib['frameRate'])

        segment_template = adaptation_set.find('{urn:mpeg:dash:schema:mpd:2011}SegmentTemplate')
        if segment_template is not None:
            media_url_template = segment_template.attrib['media']
            init_url = segment_template.attrib['initialization']
            segment_urls.append(base_url + init_url)
            seg_index = int(segment_template.attrib['startNumber'])

            for segment in segment_template.find('{urn:mpeg:dash:schema:mpd:2011}SegmentTimeline').iter('{urn:mpeg:dash:schema:mpd:2011}S'):
                segment_url = base_url + media_url_template.replace('$Number$', str(seg_index))
                segment_urls.append(segment_url)

                seg_index = seg_index + 1

    return segment_urls, frame_rate

今回はソラカメの API で取得した MPD ファイルを元にしています。その他の MPD ファイルに対して動作するかどうかはわかりません。動作としては単純に XML を parse して、必要な情報を取得しています。戻り値としては、セグメント単位の動画の URL 一覧と、配信される動画のフレームレートを返しています。これは、後で実際に動画を取得するのに必要な情報と、OpenCV を使って後で動画を作成するのにフレームレートが必要なので、この情報を戻しています。

MPD ファイルから取得した URL を使って動画をダウンロードしてみる

MPD ファイルを parse して取得した、セグメント単位の動画 URL から、実際に動画ファイルをダウンロードしてみます。parse の作業と同じく特別なことは何もなく、単純に Python でファイルをダウンロードすれば問題なさそうです。

def download_segment(url, filename):
    response = requests.get(url)
    if response.status_code == 200:
        with open(filename, 'wb') as file:
            file.write(response.content)
    else:
        print()
        print(f"Failed to download {url}. Status code: {response.status_code}. Response: {response.text}")

指定したフォルダに *.mp4 ファイルが作成されていますが、アイコンが動画ファイルのプレビューになってないことがわかるかと思います。この状態のファイルのままだと残念ながら再生できませんでした。

試しに OpenCV に放り込んでみましたが、以下のようなエラーが出てやはり読み込めませんでした。

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11f352af0] trun track id unknown, no tfhd was found
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x11f352af0] error reading header
OpenCV: Couldn't read video stream from file "_mp4_files/segment_31.mp4"
Error: Could not open video file: _mp4_files/segment_31.mp4

少し調べてみるとどうやら、動画のヘッダーと動画の本体が別々で配信されていて、個別配信された動画とヘッダー部分のファイルを結合してあげれば、問題なく再生できそうな雰囲気です。

stackoverflow.com

取得したファイルを結合して再生できるようにしてみる

基本的なところで、MPEG-DASH の配信で initialization で指定されている URL で配信されているファイルに、動画のヘッダー情報があり、各セグメントで配信されてるファイルは、その動画ファイルの実態ということがわかったので、手元にダウンロードしたファイルを結合して再生できるようにしようと思います。

試しに手元のコンソールで cat コマンドを使って結合してみたところ、問題なく再生できたので方向性は間違ってなさそうでした。

ただし

  • init + seq_1 = a
  • init + seq_2 = b
  • init + seq_1 + seq_2 = c
  • a + b = d

みたいなパターンで cat でファイル結合してみましたが、いずれの場合も結合結果のファイルは再生できました。一方で init + seq_$ の場合のみが正常で、連続で結合したり、結合したものを結合した場合は最初のセグメントだけしか再生されず、ファイルサイズだけ結合で大きくなるという状態でした。

これを踏まえて、Python で実装するコードは以下のようにしました。

def cat_mp4_files(in_path, out_path):
    files = [f for f in os.listdir(in_path) if f.endswith('.mp4')]
    files.sort()

    file_zero = ""
    for i, file in enumerate(files):
        in_file = os.path.join(in_path, file)
        out_file = os.path.join(out_path, file)

        if i == 0:
            file_zero = in_file
            continue

        combine_files = [file_zero, in_file]
        with open(out_file, 'wb') as outfile:
            for file in combine_files:
                with open(file, 'rb') as infile:
                    outfile.write(infile.read())

リストの先頭にヘッダーファイルがある状態で実行すると、指定されたフォルダに結合済みの *.mp4 ファイルが出力されます。結合したファイルが出力されているフォルダをみると、前回と違い今回は動画のサムネイルがアイコンとして設定されているのがわかると思います。実際にローカルに入ってる再生ソフトで再生してみると、1ファイル1秒の動画が問題なく再生できることがわかると思います。

OpenCV で動画ファイルを読み込んでみる

1ファイル1秒の動画が問題なく再生できることは確認できました。次が実際に OpenCV でこの動画ファイルを読み込んでみます。これが実現できると、AI / ML の連携を行う際に必要な部分は検証できたと言えそうです。

import cv2

def load_opencv(path):
    files = [f for f in os.listdir(path) if f.endswith('.mp4')]
    files.sort()

    frames = []
    for file in files:
        file_path = os.path.join(path, file)
        cap = cv2.VideoCapture(file_path)

        if not cap.isOpened():
            print("Error: Could not open video file:", file_path)
            continue

        while True:
            ret, frame = cap.read()
            if not ret:
                break

            frames.append(frame)
            cv2.imshow('frame', frame)

            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

        cap.release()
    cv2.destroyAllWindows()

    return frames

opencv.org

フォルダにある *.mp4 ファイルを順番に VideoCapture メソッドに渡して読み出しています。読み出した動画ファイルからフレームを取得して、そのフレームを imshow で画面を出して表示しています。この方法だとすべての動画ファイルの内容は表示できますが、処理のスピードによっては動画が早送りされているように表示されるかと思います。

frame rate を指定して複数のセグメント動画をひとつにしてみる

OpenCV を利用して各セグメントの動画は読み出せたので、AI/MLで利用する分には問題なく利用できるかと思います。しかし、ここまで来たら細切れではなく、全部のセグメントの動画を繋げて見たくなる気持ちがでてきませんか?というわけで、セグメント単位の複数の動画ファイルを、単一の動画ファイルにしてみようと思います。

def create_result_mp4(frames, frame_rate, path):
    fourcc = cv2.VideoWriter_fourcc(*'mp4v') 

    if frames:
        height, width, _ = frames[0].shape
        out = cv2.VideoWriter(path, fourcc, frame_rate, (width, height))

        for frame in frames:
            out.write(frame)

        out.release()
    else:
        print("The list of frames is empty.")

    print(f"A video {path} has been created.")

frames には、各セグメント動画ファイルを読み出した時に取得した frame 情報がリストで入っているので、これを指定されたフレームレートで書き出して行きます。フレームレートは MPD ファイルに入っている値をそのまま利用しています。

実際に出来上がった動画ファイルを確認しましたが、普通に再生できましたし、取得する際に指定した時間範囲も間違いなかったです。

まとめ

  • MPEG-DASH なにそれ? という状態ではなくなったと思う。
  • MPEG-DASH を利用するなら、とりあえず FFmpeg 使えばいいのでは? という状態ではなくなったと思う。
  • 実際に Python (+OpenCV) で再生できる動画が取得できた。
  • OpneCV の VideoCapture で動画が読み出せてる。
  • これで、ローカルで自前のAIモデルで動作させることも難しくないはず。
  • 今回は試しただけなので、同期処理で処理している。
  • 実際に利用する場合には、MPEG-DASH の取得と、OpneCV での読み込みを非同期で処理すれば問題なさそう。
  • ローカルでないと試せないとか、試すのが難しいという方も一定そういそうなので、ぜひローカル派の方は試してみてください。
  • オンラインや Web ブラウザーで試す方が気楽と言う方は、API サンプルとして少しですが公開されていますので、以下を参考にしてみてください。

users.soracom.io

ソラカメ API で取得できるストリーミング用の URL を、そのまま Python で操作して再生するところまでやってみました。結果としては特に難しい処理もなく、MPEG-DASH の仕様や MPD ファイルの構成をわかっていれば問題なく利用できるような感じを受けました。

これでローカルでいろいろ試せるという感じになると思うので、ぜひソラカメ API を使って検証してみてください。

以上です。