From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp0 ([2001:41d0:8:6d80::]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by ms0.migadu.com with LMTPS id gAv5DDrIYmCN4AAAgWs5BA (envelope-from ) for ; Tue, 30 Mar 2021 08:42:02 +0200 Received: from aspmx1.migadu.com ([2001:41d0:8:6d80::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp0 with LMTPS id CMECBzrIYmC5cQAA1q6Kng (envelope-from ) for ; Tue, 30 Mar 2021 06:42:02 +0000 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 2E78F2060B for ; Tue, 30 Mar 2021 08:42:01 +0200 (CEST) Received: from localhost ([::1]:47146 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lR84d-0005jo-Gg for larch@yhetil.org; Tue, 30 Mar 2021 02:41:59 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:54922) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lR83i-00056U-NA for bug-guix@gnu.org; Tue, 30 Mar 2021 02:41:05 -0400 Received: from debbugs.gnu.org ([209.51.188.43]:38633) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1lR83i-0000gv-Fw for bug-guix@gnu.org; Tue, 30 Mar 2021 02:41:02 -0400 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1lR83i-0007g4-Ca for bug-guix@gnu.org; Tue, 30 Mar 2021 02:41:02 -0400 X-Loop: help-debbugs@gnu.org Subject: bug#47260: Package GNU MediaGoblin as a Guix service Resent-From: "Dr. Arne Babenhauserheide" Original-Sender: "Debbugs-submit" Resent-CC: bug-guix@gnu.org Resent-Date: Tue, 30 Mar 2021 06:41:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 47260 X-GNU-PR-Package: guix X-GNU-PR-Keywords: To: Ben Sturmfels Received: via spool by 47260-submit@debbugs.gnu.org id=B47260.161708646029497 (code B ref 47260); Tue, 30 Mar 2021 06:41:02 +0000 Received: (at 47260) by debbugs.gnu.org; 30 Mar 2021 06:41:00 +0000 Received: from localhost ([127.0.0.1]:50179 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1lR83g-0007fh-0t for submit@debbugs.gnu.org; Tue, 30 Mar 2021 02:41:00 -0400 Received: from mout.web.de ([212.227.15.4]:40851) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1lR83d-0007fT-VU for 47260@debbugs.gnu.org; Tue, 30 Mar 2021 02:40:59 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=web.de; s=dbaedf251592; t=1617086447; bh=tyX05n91/SibmlFjCKe1rvyk9X12P37eDw4Zq9AUK+g=; h=X-UI-Sender-Class:References:From:To:Cc:Subject:In-reply-to:Date; b=U8D1xzLX+pHOTekstq6mcfXYrCZ+HlZD57bNo/UEt2T1QP7ni+oQEFgfuN8CqhT6n L6fnBQbYk6CC9a96M+G9MJ0/O5/vgVMt+K8CMo7yUShe7gd8kVEW9PhU+zOCFL3avT wqORuEYtqrD3XT9rdzWeZWXYYK24PLkyWl7Hg84Y= X-UI-Sender-Class: c548c8c5-30a9-4db5-a2e7-cb6cb037b8f9 Received: from fluss ([84.149.81.26]) by smtp.web.de (mrweb006 [213.165.67.108]) with ESMTPSA (Nemesis) id 1MhWkh-1m4KMO2Qsd-00egMW; Tue, 30 Mar 2021 08:40:47 +0200 References: <87eegbz65q.fsf@sturm.com.au> <8ce8fb88252149eb789a07514c8419f7@dismail.de> <87mtuw9jdp.fsf@sturm.com.au> <87lfafn00y.fsf@web.de> <87r1jxqog0.fsf@sturm.com.au> User-agent: mu4e 1.4.15; emacs 27.2 From: "Dr. Arne Babenhauserheide" In-reply-to: <87r1jxqog0.fsf@sturm.com.au> Date: Tue, 30 Mar 2021 08:40:44 +0200 Message-ID: <87lfa55ekz.fsf@web.de> MIME-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha256; protocol="application/pgp-signature" X-Provags-ID: V03:K1:GTqhhHfd5lDAUKg6ZvxOpwp9WbJZvNSMjFaD7saA2wgh2R2kV8z WonJb5nQ8wu4MUQipx5gbii/MGniB7XutiyOjYQpRE/3ghJw+NbmZsV/4+phOH3LerYIfj3 pRcKoc3ttXvFvejkxeblEQA8Ky7W6ASkKbYcSdnJOlKpDALjhFliQguwsKIQcxH0Va46nJ0 rrfs1etYGXnbpPt8V4Xuw== X-UI-Out-Filterresults: notjunk:1;V03:K0:WPWfqw8iOZE=:IliUDw47DzqVWOgYWRGXsN oiWPDeV8lhWitzow8hlS1qdGu9wpoje8WNw5/p9V72fR1TxxaxuBJg78FV/pCdsoMoklhjvdz +N6JV2MGuErkluK2GuWtYsNIKO54yt+el2JEqu5+ELW1uv1UwOKuImW68G37IXTT9sFmHFVJc 3Bs3rfVpURaBMi2/hJ4YtLTCRyd+sbfWBkdX+rh0jArLppK2euv3cdQmg2rwBb6qC6VqeIWTA sQJ0wbCfwRHzI4dJDQbzHTjtSRmErdvnh6qU1CfvQj4C6z2JaMJ+QlXqWPVMw9jCC9KXnlWKo hqB0aAKmC1UyDTc8I9GUuDSfGVhbc5RZWBjV9ZbYFP/rYpGh74bH1UngUmpOB/ju7vUf6peP7 5B72DYCmMPEIyLwi1kveiZ8fV7AGL+eqRxtjJ/nThaFrMd2gH4ZDrgSzxQjm2ybonmOlPchEA EHR7MA1PFnXcgsEu31LCPIbN5813v3Hi1Swl1F3pZgRAVLVkyLQBj8fweZfiaVBOfbwMO3brY 5nJmW/rddLluuzAoU7J6QhOQ3lzckc7OqPIthJM6DbNIHVlCfA6sTTNIVeunB0L1hRT95TEsT kKqzGiPRurg4WdJekfAMHKYHW9EpnZmnpQKA472diLbjDWSDlPlzybaRA2K5LCL8dWzQJN9iF mpmm+ciZcJ3Hr/SB0Lfb/vPOmDB+gIV4Ypbmc+kpNcRbvihbpEVPqT5Jx9V0pvsBLcncStlNz Ldwe9xZUayXltAZrsrINDJOZPwSpgRNC/0LcFqMQgOiT8CiNz7n8pPXw0YsyBQH3qAO9jYUzP hfcwdsReEE8g98XxrGRGsjkYe0Rsf4BdA0RpM/c75ueq/+7jJs45et/16i/HpLULU3ugOGc5l p4JFRKeZg8uYvhmY1PMULjJtomM4iwJzwRGKO5wjqnzb2ZIXJfQNdqz8Of5A3ZwGnpW9m4KyX F5Rh+uuwrTbxthzzM0kjw7E+SQk+Uc+Bv89nDzJfveoAtE0HS9ap8vP75DEsVhEYr5njyftYn LfJrVCGzM1LmXTNxOw8zEtntU7mECy9o2pfE+Por5pvlhX4Ts0tTgt66Pulz5ODic+HgTtY0w zq2/tQ+u+AN/F8fPyapD56Pkt9HrO4+vVuDhpNJ6BLpV/Hhb3Ps2TEiW8s2DbdHoMcbEYH6nU 5oIOfja/icj0zb0wjS5ov/kACg3IhkXvQdANQKibYHfckjm8ZLPgEFow4xiWindop6rko= X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-guix@gnu.org List-Id: Bug reports for GNU Guix List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: 47260@debbugs.gnu.org Errors-To: bug-guix-bounces+larch=yhetil.org@gnu.org Sender: "bug-Guix" X-Migadu-Flow: FLOW_IN ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1617086521; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type:resent-cc:resent-from:resent-sender: resent-message-id:in-reply-to:in-reply-to:references:references: list-id:list-help:list-unsubscribe:list-subscribe:list-post: dkim-signature; bh=hR/5h1JsFUOOK4fwAt45XgiP3APn/e4KmYoCEyqcAZs=; b=Xyih9cwkltIPn5jKJDA0Vn3SPsaJ7kN4mUCIT5/jOhd5H2Se6rR9To048TS0a/qUYulEIj 9f68i7OmzM1WhRLp18qjn/e3boA1nI5eBdMBAzRWSWatF6G6FyLknt7/Z8OB0Y5RJShNCJ 49uWt96LhUDy+r1/VmeQtjkdR4g2pSNmZpQLoPeVhUTFOPNGmEQJ7P8RcAN5w1SePyH7Ro oWU/lL2JNoAn0CwXMY0+gp9Ld3SAIWw0x5t0T2y/uMNJaKOJ+2O2+qXEDCJOhZjrGjH3Fu ulhEHtHCGY1+8SgAvGxzMQoEMcp1t79QCdr79Q9IATQiOXKYU0M8B73gDtM98Q== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1617086521; a=rsa-sha256; cv=none; b=e8pUrugvfIsraIoeLPpzV+WxqJTMZsI7jPRA5KRgCCkXwLdO3Rg6bjRpy+FutRDaltzaha hxHh8K5uFfXVg+2ftz9HWKBcpqRS4D4W7cFwXLuomK3Lrn5pM+F5Kqs2QIr9CK//uZtL/V uGVEV5o5MWwt3l7Ey1qx2P/tMDDgn0ZEtRKwVQbsfLKKgfLbrbE77wVrvxcgfI6hEtdUC7 fVS3tPwGkRZqtOzB9hAN3yjtBOUK3KDUIr5rADTHuwvTOjr046oACpwxOKCG+/vkq3pg50 Er4e0PTovNwsdUsDu55JVH0tKgElD8VcCXPUwneoPL2x6dZoPdrPfZIeaohMUw== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=web.de header.s=dbaedf251592 header.b=U8D1xzLX; dmarc=fail reason="SPF not aligned (relaxed)" header.from=web.de (policy=none); spf=pass (aspmx1.migadu.com: domain of bug-guix-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=bug-guix-bounces@gnu.org X-Migadu-Spam-Score: -2.42 Authentication-Results: aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=web.de header.s=dbaedf251592 header.b=U8D1xzLX; dmarc=fail reason="SPF not aligned (relaxed)" header.from=web.de (policy=none); spf=pass (aspmx1.migadu.com: domain of bug-guix-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=bug-guix-bounces@gnu.org X-Migadu-Queue-Id: 2E78F2060B X-Spam-Score: -2.42 X-Migadu-Scanner: scn0.migadu.com X-TUID: tWgRHsB86iab --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Ben Sturmfels writes: > On Mon, 22 Mar 2021, Dr. Arne Babenhauserheide wrote: > >> If you need support for m3u-playlists, you can use the player I wrote >> here: https://www.draketo.de/software/m3u-player >> =E2=86=92 https://www.draketo.de/software/m3u-player.js (save as utf-8) >> (that m3u-playlists aren=E2=80=99t supported out of the box in most play= ers is a >> strange oversight, the code adds it for video- and audio-tags, License: >> GPLv2 or later =E2=80=94 just ask me if you need something else) >> >> There=E2=80=99s also an enhanced version for Freenet, but that has lots = of >> performance-changes to work over high-latency networks and with paranoid >> CSP-settings: >> https://github.com/freenet/fred/pull/721/files#diff-33cbf95723ae7b33eb20= 5cf9adc3411b2098e27ba757e553406f689a4fafb802 > > Thanks Arne! I've forwarded this on to mediagoblin-devel@gnu.org so we > don't lose track of it. Thank you! I added one change last week to support mobile browsers which answer "maybe" to the query `mediaTag.canPlayType('audio/x-mpegurl')` (yes, seriously, and it is in the spec :-) ). Also I backported the not freenet specific changes: =2D prefetch the next three tracks as blob and keep at most 10 tracks cached to allow for fast track skipping (and now actually release the memory) =2D adjustments to allow for inlining and survive the non-utf8-encoding. =2D continue automatically when fetch succeeded if playback was stopped because it reached the end (but not if paused). =2D minimal mouseover for the back and forward arrows. When a https-m3u-list refers to a http-file, it falls back from fetching blobs to rewriting the src-part of the tag (because blobs cannot be fetched from a less secure resource). This is how it looks: https://www.draketo.de/software/m3u-player.html The changes are included in https://www.draketo.de/software/m3u-player.js You can use it like this: To make this bug-report independent of my site, here=E2=80=99s the full cod= e: // [[file:m3u-player.org::*The script][The script:1]] // @license magnet:?xt=3Durn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&= dn=3Dgpl-2.0.txt GPL-v2-or-Later const nodes =3D document.querySelectorAll("audio,video"); const playlists =3D {}; const prefetchedTracks =3D new Map(); // use a map for insertion order, so = we can just blow away old entries. // maximum prefetched blobs that are kept. const MAX_PREFETCH_KEEP =3D 10; // maximum allowed number of entries in a playlist to prevent OOM attacks a= gainst the browser with self-referencing playlists const MAX_PLAYLIST_LENGTH =3D 1000; const PLAYLIST_MIME_TYPES =3D ["audio/x-mpegurl", "audio/mpegurl", "applica= tion/vnd.apple.mpegurl","application/mpegurl","application/x-mpegurl"]; function stripUrlParameters(link) { const url =3D new URL(link, window.location); url.search =3D ""; url.hash =3D ""; return url.href; } function isPlaylist(link) { const linkHref =3D stripUrlParameters(link); return linkHref.endsWith(".m3u") || linkHref.endsWith(".m3u8"); } function isBlob(link) { return new URL(link, window.location).protocol =3D=3D 'blob'; } function parsePlaylist(textContent) { return textContent.match(/^(?!#)(?!\s).*$/mg) .filter(s =3D> s); // filter removes empty strings } /** * Download the given playlist, parse it, and store the tracks in the * global playlists object using the url as key. * * Runs callback once the playlist downloaded successfully. */ function fetchPlaylist(url, onload, onerror) { const playlistFetcher =3D new XMLHttpRequest(); playlistFetcher.open("GET", url, true); playlistFetcher.responseType =3D "blob"; // to get a mime type playlistFetcher.onload =3D () =3D> { if (PLAYLIST_MIME_TYPES.includes(playlistFetcher.response.type)) { // s= ecurity check to ensure that filters have run const reader =3D new FileReader(); const load =3D onload; // propagate to inner scope reader.addEventListener("loadend", e =3D> { playlists[url] =3D parsePlaylist(reader.result); onload(); }); reader.readAsText(playlistFetcher.response); } else { console.error("playlist must have one of the playlist MIME type '" + = PLAYLIST_MIME_TYPES + "' but it had MIME type '" + playlistFetcher.response= .type + "'."); onerror(); } }; playlistFetcher.onerror =3D onerror; playlistFetcher.abort =3D onerror; playlistFetcher.send(); } function prefetchTrack(url, onload) { if (prefetchedTracks.has(url)) { return; } // first cleanup: kill the oldest entries until we're back at the allowed= size while (prefetchedTracks.size > MAX_PREFETCH_KEEP) { const key =3D prefetchedTracks.keys().next().value; const track =3D prefetchedTracks.get(key); prefetchedTracks.delete(key); } // first set the prefetched to the url so we will never request twice prefetchedTracks.set(url, url); // now start replacing it with a blob const xhr =3D new XMLHttpRequest(); xhr.open("GET", url, true); xhr.responseType =3D "blob"; xhr.onload =3D () =3D> { prefetchedTracks.set(url, xhr.response); if (onload) { onload(); } }; xhr.send(); } function updateSrc(mediaTag, callback) { const playlistUrl =3D mediaTag.getAttribute("playlist"); const trackIndex =3D mediaTag.getAttribute("track-index"); // deepcopy playlists to avoid shared mutation let playlist =3D [...playlists[playlistUrl]]; let trackUrl =3D playlist[trackIndex]; // download and splice in playlists as needed if (isPlaylist(trackUrl)) { if (playlist.length >=3D MAX_PLAYLIST_LENGTH) { // skip playlist if we already have too many tracks changeTrack(mediaTag, +1); } else { // do not use the cached playlist here, though it is tempting: it mig= ht genuinely change to allow for updates fetchPlaylist( trackUrl, () =3D> { playlist.splice(trackIndex, 1, ...playlists[trackUrl]); playlists[playlistUrl] =3D playlist; updateSrc(mediaTag, callback); }, () =3D> callback()); } } else { let url =3D prefetchedTracks.has(trackUrl) ? prefetchedTracks.get(trackUrl) instanceof Blob ? URL.createObjectURL(prefetchedTracks.get(trackUrl)) : trackUrl : trackUrl; const oldUrl =3D mediaTag.getAttribute("src"); mediaTag.setAttribute("src", url); // replace the url when done, because a blob from an xhr request // is more reliable in the media tag; // the normal URL caused jumping prematurely to the next track. if (url =3D=3D trackUrl) { prefetchTrack(trackUrl, () =3D> { if (mediaTag.paused) { if (url =3D=3D mediaTag.getAttribute("src")) { if (mediaTag.currentTime =3D=3D=3D 0) { mediaTag.setAttribute("src", URL.createObjectURL( prefetchedTracks.get(url))); } } } }); } // allow releasing memory if (isBlob(oldUrl)) { URL.revokeObjectURL(oldUrl); } // update title mediaTag.parentElement.querySelector(".m3u-player--title").title =3D tr= ackUrl; mediaTag.parentElement.querySelector(".m3u-player--title").textContent = =3D trackUrl; // start prefetching the next three tracks. for (const i of [1, 2, 3]) { if (playlist.length > Number(trackIndex) + i) { prefetchTrack(playlist[Number(trackIndex) + i]); } } callback(); } } function changeTrack(mediaTag, diff) { const currentTrackIndex =3D Number(mediaTag.getAttribute("track-index")); const nextTrackIndex =3D currentTrackIndex + diff; const tracks =3D playlists[mediaTag.getAttribute("playlist")]; if (nextTrackIndex >=3D 0) { // do not collapse the if clauses with doubl= e-and, that does not survive inlining if (tracks.length > nextTrackIndex) { mediaTag.setAttribute("track-index", nextTrackIndex); updateSrc(mediaTag, () =3D> mediaTag.play()); } } } /** * Turn a media tag into playlist player. */ function initPlayer(mediaTag) { mediaTag.setAttribute("playlist", mediaTag.getAttribute("src")); mediaTag.setAttribute("track-index", 0); const url =3D mediaTag.getAttribute("playlist"); const wrapper =3D mediaTag.parentElement.insertBefore(document.createElem= ent("div"), mediaTag); const controls =3D document.createElement("div"); const left =3D document.createElement("span"); const title =3D document.createElement("span"); const right =3D document.createElement("span"); controls.appendChild(left); controls.appendChild(title); controls.appendChild(right); left.classList.add("m3u-player--left"); right.classList.add("m3u-player--right"); title.classList.add("m3u-player--title"); title.style.overflow =3D "hidden"; title.style.textOverflow =3D "ellipsis"; title.style.whiteSpace =3D "nowrap"; title.style.opacity =3D "0.3"; title.style.direction =3D "rtl"; // for truncation on the left title.style.paddingLeft =3D "0.5em"; title.style.paddingRight =3D "0.5em"; controls.style.display =3D "flex"; controls.style.justifyContent =3D "space-between"; const styleTag =3D document.createElement("style"); styleTag.innerHTML =3D ".m3u-player--left:hover, .m3u-player--right:hover= {color: wheat; background-color: DarkSlateGray}"; wrapper.appendChild(styleTag); wrapper.appendChild(controls); controls.style.width =3D mediaTag.getBoundingClientRect().width.toString(= ) + "px"; // appending the media tag to the wrapper removes it from the outer scope= but keeps the event listeners wrapper.appendChild(mediaTag); left.innerHTML =3D "<"; // not textContent, because we MUST escape // the tag here and textContent shows the // escaped version left.onclick =3D () =3D> changeTrack(mediaTag, -1); right.innerHTML =3D ">"; right.onclick =3D () =3D> changeTrack(mediaTag, +1); fetchPlaylist( url, () =3D> { updateSrc(mediaTag, () =3D> null); mediaTag.addEventListener("ended", event =3D> { if (mediaTag.currentTime >=3D mediaTag.duration) { changeTrack(mediaTag, +1); } }); }, () =3D> null); // keep the controls aligned to the media tag mediaTag.resizeObserver =3D new ResizeObserver(entries =3D> { controls.style.width =3D entries[0].contentRect.width.toString() + "px"; }); mediaTag.resizeObserver.observe(mediaTag); } function processTag(mediaTag) { const canPlayClaim =3D mediaTag.canPlayType('audio/x-mpegurl'); let supportsPlaylists =3D !!canPlayClaim; if (canPlayClaim =3D=3D 'maybe') { // yes, seriously: specced as you only= know when you try supportsPlaylists =3D false; } if (!supportsPlaylists) { if (isPlaylist(mediaTag.getAttribute("src"))) { initPlayer(mediaTag); } } } document.addEventListener('DOMContentLoaded', () =3D> { const nodes =3D document.querySelectorAll("audio,video"); nodes.forEach(processTag); }); // @license-end // The script:1 ends here Best wishes, Arne =2D-=20 Unpolitisch sein hei=C3=9Ft politisch sein ohne es zu merken --=-=-= Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- iQJEBAEBCAAuFiEE801qEjXQSQPNItXAE++NRSQDw+sFAmBix+4QHGFybmVfYmFi QHdlYi5kZQAKCRAT741FJAPD62HfEACj05Xrkc+b59LGbHm50jqhuYdHRJy6yXD2 H1KMGt+9DjySbIzMDvELnAMXmSlAnfrMgunzXn8Ky7TGM1Vbjn43s9BwPYfMwcMm KnQpRxw2yMBLmg2kCk6yuYDopj6dABD4qmOQs3XOjoZ/H1G9TqdMB3g5LkHj/jlE ypGXkMHVTgsG30NhprO4p5N45AvGwtRf2WkN2HoJiv29chjQzA0CMWDYezYZrTyw JDAEwWuwEDa01zskMJcp6avZZloPV7d5CUif2QCYUllKcQObfSsdnbQhVRe1R1CU 8+evfCer5uKvdrlfI0gTqTkg7ic/jZNEZAM55jawi4Pfui4dkGHfeJRirP8vG1pB dsVF3vtUjq3omQrk1w8fKlhrhhuxapJ5/E24jmg8FrtSug5NrIiKVfTJaY247KoC CW9xiOaepc4PWlivaoBJrVZFLUsGeTXf+sEWb9pF5j2JLTRk/pSIJJzYYWPtGOUT MvKiF1JAXr9qy4EU28zWkPQhPppJ1YO3i4TcaBoZZbGtwTslbDjIGE7yss/m+mEv LyJQ4YGMposXSNUgMrgIFoRV6DGVvl9WOFhz5ebmXo0jLfIMDQ6OWZa5TEnZNiCS q3iB25HpwrcEb5u9EG1kw2NcO5ICHVKgcor7C0jB9FukhOu6EEjQG7hw/s8r4sdy 2wST8dc804jEBAEBCAAuFiEE3Si95tmHXKvOSosd3M8NswvBBUgFAmBix+4QHGFy bmVfYmFiQHdlYi5kZQAKCRDczw2zC8EFSO9CA/4i6k14FE2X2EUzw+nH2k4vWqjP BzhLUrbk8wSsWdBLLUQBy7OKTwrwSnFgpSNu+cg7QxU9FKuK3YO8ofSl0/hwhEz8 4mOHWtiJy9p83fRsOVdqSvq0du64qXx9a0aY9RdSchbcARGbCtGRbNIH514B/mNn fs98CJmES7Yo37WvvA== =t966 -----END PGP SIGNATURE----- --=-=-=--