podcast
|
@ -0,0 +1,241 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr-FR"><head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta charset="UTF-8">
|
||||
<title>Bloque la pub</title>
|
||||
<link rel="stylesheet" id="style-css" href="Bloque%20la%20pub_fichiers/style.css" type="text/css" media="all">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="header">
|
||||
<img id="title" src="Bloque%20la%20pub_fichiers/titre.svg" alt="BLOQUE LA PUB SUR INTERNET"><!-- desktop logo-->
|
||||
<img id="title2" src="Bloque%20la%20pub_fichiers/titre2.svg" alt="BLOQUE LA PUB SUR INTERNET"><!-- mobile logo -->
|
||||
<img id="passele" src="Bloque%20la%20pub_fichiers/passele.svg" alt="ET PASSE LE BLOQUEUR À TES VOISIN·ES">
|
||||
</div>
|
||||
<div id="container">
|
||||
<div class="content">
|
||||
<h1>Pourquoi bloquer ?</h1>
|
||||
<div>La publicité pollue nos écrans, nos pensées et nous impose une vision stéréotypée de la société.<br>
|
||||
Elle ralentit l'affichage des autres contenus et consomme de l'énergie inutilement.<br><br>
|
||||
Elle rend dépendants la presse, les créatrices et créateurs, vidant nos médias de leur diversité.<br>
|
||||
Elle surveille toutes nos actions en ligne pour déceler nos faiblesses et nous pousser à surconsommer.<br><br>
|
||||
|
||||
Pour ton esprit, ta planète, ta culture et ta vie privée, bloque la pub sur Internet.<br>
|
||||
Et pour ceux de tes copin·es, passe le bloqueur à tes voisin·es.
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<h1>Comment bloquer ?</h1>
|
||||
Pour bloquer la publicité en ligne, nous vous conseillons d'installer le bloqueur uBlock Origin.
|
||||
<div class="centerimg">
|
||||
<img src="Bloque%20la%20pub_fichiers/UBlock_Origin.png">
|
||||
</div>
|
||||
Si vous utilisez déjà "AdBlock+" sur votre navigateur, nous vous
|
||||
invitons à le désinstaller au profit de uBlock Origin, car AdBlock a
|
||||
passé des accords avec certaines entreprises publicitaires pour <a href="https://adblockplus.org/en/acceptable-ads">ne pas tout bloquer</a>.
|
||||
<h2>Sur ordinateur</h2>
|
||||
Pour une navigation plus respectueuse de vos données personnelles, nous vous recommandons d'installer <a href="https://www.mozilla.org/fr/firefox/new/">firefox</a>.
|
||||
<div class="boite">
|
||||
<a href="https://addons.mozilla.org/fr/firefox/addon/ublock-origin/">
|
||||
<h5>uBlock Origin pour firefox</h5>
|
||||
<img src="Bloque%20la%20pub_fichiers/firefoxublock.png">
|
||||
</a>
|
||||
</div>
|
||||
<p>Si vous ne souhaitez pas changer de navigateur, uBlock Origin existe aussi pour <a href="https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm">chrome</a>,
|
||||
<a href="https://addons.opera.com/extensions/details/ublock/">opéra</a>,
|
||||
<a href="https://www.microsoft.com/store/p/app/9nblggh444l4">edge</a>,
|
||||
<a href="https://safari-extensions.apple.com/details/?id=com.el1t.uBlock-3NU33NW2M3">et safari</a>.</p>
|
||||
<h4>Configurer ublock Origin</h4>
|
||||
Une fois uBlock Origin installé, la plupart des publicités en ligne
|
||||
sont bloquées, mais le module peut être configuré pour limiter encore
|
||||
plus la surveillance.<br><br>
|
||||
|
||||
Pour cela :
|
||||
<div class="centerimg">
|
||||
<img style="width:200px" src="Bloque%20la%20pub_fichiers/configuBlock.png">
|
||||
</div>
|
||||
<strong>1.</strong> cliquez sur le bouclier uBlockOrigin<br>
|
||||
<strong>2.</strong> cliquez sur paramètres<br>
|
||||
<div class="centerimg">
|
||||
<img style="width:100%;" src="Bloque%20la%20pub_fichiers/configuBlock2.png">
|
||||
</div>
|
||||
<strong>3.</strong> allez dans l’onglet « listes de filtres »<br>
|
||||
<strong>4.</strong> cochez les cases des listes « confidentialité » et « nuisances ».<br>
|
||||
<strong>5.</strong> enfin, cliquez sur "appliquer" <br><br>
|
||||
|
||||
Si un site utilisé fonctionne mal, il est facile de désactiver
|
||||
temporairement le module en cliquant sur le bouton « power » bleu.
|
||||
|
||||
|
||||
<h2>Sur téléphone</h2>
|
||||
<h3>1. dans votre navigateur</h3>
|
||||
Installer firefox depuis votre magasin d'applications puis installer uBlock Origin
|
||||
<div class="centerimg">
|
||||
<a href="https://addons.mozilla.org/fr/firefox/addon/ublock-origin/">
|
||||
<h5>uBlock Origin pour firefox</h5>
|
||||
<img src="Bloque%20la%20pub_fichiers/firefoxublock.png">
|
||||
</a>
|
||||
</div>
|
||||
<h3> 2. dans les applications (android uniquement)</h3>
|
||||
Par défaut sur android, c'est le magasin d'application de Google qui
|
||||
est installé. Il existe d'autres magasins d'applications. F-Droid a la
|
||||
particularité de ne proposer que des applications libres et sans
|
||||
traqueur. Installez F-Droid en cliquant <a href="https://f-droid.org/fr/">ici</a> après avoir autorisé les application de sources inconnues. Depuis ce magasin, recherchez les bloqueurs blokada ou adaway.
|
||||
Pour installer ces applications, il faut activer les applications de sources inconnues
|
||||
<div class="boite">
|
||||
<a href="https://f-droid.org/en/packages/org.blokada.alarm/">
|
||||
<h5>blokada</h5>
|
||||
<img src="Bloque%20la%20pub_fichiers/blokada.png">
|
||||
</a>
|
||||
<p>ou</p>
|
||||
<a href="https://f-droid.org/en/packages/org.adaway/">
|
||||
<h5>adaway</h5>
|
||||
<img src="Bloque%20la%20pub_fichiers/adaway.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<h1>Participer</h1>
|
||||
Il est temps d’aider ses voisin·es à se débarrasser de la pub en ligne !
|
||||
<br><br>
|
||||
La prochaine fois que vous voyez vos parents, cousines ou voisins,
|
||||
demandez-leur s'ils ont un bloqueur de pub. Sinon, prenez 10 minutes
|
||||
pour les aider.<br><br>
|
||||
|
||||
Vous pouvez aussi agir pour sensibiliser tout le monde : voir notre <a href="http://bloquelapub.net/201901-Appelamobilisation-Bloquelapub.net.pdf">appel à mobilisation</a> et nos images à partager. <br><br>
|
||||
|
||||
<h2>Images à partager</h2>
|
||||
<div class="rs">
|
||||
<img src="Bloque%20la%20pub_fichiers/1.png">
|
||||
<img src="Bloque%20la%20pub_fichiers/2.png">
|
||||
</div>
|
||||
<div class="rs">
|
||||
<img src="Bloque%20la%20pub_fichiers/3.png">
|
||||
<img src="Bloque%20la%20pub_fichiers/4.png">
|
||||
</div>
|
||||
<div class="rs">
|
||||
<img src="Bloque%20la%20pub_fichiers/5.png">
|
||||
<img src="Bloque%20la%20pub_fichiers/6.png">
|
||||
</div>
|
||||
<h2>Citations à partager</h2>
|
||||
<div class="cit">
|
||||
<img src="Bloque%20la%20pub_fichiers/cit1.png">
|
||||
</div>
|
||||
<div class="cit">
|
||||
<img src="Bloque%20la%20pub_fichiers/cit2.png">
|
||||
</div>
|
||||
<h2>Affiches à imprimer</h2>
|
||||
Cliquez <a href="http://bloquelapub.net/Bloquelapub-affiches.pdf">ici</a> pour télécharger nos affiches à imprimer
|
||||
<div class="cit">
|
||||
<img src="Bloque%20la%20pub_fichiers/affiches.png">
|
||||
</div>
|
||||
|
||||
Une question ? Soutenir l'appel ? <a href="mailto:contact@bloquelapub.net"> contact@bloquelapub.net</a>
|
||||
<h4> <a href="#loin">Aller plus loin</a></h4>
|
||||
<div id="loin">
|
||||
<h2>Ressources bibliographiques spécifiques à la publicité et à la surveillance publicitaire en ligne</h2>
|
||||
|
||||
<h3>Quelques outils pour limiter la surveillance publicitaire en ligne :</h3>
|
||||
Plus d'informations sur <a href="https://github.com/gorhill/uBlock">uBlock Origin</a>
|
||||
|
||||
<h4>Modules basiques recommandés :</h4>
|
||||
<a href="https://addons.mozilla.org/fr/firefox/addon/ublock-origin/">uBlock Origin</a><br>
|
||||
<a href="https://addons.mozilla.org/fr/firefox/addon/decentraleyes/">Decentraleyes </a>
|
||||
<a href="https://disconnect.me/">Disconnect.me </a>
|
||||
|
||||
<h4>D'autres modules à découvrir pour lutter contre la publicité / la surveillance publicitaire</h4>
|
||||
<a href="https://addons.mozilla.org/fr/firefox/addon/umatrix/">uMatrix</a><br>
|
||||
<a href="https://addons.mozilla.org/fr/firefox/addon/adnauseam/">adNauseam</a><br>
|
||||
<a href="https://adguard.com/fr/welcome.html">adguard</a>
|
||||
|
||||
<h4>Découvrir les traqueurs cachés dans les applications Android </h4>
|
||||
L'association <a href="https://exodus-privacy.eu.org/fr/">Exodus Privacy</a> et son application mobile
|
||||
|
||||
<h4>Découvrir les listes de blocages (avancé)</h4>
|
||||
Le site <a href="https://filterlists.com/">filterlists</a> répertorie les différentes listes de blocages utilisées par les différents applications de blocage publicitaire<br>
|
||||
A titre d'exemple la liste Peter Lowe block list est disponible <a href="https://pgl.yoyo.org/adservers/">ici</a> (en anglais)
|
||||
|
||||
<h4>Des tutoriels de premiers pas supplémentaires :</h4>
|
||||
Sur le <a href="https://antipub.org/se-defendre/internet/">site</a> de Résistance à l'Agression Publicitaire<br>
|
||||
Sur le site de l'association CECIL voir la <a href="https://www.lececil.org/node/7692">fiche sur les protections contre le traçage</a> et <a href="https://www.lececil.org/node/7700">sur les ordiphones</a><br>
|
||||
Sur le site <a href="https://controle-tes-donnees.net/">Contrôle des données</a> de La Quadrature du Net (attention tout n'est pas à jour)<br>
|
||||
Au sein du livre numérique <a href="https://espritcreateur.net/libre-humain-a-lere-dinternet/">"Libre et humains à l'ère d'Internet"</a>
|
||||
<br><br>
|
||||
<h4>Pour sortir des logiques de la monétisation des données et du
|
||||
temps d'attention nous vous invitons à découvrir les services mis en
|
||||
valeurs par l'association <a href="https://framasoft.org/fr/">Framasoft</a> dans leur campagne <a href="https://degooglisons-internet.org/fr/">"Dégooglisons Internet"</a>
|
||||
<br><br>
|
||||
Pour découvrir les mécanismes de la publicité en ligne, nous
|
||||
recommandons les 3 articles de Numendil sur son blog PixelLibre.net</h4>
|
||||
<a href="http://pixellibre.net/2017/10/publicite-ligne-episode-1-cest-quoi-publicite/">épisode 1</a><br>
|
||||
<a href="http://pixellibre.net/2017/10/publicite-ligne-episode-2-quest-collecte/">épisode 2</a><br><a href="http://pixellibre.net/2017/10/publicite-ligne-episode-3-problemes-lies-a-publicite/">épisode 3</a>
|
||||
|
||||
<h4>Pour aller plus loin sur l'ensemble de ces enjeux :</h4>
|
||||
- Retrouvez l'ensemble de la bibliographie mis à disposition par Résistance à l'aggression publicitaire <a href="https://antipub.org/ressources/">ici</a> et <a href="https://antipub.org/ressources/a-voir/">ici</a>
|
||||
<br><br>
|
||||
- Docu Les Nouveaux Loups du Web – Cullen Hoback, 2016 <br><br>
|
||||
|
||||
- Syst PSES 2017 <a href="http://data.passageenseine.org/2017/syst_publicite-surveillance-en-ligne.webm"> Publicité et surveillance publicitaire en ligne - un combat à gagner</a> et le pdf de <a href="http://data.passageenseine.org/2017/syst_publicite-surveillance-en-ligne.pdf">présentation</a>
|
||||
|
||||
- <a href="https://www.youtube.com/watch?v=F4g3mJg7hLo">Okhin - Syst Au Capitole du Libre en 2017</a> (problème d'enregistrement)<br><br>
|
||||
|
||||
- Numendil PSES 2018 <a href="https://video.passageenseine.fr/videos/watch/d3bc2730-8c27-474f-9082-f5ea4a4d7178">La publicité en ligne et son fonctionnement </a><br><br>
|
||||
|
||||
- Syst PSES 2018 <a href="https://video.passageenseine.fr/videos/watch/7e81f44c-9332-4cbe-9753-d01a5a27a27f">Limiter la surveillance publicitaire en ligne, agir sur tous les terrains</a><br><br>
|
||||
|
||||
- Tristan Nitot – Prenons-nous le risque de vivre dans Matrix ? –
|
||||
Conférence sur le modèle publicitaire des réseaux sociaux, janvier 2018
|
||||
(15:31)<br><br>
|
||||
|
||||
- L’attention – <a href="https://www.youtube.com/watch?v=dcBepflB-A8">Le Fils de pub</a>, juillet 2018 (5:06)<br><br>
|
||||
|
||||
- L’économie de l’attention – <a href="https://www.youtube.com/watch?v=rMV1WaWGb3I">
|
||||
Stupid Economics</a>, juin 2018 (15:49)<br><br>
|
||||
|
||||
- Marketing politique : <a href="https://www.youtube.com/watch?v=xO_dH2oANWY">Démocra-ciblée – Datagueule</a>, janvier 2017 (12:07)<br><br>
|
||||
|
||||
- Zeynep Tufekci <a href="https://www.ted.com/talks/zeynep_tufekci_we_re_building_a_dystopia_just_to_make_people_click_on_ads">we're building a dystopia just to make people click on ads </a>
|
||||
</div><!-- aller plus #loin -->
|
||||
</div><!-- content -->
|
||||
</div><!-- container -->
|
||||
|
||||
<footer>
|
||||
<div class="boite">
|
||||
<a href="https://antipub.org/">
|
||||
<h4>RAP</h4>
|
||||
<img style="width:200px;" src="Bloque%20la%20pub_fichiers/RAP.png">
|
||||
</a>
|
||||
<a href="https://www.laquadrature.net/">
|
||||
<h4>La Quadrature du Net</h4>
|
||||
<img style="width:100px;" src="Bloque%20la%20pub_fichiers/lqdn.png">
|
||||
</a>
|
||||
</div>
|
||||
<div style="width: 45%;" class="boite">
|
||||
<a href="https://framasoft.org/fr/">
|
||||
<h5>Framasoft</h5>
|
||||
<img style="width:50px;" src="Bloque%20la%20pub_fichiers/framasoft.png">
|
||||
</a>
|
||||
<a href="https://www.lececil.org/">
|
||||
<h5>Le CECIL</h5>
|
||||
<img style="width:70px;" src="Bloque%20la%20pub_fichiers/cecil.jpg">
|
||||
</a>
|
||||
<a href="http://www.globenet.org/">
|
||||
<h5>Globenet</h5>
|
||||
<img style="width:50px;" src="Bloque%20la%20pub_fichiers/globenet.gif">
|
||||
</a>
|
||||
<a href="http://www.ritimo.org/">
|
||||
<h5>Ritimo</h5>
|
||||
<img style="width:50px;" src="Bloque%20la%20pub_fichiers/ritimo.png">
|
||||
</a>
|
||||
<a href="http://www.lecreis.org/">
|
||||
<h5>Creis-terminal</h5>
|
||||
<img style="width:50px;" src="Bloque%20la%20pub_fichiers/creis.jpg">
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
<script type="text/javascript" src="Bloque%20la%20pub_fichiers/scroll.js"></script>
|
||||
|
||||
|
||||
</body></html>
|
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 9.3 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 600 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 31 KiB |
|
@ -0,0 +1,99 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 324 26" style="enable-background:new 0 0 324 26;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M7.5,10.5v1.9h3.4V15H7.5v2h3.6v2.5H4.5V8h6.5v2.5H7.5z"/>
|
||||
<path d="M17.5,19.5h-3v-9h-2.5V8H20v2.5h-2.5V19.5z"/>
|
||||
<path d="M26.3,8h4.6c1.2,0,2.2,0.3,3,0.9c0.8,0.6,1.2,1.6,1.2,2.9c0,1.4-0.4,2.3-1.1,3c-0.7,0.6-1.7,0.9-3,0.9h-1.7v3.8h-3V8z
|
||||
M29.3,13.3h0.5c0.5,0,1,0,1.5-0.2c0.4-0.1,0.7-0.5,0.7-1.2c0-0.7-0.2-1.2-0.6-1.3c-0.4-0.1-0.9-0.2-1.5-0.2h-0.5V13.3z"/>
|
||||
<path d="M38.2,17.5l-0.8,2h-3.2L38.6,8h3.3l4.3,11.5H43l-0.7-2H38.2z M40.3,11.6L40.3,11.6L39,15.2h2.5L40.3,11.6z"/>
|
||||
<path d="M54.7,8.6L53.5,11c-0.3-0.2-0.6-0.4-0.9-0.5c-0.3-0.1-0.7-0.2-1.1-0.2c-0.1,0-0.1,0-0.2,0c-0.1,0-0.1,0-0.2,0
|
||||
c-0.2,0.1-0.4,0.2-0.6,0.3c-0.2,0.2-0.3,0.4-0.3,0.6c0,0.2,0.1,0.4,0.3,0.6c0.2,0.1,0.4,0.3,0.6,0.4c0.1,0,0.2,0.1,0.2,0.1
|
||||
c0.1,0,0.1,0,0.2,0.1l0.8,0.2c0.8,0.2,1.5,0.6,2,1.1c0.5,0.5,0.8,1.2,0.8,2.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.5-0.1,1-0.3,1.5
|
||||
c-0.2,0.5-0.5,0.9-0.9,1.3c-0.4,0.3-0.8,0.6-1.3,0.7c-0.5,0.2-1,0.3-1.5,0.3c-0.1,0-0.2,0-0.2,0c-0.1,0-0.1,0-0.2,0
|
||||
c-0.7,0-1.4-0.1-2.1-0.3s-1.3-0.5-1.9-1l1.3-2.4c0.4,0.3,0.8,0.6,1.2,0.9s0.9,0.4,1.4,0.4c0,0,0,0,0.1,0c0,0,0,0,0.1,0
|
||||
c0.3,0,0.6-0.1,0.9-0.3c0.3-0.2,0.4-0.5,0.4-0.8c0-0.3-0.1-0.5-0.3-0.7c-0.2-0.2-0.5-0.3-0.8-0.4c-0.1,0-0.2-0.1-0.4-0.1
|
||||
s-0.2-0.1-0.3-0.1c0,0,0,0-0.1,0c0,0,0,0-0.1,0c-0.9-0.3-1.6-0.6-2.2-1s-0.8-1.1-0.8-2.2c0-1.2,0.4-2.1,1.1-2.8
|
||||
c0.7-0.7,1.7-1.1,2.8-1.1c0.2,0,0.5,0,0.7,0c0.2,0,0.5,0.1,0.7,0.1c0.4,0.1,0.8,0.2,1.1,0.3C54,8.3,54.4,8.5,54.7,8.6L54.7,8.6z"/>
|
||||
<path d="M64.1,8.6L62.9,11c-0.3-0.2-0.6-0.4-0.9-0.5c-0.3-0.1-0.7-0.2-1.1-0.2c-0.1,0-0.1,0-0.2,0c-0.1,0-0.1,0-0.2,0
|
||||
c-0.2,0.1-0.4,0.2-0.6,0.3c-0.2,0.2-0.3,0.4-0.3,0.6c0,0.2,0.1,0.4,0.3,0.6c0.2,0.1,0.4,0.3,0.6,0.4c0.1,0,0.2,0.1,0.2,0.1
|
||||
c0.1,0,0.1,0,0.2,0.1l0.8,0.2c0.8,0.2,1.5,0.6,2,1.1c0.5,0.5,0.8,1.2,0.8,2.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.5-0.1,1-0.3,1.5
|
||||
c-0.2,0.5-0.5,0.9-0.9,1.3c-0.4,0.3-0.8,0.6-1.3,0.7c-0.5,0.2-1,0.3-1.5,0.3c-0.1,0-0.2,0-0.2,0c-0.1,0-0.1,0-0.2,0
|
||||
c-0.7,0-1.4-0.1-2.1-0.3s-1.3-0.5-1.9-1l1.3-2.4c0.4,0.3,0.8,0.6,1.2,0.9s0.9,0.4,1.4,0.4c0,0,0,0,0.1,0c0,0,0,0,0.1,0
|
||||
c0.3,0,0.6-0.1,0.9-0.3c0.3-0.2,0.4-0.5,0.4-0.8c0-0.3-0.1-0.5-0.3-0.7c-0.2-0.2-0.5-0.3-0.8-0.4c-0.1,0-0.2-0.1-0.4-0.1
|
||||
s-0.2-0.1-0.3-0.1c0,0,0,0-0.1,0c0,0,0,0-0.1,0c-0.9-0.3-1.6-0.6-2.2-1s-0.8-1.1-0.8-2.2c0-1.2,0.4-2.1,1.1-2.8
|
||||
c0.7-0.7,1.7-1.1,2.8-1.1c0.2,0,0.5,0,0.7,0c0.2,0,0.5,0.1,0.7,0.1c0.4,0.1,0.8,0.2,1.1,0.3C63.4,8.3,63.8,8.5,64.1,8.6L64.1,8.6z"
|
||||
/>
|
||||
<path d="M68.9,10.5v1.9h3.4V15h-3.4v2h3.6v2.5H66V8h6.5v2.5H68.9z"/>
|
||||
<path d="M82.6,17h3.6v2.5h-6.6V8h3V17z"/>
|
||||
<path d="M90.1,10.5v1.9h3.4V15h-3.4v2h3.6v2.5h-6.5V8h6.5v2.5H90.1z"/>
|
||||
<path d="M100.7,19.5V8h4.3c1,0,1.9,0.2,2.5,0.7c0.6,0.4,0.9,1.2,0.9,2.3c0,0.5-0.1,1-0.3,1.4c-0.2,0.4-0.5,0.7-1,1v0
|
||||
c0.8,0.1,1.4,0.4,1.8,1c0.4,0.5,0.6,1.2,0.6,2c0,1.2-0.4,2-1.3,2.5c-0.9,0.5-1.8,0.7-2.9,0.7H100.7z M103.7,12.5h0.3c0,0,0,0,0,0
|
||||
c0,0,0,0,0.1,0c0.4,0,0.7-0.1,1-0.2s0.5-0.4,0.5-0.9c0-0.5-0.2-0.8-0.5-0.9c-0.3-0.1-0.6-0.2-1-0.2c-0.1,0-0.1,0-0.1,0
|
||||
s-0.1,0-0.1,0h-0.2V12.5z M103.7,17.2h0.4c0,0,0,0,0,0s0,0,0.1,0c0.1,0,0.2,0,0.4,0s0.3,0,0.4,0c0.4,0,0.7-0.1,1-0.3
|
||||
c0.3-0.2,0.5-0.5,0.5-0.9c0-0.5-0.2-0.9-0.5-1s-0.7-0.2-1.1-0.2c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2,0-0.3,0c0,0-0.1,0-0.1,0
|
||||
c0,0-0.1,0-0.1,0h-0.3V17.2z"/>
|
||||
<path d="M114.1,17h3.6v2.5h-6.6V8h3V17z"/>
|
||||
<path d="M124.4,7.6c1.7,0,3.1,0.6,4.4,1.7c1.3,1.1,1.9,2.5,1.9,4.3c0,1.9-0.6,3.4-1.8,4.6c-1.2,1.2-2.7,1.8-4.6,1.8
|
||||
c-1.8,0-3.4-0.6-4.6-1.8s-1.8-2.7-1.8-4.6c0-1.7,0.6-3.2,1.9-4.3S122.7,7.6,124.4,7.6L124.4,7.6z M122.1,11.5c-0.6,0.6-1,1.3-1,2.1
|
||||
c0,1,0.3,1.8,1,2.5s1.4,1,2.3,1c0.9,0,1.7-0.3,2.3-1c0.6-0.7,1-1.5,1-2.5c0-0.8-0.3-1.5-1-2.1c-0.6-0.6-1.4-0.9-2.3-0.9
|
||||
C123.5,10.5,122.7,10.8,122.1,11.5z"/>
|
||||
<path d="M142,20.2l-0.9-1.1c-0.4,0.3-0.9,0.5-1.4,0.6c-0.5,0.1-1.1,0.2-1.7,0.2c-1.8,0-3.4-0.6-4.6-1.8s-1.8-2.7-1.8-4.6
|
||||
c0-1.7,0.6-3.2,1.9-4.3s2.8-1.7,4.4-1.7c1.8,0,3.3,0.6,4.5,1.7c1.2,1.1,1.8,2.6,1.8,4.5c0,0.8-0.1,1.5-0.4,2.1
|
||||
c-0.3,0.7-0.6,1.3-1.1,1.9l1.9,2L142,20.2z M140.7,15.5c0.1-0.2,0.2-0.4,0.3-0.6s0.1-0.4,0.2-0.7c0-0.1,0-0.1,0-0.2
|
||||
c0-0.1,0-0.1,0-0.2c0-0.9-0.3-1.6-0.9-2.3c-0.6-0.7-1.4-1-2.3-1c-0.9,0-1.7,0.3-2.3,0.9s-1,1.3-1,2.1c0,1,0.3,1.8,1,2.5
|
||||
s1.4,1,2.3,1c0.2,0,0.3,0,0.5,0s0.3-0.1,0.5-0.1L137,15l2.8-0.4L140.7,15.5z"/>
|
||||
<path d="M155.7,8v6.5c0,0.8-0.1,1.5-0.2,2.2c-0.1,0.7-0.5,1.3-1,1.9c-0.4,0.4-0.8,0.6-1.3,0.8c-0.5,0.2-1,0.3-1.5,0.4
|
||||
c-0.2,0-0.3,0-0.5,0s-0.3,0-0.5,0c-0.2,0-0.3,0-0.5,0s-0.3,0-0.5,0c-0.5-0.1-1-0.2-1.5-0.4c-0.5-0.2-0.9-0.5-1.3-0.8
|
||||
c-0.6-0.6-0.9-1.2-1-1.9c-0.1-0.7-0.2-1.4-0.2-2.2V8h3v6.1c0,0.1,0,0.1,0,0.2c0,0.1,0,0.1,0,0.2c0,0.7,0.1,1.3,0.4,1.9
|
||||
c0.2,0.6,0.8,0.9,1.7,0.9s1.4-0.3,1.7-0.9c0.2-0.6,0.3-1.2,0.4-1.9c0-0.1,0-0.1,0-0.2c0-0.1,0-0.1,0-0.2V8H155.7z"/>
|
||||
<path d="M160.5,10.5v1.9h3.4V15h-3.4v2h3.6v2.5h-6.5V8h6.5v2.5H160.5z"/>
|
||||
<path d="M175.9,8v6.5c0,0.8-0.1,1.5-0.2,2.2c-0.1,0.7-0.5,1.3-1,1.9c-0.4,0.4-0.8,0.6-1.3,0.8c-0.5,0.2-1,0.3-1.5,0.4
|
||||
c-0.2,0-0.3,0-0.5,0c-0.2,0-0.3,0-0.5,0c-0.2,0-0.3,0-0.5,0c-0.2,0-0.3,0-0.5,0c-0.5-0.1-1-0.2-1.5-0.4c-0.5-0.2-0.9-0.5-1.3-0.8
|
||||
c-0.6-0.6-0.9-1.2-1-1.9c-0.1-0.7-0.2-1.4-0.2-2.2V8h3v6.1c0,0.1,0,0.1,0,0.2c0,0.1,0,0.1,0,0.2c0,0.7,0.1,1.3,0.4,1.9
|
||||
c0.2,0.6,0.8,0.9,1.7,0.9s1.4-0.3,1.7-0.9s0.3-1.2,0.4-1.9c0-0.1,0-0.1,0-0.2c0-0.1,0-0.1,0-0.2V8H175.9z"/>
|
||||
<path d="M183.8,14.8l3.6,4.7h-3.7l-2.8-4.4h0v4.4h-3V8h4.5c1.1,0,2.1,0.3,2.9,0.9s1.1,1.5,1.1,2.7c0,0.8-0.2,1.5-0.6,2.1
|
||||
C185.2,14.3,184.6,14.7,183.8,14.8L183.8,14.8z M180.8,13.2h0.3c0.1,0,0.1,0,0.2,0s0.1,0,0.1,0c0.4,0,0.9-0.1,1.2-0.3
|
||||
s0.6-0.6,0.6-1.1c0-0.6-0.2-0.9-0.6-1.1c-0.4-0.2-0.8-0.3-1.2-0.3c0,0-0.1,0-0.1,0s-0.1,0-0.2,0h-0.3V13.2z"/>
|
||||
<path d="M196.3,17.5l-0.8,2h-3.2L196.8,8h3.3l4.3,11.5h-3.2l-0.7-2H196.3z M198.2,3.3l2.2,2.6l-1.2,0.9l-2.6-2.3L198.2,3.3z
|
||||
M198.4,11.6L198.4,11.6l-1.3,3.7h2.5L198.4,11.6z"/>
|
||||
<path d="M215.1,19.5h-3v-9h-2.5V8h7.9v2.5h-2.5V19.5z"/>
|
||||
<path d="M221.7,10.5v1.9h3.4V15h-3.4v2h3.6v2.5h-6.5V8h6.5v2.5H221.7z"/>
|
||||
<path d="M234.6,8.6l-1.2,2.3c-0.3-0.2-0.6-0.4-0.9-0.5c-0.3-0.1-0.7-0.2-1.1-0.2c-0.1,0-0.1,0-0.2,0c-0.1,0-0.1,0-0.2,0
|
||||
c-0.2,0.1-0.4,0.2-0.6,0.3c-0.2,0.2-0.3,0.4-0.3,0.6c0,0.2,0.1,0.4,0.3,0.6c0.2,0.1,0.4,0.3,0.6,0.4c0.1,0,0.2,0.1,0.2,0.1
|
||||
c0.1,0,0.1,0,0.2,0.1l0.8,0.2c0.8,0.2,1.5,0.6,2,1.1c0.5,0.5,0.8,1.2,0.8,2.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.5-0.1,1-0.3,1.5
|
||||
c-0.2,0.5-0.5,0.9-0.9,1.3c-0.4,0.3-0.8,0.6-1.3,0.7c-0.5,0.2-1,0.3-1.5,0.3c-0.1,0-0.2,0-0.2,0c-0.1,0-0.2,0-0.2,0
|
||||
c-0.7,0-1.4-0.1-2.1-0.3c-0.7-0.2-1.3-0.5-1.9-1l1.3-2.4c0.4,0.3,0.8,0.6,1.2,0.9s0.9,0.4,1.4,0.4c0,0,0,0,0.1,0s0,0,0.1,0
|
||||
c0.3,0,0.6-0.1,0.9-0.3c0.3-0.2,0.4-0.5,0.4-0.8c0-0.3-0.1-0.5-0.3-0.7c-0.2-0.2-0.5-0.3-0.8-0.4c-0.1,0-0.2-0.1-0.4-0.1
|
||||
s-0.2-0.1-0.3-0.1c0,0,0,0-0.1,0c0,0,0,0-0.1,0c-0.9-0.3-1.6-0.6-2.2-1s-0.8-1.1-0.8-2.2c0-1.2,0.4-2.1,1.1-2.8
|
||||
c0.7-0.7,1.7-1.1,2.8-1.1c0.2,0,0.5,0,0.7,0s0.5,0.1,0.7,0.1c0.4,0.1,0.8,0.2,1.1,0.3C233.9,8.3,234.3,8.5,234.6,8.6L234.6,8.6z"/>
|
||||
<path d="M246.5,15l2.8-7h3.2l-4.9,11.5h-2.3L240.5,8h3.2L246.5,15z"/>
|
||||
<path d="M259.1,7.6c1.7,0,3.1,0.6,4.4,1.7c1.3,1.1,1.9,2.5,1.9,4.3c0,1.9-0.6,3.4-1.8,4.6c-1.2,1.2-2.7,1.8-4.6,1.8
|
||||
c-1.8,0-3.4-0.6-4.6-1.8s-1.8-2.7-1.8-4.6c0-1.7,0.6-3.2,1.9-4.3C256,8.2,257.4,7.6,259.1,7.6L259.1,7.6z M256.8,11.5
|
||||
c-0.6,0.6-1,1.3-1,2.1c0,1,0.3,1.8,1,2.5c0.6,0.7,1.4,1,2.3,1s1.7-0.3,2.3-1s1-1.5,1-2.5c0-0.8-0.3-1.5-1-2.1s-1.4-0.9-2.3-0.9
|
||||
C258.2,10.5,257.4,10.8,256.8,11.5z"/>
|
||||
<path d="M269.8,19.5h-3V8h3V19.5z"/>
|
||||
<path d="M279.3,8.6l-1.2,2.3c-0.3-0.2-0.6-0.4-0.9-0.5c-0.3-0.1-0.7-0.2-1.1-0.2c-0.1,0-0.1,0-0.2,0c-0.1,0-0.1,0-0.2,0
|
||||
c-0.2,0.1-0.4,0.2-0.6,0.3c-0.2,0.2-0.3,0.4-0.3,0.6c0,0.2,0.1,0.4,0.3,0.6c0.2,0.1,0.4,0.3,0.6,0.4c0.1,0,0.2,0.1,0.2,0.1
|
||||
c0.1,0,0.1,0,0.2,0.1l0.8,0.2c0.8,0.2,1.5,0.6,2,1.1c0.5,0.5,0.8,1.2,0.8,2.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.5-0.1,1-0.3,1.5
|
||||
c-0.2,0.5-0.5,0.9-0.9,1.3c-0.4,0.3-0.8,0.6-1.3,0.7c-0.5,0.2-1,0.3-1.5,0.3c-0.1,0-0.2,0-0.2,0c-0.1,0-0.2,0-0.2,0
|
||||
c-0.7,0-1.4-0.1-2.1-0.3c-0.7-0.2-1.3-0.5-1.9-1l1.3-2.4c0.4,0.3,0.8,0.6,1.2,0.9s0.9,0.4,1.4,0.4c0,0,0,0,0.1,0s0,0,0.1,0
|
||||
c0.3,0,0.6-0.1,0.9-0.3c0.3-0.2,0.4-0.5,0.4-0.8c0-0.3-0.1-0.5-0.3-0.7c-0.2-0.2-0.5-0.3-0.8-0.4c-0.1,0-0.2-0.1-0.4-0.1
|
||||
s-0.2-0.1-0.3-0.1c0,0,0,0-0.1,0c0,0,0,0-0.1,0c-0.9-0.3-1.6-0.6-2.2-1s-0.8-1.1-0.8-2.2c0-1.2,0.4-2.1,1.1-2.8
|
||||
c0.7-0.7,1.7-1.1,2.8-1.1c0.2,0,0.5,0,0.7,0s0.5,0.1,0.7,0.1c0.4,0.1,0.8,0.2,1.1,0.3C278.6,8.3,279,8.5,279.3,8.6L279.3,8.6z"/>
|
||||
<path d="M284.2,19.5h-3V8h3V19.5z"/>
|
||||
<path d="M286.1,8h3l5.5,7h0V8h3v11.5h-3l-5.5-7h0v7h-3V8z"/>
|
||||
<path d="M301,13.1c0.4,0,0.7,0.1,1,0.4s0.4,0.5,0.4,0.9c0,0.4-0.1,0.7-0.4,0.9s-0.6,0.4-1,0.4c-0.4,0-0.7-0.1-1-0.4
|
||||
c-0.3-0.2-0.4-0.5-0.4-0.9c0-0.4,0.1-0.7,0.4-0.9C300.3,13.3,300.6,13.1,301,13.1z"/>
|
||||
<path d="M307.4,10.5v1.9h3.4V15h-3.4v2h3.6v2.5h-6.5V8h6.5v2.5H307.4z"/>
|
||||
<path d="M320.3,8.6l-1.2,2.3c-0.3-0.2-0.6-0.4-0.9-0.5c-0.3-0.1-0.7-0.2-1.1-0.2c-0.1,0-0.1,0-0.2,0c-0.1,0-0.1,0-0.2,0
|
||||
c-0.2,0.1-0.4,0.2-0.6,0.3c-0.2,0.2-0.3,0.4-0.3,0.6c0,0.2,0.1,0.4,0.3,0.6c0.2,0.1,0.4,0.3,0.6,0.4c0.1,0,0.2,0.1,0.2,0.1
|
||||
c0.1,0,0.1,0,0.2,0.1l0.8,0.2c0.8,0.2,1.5,0.6,2,1.1c0.5,0.5,0.8,1.2,0.8,2.1c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.5-0.1,1-0.3,1.5
|
||||
c-0.2,0.5-0.5,0.9-0.9,1.3c-0.4,0.3-0.8,0.6-1.3,0.7c-0.5,0.2-1,0.3-1.5,0.3c-0.1,0-0.2,0-0.2,0c-0.1,0-0.2,0-0.2,0
|
||||
c-0.7,0-1.4-0.1-2.1-0.3c-0.7-0.2-1.3-0.5-1.9-1l1.3-2.4c0.4,0.3,0.8,0.6,1.2,0.9s0.9,0.4,1.4,0.4c0,0,0,0,0.1,0s0,0,0.1,0
|
||||
c0.3,0,0.6-0.1,0.9-0.3c0.3-0.2,0.4-0.5,0.4-0.8c0-0.3-0.1-0.5-0.3-0.7c-0.2-0.2-0.5-0.3-0.8-0.4c-0.1,0-0.2-0.1-0.4-0.1
|
||||
s-0.2-0.1-0.3-0.1c0,0,0,0-0.1,0c0,0,0,0-0.1,0c-0.9-0.3-1.6-0.6-2.2-1s-0.8-1.1-0.8-2.2c0-1.2,0.4-2.1,1.1-2.8
|
||||
c0.7-0.7,1.7-1.1,2.8-1.1c0.2,0,0.5,0,0.7,0s0.5,0.1,0.7,0.1c0.4,0.1,0.8,0.2,1.1,0.3C319.7,8.3,320,8.5,320.3,8.6L320.3,8.6z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 159 KiB |
|
@ -0,0 +1,36 @@
|
|||
var header = document.getElementById("header");
|
||||
var title = document.getElementById("title");
|
||||
var content = document.getElementById("container");
|
||||
var browser = navigator.userAgent.toLowerCase();
|
||||
var minPad = 20;
|
||||
|
||||
if (browser.indexOf('firefox') > -1)
|
||||
header.style.position = "fixed";
|
||||
}
|
||||
|
||||
function resize()
|
||||
{
|
||||
pad = window.innerHeight*.28 - title.clientHeight/2;
|
||||
|
||||
content.style.marginTop = window.innerHeight*2/3 + "px";
|
||||
content.style.paddingTop = window.innerHeight*2/5 + "px";
|
||||
|
||||
scroll();
|
||||
}
|
||||
|
||||
function scroll()
|
||||
{
|
||||
var scrolled = document.documentElement.scrollTop;
|
||||
var top = pad - scrolled;
|
||||
var bottom = pad*2 - scrolled;
|
||||
|
||||
header.style.paddingTop = ( top < minPad ? minPad : top ) + "px";
|
||||
header.style.paddingBottom = ( bottom < minPad ? minPad : ( pad < bottom ? pad : bottom ) ) + "px";
|
||||
}
|
||||
|
||||
if (browser.indexOf('firefox' OR 'chrome') > -1) {
|
||||
onresize = resize;
|
||||
onscroll = scroll;
|
||||
resize();
|
||||
|
||||
if (browser.indexOf('firefox') > -1)
|
|
@ -0,0 +1,281 @@
|
|||
/* roboto-100 - latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url('webfonts/roboto-v18-latin-100.eot'); /* IE9 Compat Modes */
|
||||
src: local('Roboto Thin'), local('Roboto-Thin'),
|
||||
url('webfonts/roboto-v18-latin-100.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('webfonts/roboto-v18-latin-100.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-100.woff') format('woff'), /* Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-100.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('webfonts/roboto-v18-latin-100.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-100italic - latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
src: url('webfonts/roboto-v18-latin-100italic.eot'); /* IE9 Compat Modes */
|
||||
src: local('Roboto Thin Italic'), local('Roboto-ThinItalic'),
|
||||
url('webfonts/roboto-v18-latin-100italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('webfonts/roboto-v18-latin-100italic.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-100italic.woff') format('woff'), /* Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-100italic.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('webfonts/roboto-v18-latin-100italic.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
/* roboto-300 - latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('webfonts/roboto-v18-latin-300.eot'); /* IE9 Compat Modes */
|
||||
src: local('Roboto Light'), local('Roboto-Light'),
|
||||
url('webfonts/roboto-v18-latin-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('webfonts/roboto-v18-latin-300.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-300.woff') format('woff'), /* Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-300.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('webfonts/roboto-v18-latin-300.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-300italic - latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: url('webfonts/roboto-v18-latin-300italic.eot'); /* IE9 Compat Modes */
|
||||
src: local('Roboto Light Italic'), local('Roboto-LightItalic'),
|
||||
url('webfonts/roboto-v18-latin-300italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('webfonts/roboto-v18-latin-300italic.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-300italic.woff') format('woff'), /* Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-300italic.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('webfonts/roboto-v18-latin-300italic.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
|
||||
/* roboto-regular - latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url('webfonts/roboto-v18-latin-regular.eot'); /* IE9 Compat Modes */
|
||||
src: local('Roboto'), local('Roboto-Regular'),
|
||||
url('webfonts/roboto-v18-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('webfonts/roboto-v18-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('webfonts/roboto-v18-latin-regular.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-italic - latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
src: url('webfonts/roboto-v18-latin-italic.eot'); /* IE9 Compat Modes */
|
||||
src: local('Roboto Italic'), local('Roboto-Italic'),
|
||||
url('webfonts/roboto-v18-latin-italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('webfonts/roboto-v18-latin-italic.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-italic.woff') format('woff'), /* Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-italic.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('webfonts/roboto-v18-latin-italic.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-700 - latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('webfonts/roboto-v18-latin-700.eot'); /* IE9 Compat Modes */
|
||||
src: local('Roboto Bold'), local('Roboto-Bold'),
|
||||
url('webfonts/roboto-v18-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('webfonts/roboto-v18-latin-700.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-700.woff') format('woff'), /* Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('webfonts/roboto-v18-latin-700.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-700italic - latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url('webfonts/roboto-v18-latin-700italic.eot'); /* IE9 Compat Modes */
|
||||
src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'),
|
||||
url('webfonts/roboto-v18-latin-700italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('webfonts/roboto-v18-latin-700italic.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-700italic.woff') format('woff'), /* Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-700italic.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('webfonts/roboto-v18-latin-700italic.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-900 - latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url('webfonts/roboto-v18-latin-900.eot'); /* IE9 Compat Modes */
|
||||
src: local('Roboto Black'), local('Roboto-Black'),
|
||||
url('webfonts/roboto-v18-latin-900.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('webfonts/roboto-v18-latin-900.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-900.woff') format('woff'), /* Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-900.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('webfonts/roboto-v18-latin-900.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
/* roboto-900italic - latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
src: url('webfonts/roboto-v18-latin-900italic.eot'); /* IE9 Compat Modes */
|
||||
src: local('Roboto Black Italic'), local('Roboto-BlackItalic'),
|
||||
url('webfonts/roboto-v18-latin-900italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('webfonts/roboto-v18-latin-900italic.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-900italic.woff') format('woff'), /* Modern Browsers */
|
||||
url('webfonts/roboto-v18-latin-900italic.ttf') format('truetype'), /* Safari, Android, iOS */
|
||||
url('webfonts/roboto-v18-latin-900italic.svg#Roboto') format('svg'); /* Legacy iOS */
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
font-family: Roboto, sans-serif;
|
||||
overflow-x: hidden;
|
||||
margin:0;
|
||||
background-color:#E52421;
|
||||
font-size:1.5em;
|
||||
}
|
||||
h1,h2,h3,h4,h5 {
|
||||
font-weight: 900;
|
||||
}
|
||||
#header img {
|
||||
width:70vw;
|
||||
}
|
||||
#header {
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 108%;
|
||||
margin-left:-4%;
|
||||
padding:30vh 0;
|
||||
border-bottom: 25px solid black;
|
||||
top:0;
|
||||
background: white;
|
||||
}
|
||||
#container {
|
||||
border-bottom: 25px solid black;
|
||||
margin-left: -4%;
|
||||
width:108%;
|
||||
padding-top: 34vh;
|
||||
}
|
||||
#container .content {
|
||||
margin-top:30px;
|
||||
background-color:white;
|
||||
width:38%;
|
||||
margin:0px 28% 60px 28%;
|
||||
padding:3%;
|
||||
}
|
||||
.boite {
|
||||
display:flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.boite a {
|
||||
width:40%;
|
||||
text-decoration:none;
|
||||
color:black;
|
||||
text-align:center;
|
||||
}
|
||||
.boite img {
|
||||
max-width:150px;
|
||||
width:80%;
|
||||
margin:auto;
|
||||
}
|
||||
footer {
|
||||
background-color:white;
|
||||
padding: 1em;
|
||||
}
|
||||
a {
|
||||
color: #E52421;
|
||||
text-decoration:none;
|
||||
}
|
||||
a:hover {
|
||||
opacity:0.8;
|
||||
}
|
||||
#passele {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
img#passele {
|
||||
width: 38vw;
|
||||
}
|
||||
.centerimg {
|
||||
width:100%
|
||||
}
|
||||
.centerimg img, .centerimg a {
|
||||
width: 122px;
|
||||
margin: 1em auto;
|
||||
display: block;
|
||||
color:black;
|
||||
text-align:center;
|
||||
}
|
||||
footer div {
|
||||
width:50%;
|
||||
margin:auto;
|
||||
align-items:stretch;
|
||||
}
|
||||
footer {
|
||||
text-align:center;
|
||||
padding-top:2em;
|
||||
}
|
||||
#loin {
|
||||
height:0;
|
||||
display:none;
|
||||
transition: height 1s ease;
|
||||
margin-top: -200px;
|
||||
padding-top: 200px;
|
||||
}
|
||||
#loin:target {
|
||||
height:auto;
|
||||
display:block;
|
||||
transition: height 1s ease;
|
||||
}
|
||||
.rs img {
|
||||
width: 42%;
|
||||
margin: 2%;
|
||||
border: solid 4px black;
|
||||
}
|
||||
.cit img {
|
||||
width:96%;
|
||||
margin: 2%;
|
||||
border: solid 4px black;
|
||||
}
|
||||
#title {
|
||||
display:inline;
|
||||
}
|
||||
#title2 {
|
||||
display:none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Media Queries : version mobile */
|
||||
@media (max-width: 1100px) {
|
||||
#container .content {
|
||||
width: 82%;
|
||||
margin: 0px 6% 60px 6%;
|
||||
}
|
||||
}
|
||||
@media (orientation: portrait){
|
||||
body {
|
||||
font-size:3em;
|
||||
}
|
||||
#title2 {
|
||||
display:inline;
|
||||
}
|
||||
#title {
|
||||
display:none;
|
||||
}
|
||||
img#passele {
|
||||
width: 57vw;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 487 26" style="enable-background:new 0 0 487 26;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M1.6,24.7V1.1h8.8c2.1,0,3.8,0.5,5.1,1.4s1.9,2.5,1.9,4.8c0,1.1-0.2,2-0.6,2.9c-0.4,0.8-1.1,1.5-2,2l0,0
|
||||
c1.7,0.2,2.9,0.9,3.7,2c0.8,1.1,1.1,2.4,1.1,4.1c0,2.5-0.9,4.2-2.6,5.1s-3.7,1.4-5.9,1.4H1.6V24.7z M7.7,10.4h0.7c0,0,0,0,0.1,0
|
||||
h0.1c0.8,0,1.5-0.2,2.1-0.4c0.7-0.3,1-0.9,1-1.8c0-1-0.3-1.6-0.9-1.9c-0.6-0.3-1.3-0.4-2-0.4c-0.1,0-0.2,0-0.3,0s-0.2,0-0.3,0H7.7
|
||||
V10.4z M7.7,20h0.8c0,0,0,0,0.1,0s0.1,0,0.1,0c0.3,0,0.5,0,0.8,0s0.5,0,0.8,0c0.8-0.1,1.5-0.3,2.1-0.6c0.6-0.3,1-1,1-1.9
|
||||
c0-1.1-0.4-1.8-1.1-2.1C11.6,15.2,10.9,15,10,15c-0.2,0-0.4,0-0.6,0c-0.2,0-0.4,0-0.6,0c-0.1,0-0.2,0-0.3,0s-0.2,0-0.3,0H7.7V20z"
|
||||
/>
|
||||
<path d="M28.3,19.5h7.4v5.2H22.1V1.1h6.1L28.3,19.5L28.3,19.5z"/>
|
||||
<path d="M48.9,0.3c3.4,0,6.4,1.2,9.1,3.4c2.7,2.3,4,5.2,4,8.8c0,3.8-1.2,6.9-3.7,9.4c-2.5,2.4-5.6,3.6-9.4,3.6s-6.9-1.2-9.4-3.6
|
||||
s-3.7-5.5-3.7-9.4c0-3.6,1.3-6.5,4-8.8C42.4,1.5,45.5,0.4,48.9,0.3L48.9,0.3z M44.2,8.2c-1.3,1.3-2,2.7-2,4.3c0,2,0.7,3.7,2,5
|
||||
s2.9,2,4.7,2s3.4-0.7,4.7-2s2-3,2-5.1c0-1.6-0.7-3.1-2-4.4s-2.9-1.9-4.7-1.9C47,6.3,45.5,6.9,44.2,8.2z"/>
|
||||
<path d="M84.4,26.2L82.5,24c-0.9,0.5-1.9,0.9-3,1.2c-1.1,0.2-2.3,0.4-3.5,0.4c-3.8,0-6.9-1.2-9.4-3.6s-3.7-5.5-3.7-9.4
|
||||
c0-3.6,1.3-6.5,4-8.8s5.7-3.4,9.1-3.4c3.7,0,6.8,1.1,9.3,3.4s3.8,5.4,3.8,9.2c0,1.6-0.3,3-0.8,4.4S87,20.1,86,21.3l4,4L84.4,26.2z
|
||||
M81.6,16.5c0.3-0.4,0.5-0.8,0.7-1.2s0.3-0.9,0.4-1.3c0-0.1,0-0.3,0-0.5s0-0.3,0-0.5c0-1.8-0.6-3.3-1.9-4.7s-2.9-2-4.7-2
|
||||
c-1.8,0-3.4,0.6-4.7,1.9s-2,2.7-2,4.3c0,2,0.7,3.7,2,5s2.9,2,4.7,2c0.3,0,0.6,0,0.9-0.1s0.6-0.1,0.9-0.2l-3.8-3.8l5.8-0.7
|
||||
L81.6,16.5z"/>
|
||||
<path d="M111.8,1.1v13.3c0,1.5-0.1,3-0.4,4.5c-0.2,1.4-0.9,2.7-2.1,3.9c-0.8,0.8-1.6,1.3-2.7,1.7c-1,0.4-2.1,0.6-3.2,0.8
|
||||
c-0.3,0-0.7,0.1-1,0.1s-0.7,0-1,0s-0.6,0-1,0c-0.3,0-0.7-0.1-1-0.1c-1.1-0.1-2.1-0.4-3.2-0.8c-1-0.4-1.9-1-2.7-1.7
|
||||
c-1.2-1.1-1.9-2.4-2.1-3.9C91.2,17.5,91,16,91,14.5V1.1h6.1v12.4c0,0.1,0,0.3,0,0.4c0,0.1,0,0.2,0,0.4c0,1.4,0.3,2.7,0.7,3.9
|
||||
c0.5,1.2,1.6,1.8,3.4,1.8s3-0.6,3.4-1.8s0.7-2.5,0.7-3.9c0-0.1,0-0.2,0-0.4c0-0.1,0-0.2,0-0.4V1.1H111.8z"/>
|
||||
<path d="M121.1,6.3v3.9h6.9v5.2h-6.9v4.1h7.3v5.2H115V1.1h13.4v5.2C128.4,6.3,121.1,6.3,121.1,6.3z"/>
|
||||
<path d="M147.8,19.5h7.4v5.2h-13.5V1.1h6.1V19.5z"/>
|
||||
<path d="M162.5,20.6l-1.6,4.1h-6.5l9.1-23.6h6.7l8.9,23.6h-6.6l-1.5-4.1H162.5z M166.8,8.4L166.8,8.4l-2.6,7.5h5.1L166.8,8.4z"/>
|
||||
<path d="M190.4,1.1h9.4c2.5,0,4.6,0.6,6.2,1.9c1.6,1.2,2.4,3.2,2.4,5.9c0,2.8-0.7,4.8-2.2,6.1s-3.5,1.9-6.2,1.9h-3.5v7.8h-6.1
|
||||
L190.4,1.1L190.4,1.1z M196.6,12h1c1.1,0,2.1-0.1,3-0.3s1.4-1.1,1.4-2.5c0-1.5-0.4-2.4-1.3-2.6c-0.9-0.3-1.9-0.4-3.1-0.4h-1
|
||||
L196.6,12L196.6,12z"/>
|
||||
<path d="M231,1.1v13.3c0,1.5-0.1,3-0.4,4.5c-0.2,1.4-0.9,2.7-2.1,3.9c-0.8,0.8-1.6,1.3-2.7,1.7c-1,0.4-2.1,0.6-3.2,0.8
|
||||
c-0.3,0-0.7,0.1-1,0.1s-0.7,0-1,0s-0.6,0-1,0c-0.3,0-0.7-0.1-1-0.1c-1.1-0.1-2.1-0.4-3.2-0.8c-1-0.4-1.9-1-2.7-1.7
|
||||
c-1.2-1.1-1.9-2.4-2.1-3.9c-0.2-1.4-0.4-2.9-0.4-4.4V1.1h6.1v12.4c0,0.1,0,0.3,0,0.4c0,0.1,0,0.2,0,0.4c0,1.4,0.3,2.7,0.7,3.9
|
||||
c0.5,1.2,1.6,1.8,3.4,1.8c1.8,0,3-0.6,3.4-1.8s0.7-2.5,0.7-3.9c0-0.1,0-0.2,0-0.4c0-0.1,0-0.2,0-0.4V1.1H231z"/>
|
||||
<path d="M234.2,24.7V1.1h8.8c2.1,0,3.8,0.5,5.1,1.4S250,5,250,7.3c0,1.1-0.2,2-0.6,2.9c-0.4,0.8-1.1,1.5-2,2l0,0
|
||||
c1.7,0.2,2.9,0.9,3.7,2s1.1,2.4,1.1,4.1c0,2.5-0.9,4.2-2.6,5.1c-1.8,1-3.7,1.4-5.9,1.4h-9.5V24.7z M240.4,10.4h0.7c0,0,0,0,0.1,0
|
||||
s0.1,0,0.1,0c0.8,0,1.5-0.2,2.1-0.4c0.7-0.3,1-0.9,1-1.8c0-1-0.3-1.6-0.9-1.9c-0.6-0.3-1.3-0.4-2-0.4c-0.1,0-0.2,0-0.3,0
|
||||
s-0.2,0-0.3,0h-0.4v4.5H240.4z M240.4,20h0.8c0,0,0,0,0.1,0h0.1c0.3,0,0.5,0,0.8,0s0.5,0,0.8,0c0.8-0.1,1.5-0.3,2.1-0.6
|
||||
c0.6-0.3,1-1,1-1.9c0-1.1-0.4-1.8-1.1-2.1c-0.7-0.3-1.5-0.5-2.3-0.5c-0.2,0-0.4,0-0.6,0c-0.2,0-0.4,0-0.6,0c-0.1,0-0.2,0-0.3,0
|
||||
s-0.2,0-0.3,0h-0.6V20H240.4z"/>
|
||||
<path d="M280.3,2.4l-2.4,4.8c-0.6-0.5-1.2-0.8-1.9-1.1c-0.7-0.3-1.4-0.4-2.2-0.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.3,0-0.4,0.1
|
||||
c-0.5,0.1-0.9,0.3-1.3,0.6c-0.4,0.3-0.6,0.7-0.6,1.3c0,0.5,0.2,0.9,0.6,1.2s0.8,0.5,1.2,0.7c0.2,0.1,0.3,0.1,0.5,0.2
|
||||
c0.2,0.1,0.3,0.1,0.5,0.1l1.6,0.4c1.7,0.5,3,1.2,4.2,2.2c1.1,1,1.7,2.4,1.7,4.3c0,0.1,0,0.2,0,0.3c0,0.1,0,0.2,0,0.3
|
||||
c0,1.1-0.2,2.1-0.6,3.2c-0.4,1-1,1.9-1.8,2.7c-0.8,0.7-1.7,1.2-2.7,1.5s-2,0.5-3.1,0.6c-0.2,0-0.3,0-0.5,0s-0.3,0-0.5,0
|
||||
c-1.5,0-2.9-0.2-4.4-0.7c-1.4-0.5-2.7-1.1-3.9-2l2.6-5c0.8,0.7,1.6,1.3,2.5,1.8s1.9,0.7,2.9,0.7h0.1h0.1c0.7,0,1.3-0.3,1.9-0.6
|
||||
c0.6-0.4,0.8-1,0.8-1.7c0-0.6-0.2-1.1-0.7-1.4c-0.5-0.4-1-0.6-1.6-0.8c-0.3-0.1-0.5-0.2-0.8-0.2c-0.3-0.1-0.5-0.1-0.7-0.2H271
|
||||
c-0.1,0-0.1,0-0.1,0c-1.9-0.5-3.3-1.2-4.5-2s-1.7-2.3-1.7-4.6c0-2.4,0.8-4.3,2.3-5.8c1.5-1.4,3.5-2.2,5.8-2.2c0.5,0,1,0,1.5,0.1
|
||||
s1,0.1,1.5,0.3c0.8,0.2,1.6,0.4,2.3,0.7C279,1.8,279.7,2.1,280.3,2.4L280.3,2.4z"/>
|
||||
<path d="M304,1.1v13.3c0,1.5-0.1,3-0.4,4.5c-0.2,1.4-0.9,2.7-2.1,3.9c-0.8,0.8-1.6,1.3-2.7,1.7c-1,0.4-2.1,0.6-3.2,0.8
|
||||
c-0.3,0-0.7,0.1-1,0.1s-0.7,0-1,0s-0.6,0-1,0c-0.3,0-0.7-0.1-1-0.1c-1.1-0.1-2.1-0.4-3.2-0.8c-1-0.4-1.9-1-2.7-1.7
|
||||
c-1.2-1.1-1.9-2.4-2.1-3.9c-0.2-1.4-0.4-2.9-0.4-4.4V1.1h6.1v12.4c0,0.1,0,0.3,0,0.4c0,0.1,0,0.2,0,0.4c0,1.4,0.3,2.7,0.7,3.9
|
||||
c0.5,1.2,1.6,1.8,3.4,1.8s3-0.6,3.4-1.8s0.7-2.5,0.7-3.9c0-0.1,0-0.2,0-0.4c0-0.1,0-0.2,0-0.4V1.1H304z"/>
|
||||
<path d="M319.5,15.1l7.4,9.7h-7.6l-5.8-9.1h-0.1v9.1h-6.1V1.1h9.2c2.3,0,4.3,0.6,5.9,1.8s2.4,3,2.4,5.5c0,1.6-0.4,3.1-1.3,4.3
|
||||
C322.4,14,321.1,14.8,319.5,15.1L319.5,15.1z M313.4,11.7h0.6c0.1,0,0.2,0,0.3,0c0.1,0,0.2,0,0.3,0c0.9,0,1.8-0.3,2.5-0.6
|
||||
s1.1-1.2,1.1-2.3c0-1.2-0.4-1.9-1.1-2.3c-0.8-0.4-1.6-0.6-2.5-0.6c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2,0-0.3,0h-0.6V11.7z"/>
|
||||
<path d="M344.1,24.7H338V1.1h6.1V24.7z"/>
|
||||
<path d="M347.5,1.1h6.1l11.3,14.4h0.1V1.1h6.1v23.6H365l-11.3-14.5h-0.1v14.5h-6.1V1.1z"/>
|
||||
<path d="M383.8,24.7h-6.1V6.3h-5.1V1.1h16.3v5.2h-5.1L383.8,24.7L383.8,24.7z"/>
|
||||
<path d="M396.6,6.3v3.9h6.9v5.2h-6.9v4.1h7.3v5.2h-13.4V1.1h13.4v5.2C403.9,6.3,396.6,6.3,396.6,6.3z"/>
|
||||
<path d="M419.3,15.1l7.4,9.7h-7.6l-5.8-9.1h-0.1v9.1h-6.1V1.1h9.2c2.3,0,4.3,0.6,5.9,1.8s2.4,3,2.4,5.5c0,1.6-0.4,3.1-1.3,4.3
|
||||
C422.2,14,421,14.8,419.3,15.1L419.3,15.1z M413.2,11.7h0.6c0.1,0,0.2,0,0.3,0c0.1,0,0.2,0,0.3,0c0.9,0,1.8-0.3,2.5-0.6
|
||||
s1.1-1.2,1.1-2.3c0-1.2-0.4-1.9-1.1-2.3c-0.8-0.4-1.6-0.6-2.5-0.6c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2,0-0.3,0h-0.6V11.7z"/>
|
||||
<path d="M427.7,1.1h6.1l11.3,14.4h0.1V1.1h6.1v23.6h-6.1l-11.3-14.5h-0.1v14.5h-6.1V1.1z"/>
|
||||
<path d="M460.8,6.3v3.9h6.9v5.2h-6.9v4.1h7.3v5.2h-13.4V1.1h13.4v5.2C468.1,6.3,460.8,6.3,460.8,6.3z"/>
|
||||
<path d="M480.6,24.7h-6.1V6.3h-5.1V1.1h16.3v5.2h-5.1L480.6,24.7L480.6,24.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 252.5 62.4" style="enable-background:new 0 0 252.5 62.4;" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M1,24.4V0.8h8.8c2.1,0,3.8,0.5,5.1,1.4s1.9,2.5,1.9,4.8c0,1.1-0.2,2-0.6,2.9c-0.4,0.8-1.1,1.5-2,2l0,0
|
||||
c1.7,0.2,2.9,0.9,3.7,2C18.7,15,19,16.3,19,18c0,2.5-0.9,4.2-2.6,5.1s-3.7,1.4-5.9,1.4H1V24.4z M7.1,10.1h0.7c0,0,0,0,0.1,0H8
|
||||
c0.8,0,1.5-0.2,2.1-0.4c0.7-0.3,1-0.9,1-1.8c0-1-0.3-1.6-0.9-1.9c-0.6-0.3-1.3-0.4-2-0.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2,0-0.3,0
|
||||
H7.1V10.1z M7.1,19.7h0.8c0,0,0,0,0.1,0c0.1,0,0.1,0,0.1,0c0.3,0,0.5,0,0.8,0s0.5,0,0.8,0c0.8-0.1,1.5-0.3,2.1-0.6
|
||||
c0.6-0.3,1-1,1-1.9c0-1.1-0.4-1.8-1.1-2.1c-0.7-0.2-1.4-0.4-2.3-0.4c-0.2,0-0.4,0-0.6,0c-0.2,0-0.4,0-0.6,0c-0.1,0-0.2,0-0.3,0
|
||||
s-0.2,0-0.3,0H7.1V19.7z"/>
|
||||
<path d="M27.7,19.2h7.4v5.2H21.5V0.8h6.1L27.7,19.2L27.7,19.2z"/>
|
||||
<path d="M48.3,0c3.4,0,6.4,1.2,9.1,3.4c2.7,2.3,4,5.2,4,8.8c0,3.8-1.2,6.9-3.7,9.4c-2.5,2.4-5.6,3.6-9.4,3.6s-6.9-1.2-9.4-3.6
|
||||
s-3.7-5.5-3.7-9.4c0-3.6,1.3-6.5,4-8.8C41.8,1.2,44.9,0.1,48.3,0L48.3,0z M43.6,7.9c-1.3,1.3-2,2.7-2,4.3c0,2,0.7,3.7,2,5
|
||||
s2.9,2,4.7,2s3.4-0.7,4.7-2s2-3,2-5.1c0-1.6-0.7-3.1-2-4.4s-2.9-1.9-4.7-1.9C46.4,6,44.9,6.6,43.6,7.9z"/>
|
||||
<path d="M83.8,25.9l-1.9-2.2c-0.9,0.5-1.9,0.9-3,1.2c-1.1,0.2-2.3,0.4-3.5,0.4c-3.8,0-6.9-1.2-9.4-3.6s-3.7-5.5-3.7-9.4
|
||||
c0-3.6,1.3-6.5,4-8.8s5.7-3.4,9.1-3.4c3.7,0,6.8,1.1,9.3,3.4s3.8,5.4,3.8,9.2c0,1.6-0.3,3-0.8,4.4s-1.3,2.7-2.3,3.9l4,4L83.8,25.9z
|
||||
M81,16.2c0.3-0.4,0.5-0.8,0.7-1.2s0.3-0.9,0.4-1.3c0-0.1,0-0.3,0-0.5s0-0.3,0-0.5c0-1.8-0.6-3.3-1.9-4.7s-2.9-2-4.7-2
|
||||
c-1.8,0-3.4,0.6-4.7,1.9s-2,2.7-2,4.3c0,2,0.7,3.7,2,5s2.9,2,4.7,2c0.3,0,0.6,0,0.9-0.1s0.6-0.1,0.9-0.2l-3.8-3.8l5.8-0.7L81,16.2z
|
||||
"/>
|
||||
<path d="M111.2,0.8v13.3c0,1.5-0.1,3-0.4,4.5c-0.2,1.4-0.9,2.7-2.1,3.9c-0.8,0.8-1.6,1.3-2.7,1.7c-1,0.4-2.1,0.6-3.2,0.8
|
||||
c-0.3,0-0.7,0.1-1,0.1s-0.7,0-1,0s-0.6,0-1,0c-0.3,0-0.7-0.1-1-0.1c-1.1-0.1-2.1-0.4-3.2-0.8c-1-0.4-1.9-1-2.7-1.7
|
||||
c-1.2-1.1-1.9-2.4-2.1-3.9c-0.2-1.4-0.4-2.9-0.4-4.4V0.8h6.1v12.4c0,0.1,0,0.3,0,0.4c0,0.1,0,0.2,0,0.4c0,1.4,0.3,2.7,0.7,3.9
|
||||
c0.5,1.2,1.6,1.8,3.4,1.8s3-0.6,3.4-1.8s0.7-2.5,0.7-3.9c0-0.1,0-0.2,0-0.4c0-0.1,0-0.2,0-0.4V0.8H111.2z"/>
|
||||
<path d="M120.5,6v3.9h6.9v5.2h-6.9v4.1h7.3v5.2h-13.4V0.8h13.4V6C127.8,6,120.5,6,120.5,6z"/>
|
||||
<path d="M147.2,19.2h7.4v5.2h-13.5V0.8h6.1V19.2z"/>
|
||||
<path d="M161.9,20.3l-1.6,4.1h-6.5l9.1-23.6h6.7l8.9,23.6h-6.6l-1.5-4.1H161.9z M166.2,8.1L166.2,8.1l-2.6,7.5h5.1L166.2,8.1z"/>
|
||||
<path d="M189.8,0.8h9.4c2.5,0,4.6,0.6,6.2,1.9c1.6,1.2,2.4,3.2,2.4,5.9c0,2.8-0.7,4.8-2.2,6.1s-3.5,1.9-6.2,1.9h-3.5v7.8h-6.1
|
||||
L189.8,0.8L189.8,0.8z M196,11.7h1c1.1,0,2.1-0.1,3-0.3s1.4-1.1,1.4-2.5c0-1.5-0.4-2.4-1.3-2.6c-0.9-0.3-1.9-0.4-3.1-0.4h-1
|
||||
L196,11.7L196,11.7z"/>
|
||||
<path d="M230.4,0.8v13.3c0,1.5-0.1,3-0.4,4.5c-0.2,1.4-0.9,2.7-2.1,3.9c-0.8,0.8-1.6,1.3-2.7,1.7c-1,0.4-2.1,0.6-3.2,0.8
|
||||
c-0.3,0-0.7,0.1-1,0.1s-0.7,0-1,0s-0.6,0-1,0c-0.3,0-0.7-0.1-1-0.1c-1.1-0.1-2.1-0.4-3.2-0.8c-1-0.4-1.9-1-2.7-1.7
|
||||
c-1.2-1.1-1.9-2.4-2.1-3.9c-0.2-1.4-0.4-2.9-0.4-4.4V0.8h6.1v12.4c0,0.1,0,0.3,0,0.4c0,0.1,0,0.2,0,0.4c0,1.4,0.3,2.7,0.7,3.9
|
||||
c0.5,1.2,1.6,1.8,3.4,1.8c1.8,0,3-0.6,3.4-1.8s0.7-2.5,0.7-3.9c0-0.1,0-0.2,0-0.4c0-0.1,0-0.2,0-0.4V0.8H230.4z"/>
|
||||
<path d="M233.6,24.4V0.8h8.8c2.1,0,3.8,0.5,5.1,1.4s1.9,2.5,1.9,4.8c0,1.1-0.2,2-0.6,2.9c-0.4,0.8-1.1,1.5-2,2l0,0
|
||||
c1.7,0.2,2.9,0.9,3.7,2s1.1,2.4,1.1,4.1c0,2.5-0.9,4.2-2.6,5.1c-1.8,1-3.7,1.4-5.9,1.4h-9.5V24.4z M239.8,10.1h0.7c0,0,0,0,0.1,0
|
||||
s0.1,0,0.1,0c0.8,0,1.5-0.2,2.1-0.4c0.7-0.3,1-0.9,1-1.8c0-1-0.3-1.6-0.9-1.9c-0.6-0.3-1.3-0.4-2-0.4c-0.1,0-0.2,0-0.3,0
|
||||
s-0.2,0-0.3,0h-0.4v4.5H239.8z M239.8,19.7h0.8c0,0,0,0,0.1,0h0.1c0.3,0,0.5,0,0.8,0s0.5,0,0.8,0c0.8-0.1,1.5-0.3,2.1-0.6
|
||||
c0.6-0.3,1-1,1-1.9c0-1.1-0.4-1.8-1.1-2.1c-0.7-0.3-1.5-0.5-2.3-0.5c-0.2,0-0.4,0-0.6,0c-0.2,0-0.4,0-0.6,0c-0.1,0-0.2,0-0.3,0
|
||||
s-0.2,0-0.3,0h-0.6v5.1H239.8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M31.7,38.5l-2.4,4.8c-0.6-0.5-1.2-0.8-1.9-1.1c-0.7-0.3-1.4-0.4-2.2-0.4c-0.1,0-0.2,0-0.3,0c-0.1,0-0.3,0-0.4,0.1
|
||||
c-0.5,0.1-0.9,0.3-1.3,0.6s-0.6,0.7-0.6,1.3c0,0.5,0.2,0.9,0.6,1.2c0.4,0.3,0.8,0.5,1.2,0.7c0.2,0.1,0.3,0.1,0.5,0.2
|
||||
c0.2,0.1,0.3,0.1,0.5,0.1l1.6,0.4c1.7,0.5,3,1.2,4.2,2.2c1.1,1,1.7,2.4,1.7,4.3c0,0.1,0,0.2,0,0.3c0,0.1,0,0.2,0,0.3
|
||||
c0,1.1-0.2,2.1-0.6,3.2c-0.4,1-1,1.9-1.8,2.7c-0.8,0.7-1.7,1.2-2.7,1.5s-2,0.5-3.1,0.6c-0.2,0-0.3,0-0.5,0s-0.3,0-0.5,0
|
||||
c-1.5,0-2.9-0.2-4.4-0.7c-1.4-0.5-2.7-1.1-3.9-2l2.6-5c0.8,0.7,1.6,1.3,2.5,1.8s1.9,0.7,2.9,0.7h0.1h0.1c0.7,0,1.3-0.3,1.9-0.6
|
||||
c0.6-0.4,0.8-1,0.8-1.7c0-0.6-0.2-1.1-0.7-1.4c-0.5-0.4-1-0.6-1.6-0.8c-0.3-0.1-0.5-0.2-0.8-0.2c-0.3-0.1-0.5-0.1-0.7-0.2h-0.1
|
||||
c-0.1,0-0.1,0-0.1,0c-1.9-0.5-3.3-1.2-4.5-2s-1.7-2.3-1.7-4.6c0-2.4,0.8-4.3,2.3-5.8c1.5-1.4,3.5-2.2,5.8-2.2c0.5,0,1,0,1.5,0.1
|
||||
c0.5,0.1,1,0.1,1.5,0.3c0.8,0.2,1.6,0.4,2.3,0.7C30.4,37.9,31.1,38.2,31.7,38.5L31.7,38.5z"/>
|
||||
<path d="M55.4,37.2v13.3c0,1.5-0.1,3-0.4,4.5c-0.2,1.4-0.9,2.7-2.1,3.9c-0.8,0.8-1.6,1.3-2.7,1.7c-1,0.4-2.1,0.6-3.2,0.8
|
||||
c-0.3,0-0.7,0.1-1,0.1s-0.7,0-1,0s-0.6,0-1,0c-0.3,0-0.7-0.1-1-0.1c-1.1-0.1-2.1-0.4-3.2-0.8c-1-0.4-1.9-1-2.7-1.7
|
||||
c-1.2-1.1-1.9-2.4-2.1-3.9c-0.2-1.4-0.4-2.9-0.4-4.4V37.2h6.1v12.4c0,0.1,0,0.3,0,0.4c0,0.1,0,0.2,0,0.4c0,1.4,0.3,2.7,0.7,3.9
|
||||
c0.5,1.2,1.6,1.8,3.4,1.8s3-0.6,3.4-1.8c0.4-1.2,0.7-2.5,0.7-3.9c0-0.1,0-0.2,0-0.4c0-0.1,0-0.2,0-0.4V37.2H55.4z"/>
|
||||
<path d="M70.9,51.2l7.4,9.7h-7.6l-5.8-9.1h-0.1v9.1h-6.1V37.2h9.2c2.3,0,4.3,0.6,5.9,1.8s2.4,3,2.4,5.5c0,1.6-0.4,3.1-1.3,4.3
|
||||
C73.8,50.1,72.5,50.9,70.9,51.2L70.9,51.2z M64.8,47.8h0.6c0.1,0,0.2,0,0.3,0c0.1,0,0.2,0,0.3,0c0.9,0,1.8-0.3,2.5-0.6
|
||||
c0.7-0.3,1.1-1.2,1.1-2.3c0-1.2-0.4-1.9-1.1-2.3C67.7,42.2,66.9,42,66,42c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2,0-0.3,0h-0.6V47.8z"/>
|
||||
<path d="M95.5,60.8h-6.1V37.2h6.1V60.8z"/>
|
||||
<path d="M98.9,37.2h6.1l11.3,14.4h0.1V37.2h6.1v23.6h-6.1l-11.3-14.5H105v14.5h-6.1V37.2z"/>
|
||||
<path d="M135.2,60.8h-6.1V42.4H124v-5.2h16.3v5.2h-5.1L135.2,60.8L135.2,60.8z"/>
|
||||
<path d="M148,42.4v3.9h6.9v5.2H148v4.1h7.3v5.2h-13.4V37.2h13.4v5.2C155.3,42.4,148,42.4,148,42.4z"/>
|
||||
<path d="M170.7,51.2l7.4,9.7h-7.6l-5.8-9.1h-0.1v9.1h-6.1V37.2h9.2c2.3,0,4.3,0.6,5.9,1.8s2.4,3,2.4,5.5c0,1.6-0.4,3.1-1.3,4.3
|
||||
C173.6,50.1,172.4,50.9,170.7,51.2L170.7,51.2z M164.6,47.8h0.6c0.1,0,0.2,0,0.3,0c0.1,0,0.2,0,0.3,0c0.9,0,1.8-0.3,2.5-0.6
|
||||
c0.7-0.3,1.1-1.2,1.1-2.3c0-1.2-0.4-1.9-1.1-2.3c-0.8-0.4-1.6-0.6-2.5-0.6c-0.1,0-0.2,0-0.3,0c-0.1,0-0.2,0-0.3,0h-0.6V47.8z"/>
|
||||
<path d="M179.1,37.2h6.1l11.3,14.4h0.1V37.2h6.1v23.6h-6.1l-11.3-14.5h-0.1v14.5h-6.1V37.2z"/>
|
||||
<path d="M212.2,42.4v3.9h6.9v5.2h-6.9v4.1h7.3v5.2h-13.4V37.2h13.4v5.2C219.5,42.4,212.2,42.4,212.2,42.4z"/>
|
||||
<path d="M232,60.8h-6.1V42.4h-5.1v-5.2h16.3v5.2H232L232,60.8L232,60.8z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 91 KiB |
After Width: | Height: | Size: 64 KiB |
|
@ -0,0 +1,20 @@
|
|||
|
||||
categories.xml
|
||||
|
||||
config.php
|
||||
|
||||
freebox-content.txt
|
||||
|
||||
rsscache_*
|
||||
|
||||
feed.xml
|
||||
|
||||
media/*
|
||||
|
||||
images/*
|
||||
|
||||
!media/index.htm
|
||||
|
||||
!images/itunes_image.jpg
|
||||
|
||||
!images/smiley_default_freebox.png
|
|
@ -0,0 +1,674 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
|
@ -0,0 +1,26 @@
|
|||
# Podcast Generator
|
||||
|
||||
Podcast Generator (PG) is an open source Content Management System written in PHP
|
||||
and specifically designed for podcast publishing. It provides the user with the tools
|
||||
to easily manage all of the aspects related to the publication of a podcast, from
|
||||
the upload of episodes to its submission to the iTunes store.
|
||||
|
||||
|
||||
Visit the **official website** for more information, documentation, demo and to download the latest stable release:
|
||||
http://podcastgenerator.net
|
||||
|
||||
## Download the latest release
|
||||
Download the latest stable release on the official website:
|
||||
http://podcastgenerator.net/download
|
||||
|
||||
|
||||
## Documentation
|
||||
A comprehensive documentation is available at:
|
||||
http://podcastgenerator.net/documentation
|
||||
|
||||
|
||||
## Demo
|
||||
A fully-functional live demo of Podcast generator is available at:
|
||||
http://podcastgenerator.net/demo
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// also https://github.com/JamesHeinrich/getID3 //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
*****************************************************************
|
||||
*****************************************************************
|
||||
|
||||
getID3() is released under multiple licenses. You may choose
|
||||
from the following licenses, and use getID3 according to the
|
||||
terms of the license most suitable to your project.
|
||||
|
||||
GNU GPL: https://gnu.org/licenses/gpl.html (v3)
|
||||
https://gnu.org/licenses/old-licenses/gpl-2.0.html (v2)
|
||||
https://gnu.org/licenses/old-licenses/gpl-1.0.html (v1)
|
||||
|
||||
GNU LGPL: https://gnu.org/licenses/lgpl.html (v3)
|
||||
|
||||
Mozilla MPL: http://www.mozilla.org/MPL/2.0/ (v2)
|
||||
|
||||
getID3 Commercial License: http://getid3.org/#gCL (payment required)
|
||||
|
||||
*****************************************************************
|
||||
*****************************************************************
|
||||
|
||||
Copies of each of the above licenses are included in the 'licenses'
|
||||
directory of the getID3 distribution.
|
|
@ -0,0 +1,513 @@
|
|||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// also https://github.com/JamesHeinrich/getID3 //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.archive.zip.php //
|
||||
// module for analyzing pkZip files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_zip extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'zip';
|
||||
$info['zip']['encoding'] = 'ISO-8859-1';
|
||||
$info['zip']['files'] = array();
|
||||
|
||||
$info['zip']['compressed_size'] = 0;
|
||||
$info['zip']['uncompressed_size'] = 0;
|
||||
$info['zip']['entries_count'] = 0;
|
||||
|
||||
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||
$info['error'][] = 'File is larger than '.round(PHP_INT_MAX / 1073741824).'GB, not supported by PHP';
|
||||
return false;
|
||||
} else {
|
||||
$EOCDsearchData = '';
|
||||
$EOCDsearchCounter = 0;
|
||||
while ($EOCDsearchCounter++ < 512) {
|
||||
|
||||
$this->fseek(-128 * $EOCDsearchCounter, SEEK_END);
|
||||
$EOCDsearchData = $this->fread(128).$EOCDsearchData;
|
||||
|
||||
if (strstr($EOCDsearchData, 'PK'."\x05\x06")) {
|
||||
|
||||
$EOCDposition = strpos($EOCDsearchData, 'PK'."\x05\x06");
|
||||
$this->fseek((-128 * $EOCDsearchCounter) + $EOCDposition, SEEK_END);
|
||||
$info['zip']['end_central_directory'] = $this->ZIPparseEndOfCentralDirectory();
|
||||
|
||||
$this->fseek($info['zip']['end_central_directory']['directory_offset']);
|
||||
$info['zip']['entries_count'] = 0;
|
||||
while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($this->getid3->fp)) {
|
||||
$info['zip']['central_directory'][] = $centraldirectoryentry;
|
||||
$info['zip']['entries_count']++;
|
||||
$info['zip']['compressed_size'] += $centraldirectoryentry['compressed_size'];
|
||||
$info['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size'];
|
||||
|
||||
//if ($centraldirectoryentry['uncompressed_size'] > 0) { zero-byte files are valid
|
||||
if (!empty($centraldirectoryentry['filename'])) {
|
||||
$info['zip']['files'] = getid3_lib::array_merge_clobber($info['zip']['files'], getid3_lib::CreateDeepArray($centraldirectoryentry['filename'], '/', $centraldirectoryentry['uncompressed_size']));
|
||||
}
|
||||
}
|
||||
|
||||
if ($info['zip']['entries_count'] == 0) {
|
||||
$info['error'][] = 'No Central Directory entries found (truncated file?)';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($info['zip']['end_central_directory']['comment'])) {
|
||||
$info['zip']['comments']['comment'][] = $info['zip']['end_central_directory']['comment'];
|
||||
}
|
||||
|
||||
if (isset($info['zip']['central_directory'][0]['compression_method'])) {
|
||||
$info['zip']['compression_method'] = $info['zip']['central_directory'][0]['compression_method'];
|
||||
}
|
||||
if (isset($info['zip']['central_directory'][0]['flags']['compression_speed'])) {
|
||||
$info['zip']['compression_speed'] = $info['zip']['central_directory'][0]['flags']['compression_speed'];
|
||||
}
|
||||
if (isset($info['zip']['compression_method']) && ($info['zip']['compression_method'] == 'store') && !isset($info['zip']['compression_speed'])) {
|
||||
$info['zip']['compression_speed'] = 'store';
|
||||
}
|
||||
|
||||
// secondary check - we (should) already have all the info we NEED from the Central Directory above, but scanning each
|
||||
// Local File Header entry will
|
||||
foreach ($info['zip']['central_directory'] as $central_directory_entry) {
|
||||
$this->fseek($central_directory_entry['entry_offset']);
|
||||
if ($fileentry = $this->ZIPparseLocalFileHeader()) {
|
||||
$info['zip']['entries'][] = $fileentry;
|
||||
} else {
|
||||
$info['warning'][] = 'Error parsing Local File Header at offset '.$central_directory_entry['entry_offset'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($info['zip']['files']['[Content_Types].xml']) &&
|
||||
!empty($info['zip']['files']['_rels']['.rels']) &&
|
||||
!empty($info['zip']['files']['docProps']['app.xml']) &&
|
||||
!empty($info['zip']['files']['docProps']['core.xml'])) {
|
||||
// http://technet.microsoft.com/en-us/library/cc179224.aspx
|
||||
$info['fileformat'] = 'zip.msoffice';
|
||||
if (!empty($ThisFileInfo['zip']['files']['ppt'])) {
|
||||
$info['mime_type'] = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
|
||||
} elseif (!empty($ThisFileInfo['zip']['files']['xl'])) {
|
||||
$info['mime_type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
} elseif (!empty($ThisFileInfo['zip']['files']['word'])) {
|
||||
$info['mime_type'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->getZIPentriesFilepointer()) {
|
||||
unset($info['zip']);
|
||||
$info['fileformat'] = '';
|
||||
$info['error'][] = 'Cannot find End Of Central Directory (truncated file?)';
|
||||
return false;
|
||||
}
|
||||
|
||||
// central directory couldn't be found and/or parsed
|
||||
// scan through actual file data entries, recover as much as possible from probable trucated file
|
||||
if ($info['zip']['compressed_size'] > ($info['filesize'] - 46 - 22)) {
|
||||
$info['error'][] = 'Warning: Truncated file! - Total compressed file sizes ('.$info['zip']['compressed_size'].' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures ('.($info['filesize'] - 46 - 22).' bytes)';
|
||||
}
|
||||
$info['error'][] = 'Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete';
|
||||
foreach ($info['zip']['entries'] as $key => $valuearray) {
|
||||
$info['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size'];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function getZIPHeaderFilepointerTopDown() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'zip';
|
||||
|
||||
$info['zip']['compressed_size'] = 0;
|
||||
$info['zip']['uncompressed_size'] = 0;
|
||||
$info['zip']['entries_count'] = 0;
|
||||
|
||||
rewind($this->getid3->fp);
|
||||
while ($fileentry = $this->ZIPparseLocalFileHeader()) {
|
||||
$info['zip']['entries'][] = $fileentry;
|
||||
$info['zip']['entries_count']++;
|
||||
}
|
||||
if ($info['zip']['entries_count'] == 0) {
|
||||
$info['error'][] = 'No Local File Header entries found';
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['zip']['entries_count'] = 0;
|
||||
while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($this->getid3->fp)) {
|
||||
$info['zip']['central_directory'][] = $centraldirectoryentry;
|
||||
$info['zip']['entries_count']++;
|
||||
$info['zip']['compressed_size'] += $centraldirectoryentry['compressed_size'];
|
||||
$info['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size'];
|
||||
}
|
||||
if ($info['zip']['entries_count'] == 0) {
|
||||
$info['error'][] = 'No Central Directory entries found (truncated file?)';
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($EOCD = $this->ZIPparseEndOfCentralDirectory()) {
|
||||
$info['zip']['end_central_directory'] = $EOCD;
|
||||
} else {
|
||||
$info['error'][] = 'No End Of Central Directory entry found (truncated file?)';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($info['zip']['end_central_directory']['comment'])) {
|
||||
$info['zip']['comments']['comment'][] = $info['zip']['end_central_directory']['comment'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function getZIPentriesFilepointer() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['zip']['compressed_size'] = 0;
|
||||
$info['zip']['uncompressed_size'] = 0;
|
||||
$info['zip']['entries_count'] = 0;
|
||||
|
||||
rewind($this->getid3->fp);
|
||||
while ($fileentry = $this->ZIPparseLocalFileHeader()) {
|
||||
$info['zip']['entries'][] = $fileentry;
|
||||
$info['zip']['entries_count']++;
|
||||
$info['zip']['compressed_size'] += $fileentry['compressed_size'];
|
||||
$info['zip']['uncompressed_size'] += $fileentry['uncompressed_size'];
|
||||
}
|
||||
if ($info['zip']['entries_count'] == 0) {
|
||||
$info['error'][] = 'No Local File Header entries found';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function ZIPparseLocalFileHeader() {
|
||||
$LocalFileHeader['offset'] = $this->ftell();
|
||||
|
||||
$ZIPlocalFileHeader = $this->fread(30);
|
||||
|
||||
$LocalFileHeader['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 0, 4));
|
||||
if ($LocalFileHeader['raw']['signature'] != 0x04034B50) { // "PK\x03\x04"
|
||||
// invalid Local File Header Signature
|
||||
$this->fseek($LocalFileHeader['offset']); // seek back to where filepointer originally was so it can be handled properly
|
||||
return false;
|
||||
}
|
||||
$LocalFileHeader['raw']['extract_version'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 4, 2));
|
||||
$LocalFileHeader['raw']['general_flags'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 6, 2));
|
||||
$LocalFileHeader['raw']['compression_method'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 8, 2));
|
||||
$LocalFileHeader['raw']['last_mod_file_time'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 10, 2));
|
||||
$LocalFileHeader['raw']['last_mod_file_date'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 12, 2));
|
||||
$LocalFileHeader['raw']['crc_32'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 14, 4));
|
||||
$LocalFileHeader['raw']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 18, 4));
|
||||
$LocalFileHeader['raw']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 22, 4));
|
||||
$LocalFileHeader['raw']['filename_length'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 26, 2));
|
||||
$LocalFileHeader['raw']['extra_field_length'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 28, 2));
|
||||
|
||||
$LocalFileHeader['extract_version'] = sprintf('%1.1f', $LocalFileHeader['raw']['extract_version'] / 10);
|
||||
$LocalFileHeader['host_os'] = $this->ZIPversionOSLookup(($LocalFileHeader['raw']['extract_version'] & 0xFF00) >> 8);
|
||||
$LocalFileHeader['compression_method'] = $this->ZIPcompressionMethodLookup($LocalFileHeader['raw']['compression_method']);
|
||||
$LocalFileHeader['compressed_size'] = $LocalFileHeader['raw']['compressed_size'];
|
||||
$LocalFileHeader['uncompressed_size'] = $LocalFileHeader['raw']['uncompressed_size'];
|
||||
$LocalFileHeader['flags'] = $this->ZIPparseGeneralPurposeFlags($LocalFileHeader['raw']['general_flags'], $LocalFileHeader['raw']['compression_method']);
|
||||
$LocalFileHeader['last_modified_timestamp'] = $this->DOStime2UNIXtime($LocalFileHeader['raw']['last_mod_file_date'], $LocalFileHeader['raw']['last_mod_file_time']);
|
||||
|
||||
$FilenameExtrafieldLength = $LocalFileHeader['raw']['filename_length'] + $LocalFileHeader['raw']['extra_field_length'];
|
||||
if ($FilenameExtrafieldLength > 0) {
|
||||
$ZIPlocalFileHeader .= $this->fread($FilenameExtrafieldLength);
|
||||
|
||||
if ($LocalFileHeader['raw']['filename_length'] > 0) {
|
||||
$LocalFileHeader['filename'] = substr($ZIPlocalFileHeader, 30, $LocalFileHeader['raw']['filename_length']);
|
||||
}
|
||||
if ($LocalFileHeader['raw']['extra_field_length'] > 0) {
|
||||
$LocalFileHeader['raw']['extra_field_data'] = substr($ZIPlocalFileHeader, 30 + $LocalFileHeader['raw']['filename_length'], $LocalFileHeader['raw']['extra_field_length']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($LocalFileHeader['compressed_size'] == 0) {
|
||||
// *Could* be a zero-byte file
|
||||
// But could also be a file written on the fly that didn't know compressed filesize beforehand.
|
||||
// Correct compressed filesize should be in the data_descriptor located after this file data, and also in Central Directory (at end of zip file)
|
||||
if (!empty($this->getid3->info['zip']['central_directory'])) {
|
||||
foreach ($this->getid3->info['zip']['central_directory'] as $central_directory_entry) {
|
||||
if ($central_directory_entry['entry_offset'] == $LocalFileHeader['offset']) {
|
||||
if ($central_directory_entry['compressed_size'] > 0) {
|
||||
// overwrite local zero value (but not ['raw']'compressed_size']) so that seeking for data_descriptor (and next file entry) works correctly
|
||||
$LocalFileHeader['compressed_size'] = $central_directory_entry['compressed_size'];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
$LocalFileHeader['data_offset'] = $this->ftell();
|
||||
$this->fseek($LocalFileHeader['compressed_size'], SEEK_CUR); // this should (but may not) match value in $LocalFileHeader['raw']['compressed_size'] -- $LocalFileHeader['compressed_size'] could have been overwritten above with value from Central Directory
|
||||
|
||||
if ($LocalFileHeader['flags']['data_descriptor_used']) {
|
||||
$DataDescriptor = $this->fread(16);
|
||||
$LocalFileHeader['data_descriptor']['signature'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 0, 4));
|
||||
if ($LocalFileHeader['data_descriptor']['signature'] != 0x08074B50) { // "PK\x07\x08"
|
||||
$this->getid3->warning[] = 'invalid Local File Header Data Descriptor Signature at offset '.($this->ftell() - 16).' - expecting 08 07 4B 50, found '.getid3_lib::PrintHexBytes($LocalFileHeader['data_descriptor']['signature']);
|
||||
$this->fseek($LocalFileHeader['offset']); // seek back to where filepointer originally was so it can be handled properly
|
||||
return false;
|
||||
}
|
||||
$LocalFileHeader['data_descriptor']['crc_32'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 4, 4));
|
||||
$LocalFileHeader['data_descriptor']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 8, 4));
|
||||
$LocalFileHeader['data_descriptor']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 12, 4));
|
||||
if (!$LocalFileHeader['raw']['compressed_size'] && $LocalFileHeader['data_descriptor']['compressed_size']) {
|
||||
foreach ($this->getid3->info['zip']['central_directory'] as $central_directory_entry) {
|
||||
if ($central_directory_entry['entry_offset'] == $LocalFileHeader['offset']) {
|
||||
if ($LocalFileHeader['data_descriptor']['compressed_size'] == $central_directory_entry['compressed_size']) {
|
||||
// $LocalFileHeader['compressed_size'] already set from Central Directory
|
||||
} else {
|
||||
$this->getid3->info['warning'][] = 'conflicting compressed_size from data_descriptor ('.$LocalFileHeader['data_descriptor']['compressed_size'].') vs Central Directory ('.$central_directory_entry['compressed_size'].') for file at offset '.$LocalFileHeader['offset'];
|
||||
}
|
||||
|
||||
if ($LocalFileHeader['data_descriptor']['uncompressed_size'] == $central_directory_entry['uncompressed_size']) {
|
||||
$LocalFileHeader['uncompressed_size'] = $LocalFileHeader['data_descriptor']['uncompressed_size'];
|
||||
} else {
|
||||
$this->getid3->info['warning'][] = 'conflicting uncompressed_size from data_descriptor ('.$LocalFileHeader['data_descriptor']['uncompressed_size'].') vs Central Directory ('.$central_directory_entry['uncompressed_size'].') for file at offset '.$LocalFileHeader['offset'];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $LocalFileHeader;
|
||||
}
|
||||
|
||||
|
||||
public function ZIPparseCentralDirectory() {
|
||||
$CentralDirectory['offset'] = $this->ftell();
|
||||
|
||||
$ZIPcentralDirectory = $this->fread(46);
|
||||
|
||||
$CentralDirectory['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 0, 4));
|
||||
if ($CentralDirectory['raw']['signature'] != 0x02014B50) {
|
||||
// invalid Central Directory Signature
|
||||
$this->fseek($CentralDirectory['offset']); // seek back to where filepointer originally was so it can be handled properly
|
||||
return false;
|
||||
}
|
||||
$CentralDirectory['raw']['create_version'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 4, 2));
|
||||
$CentralDirectory['raw']['extract_version'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 6, 2));
|
||||
$CentralDirectory['raw']['general_flags'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 8, 2));
|
||||
$CentralDirectory['raw']['compression_method'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 10, 2));
|
||||
$CentralDirectory['raw']['last_mod_file_time'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 12, 2));
|
||||
$CentralDirectory['raw']['last_mod_file_date'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 14, 2));
|
||||
$CentralDirectory['raw']['crc_32'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 16, 4));
|
||||
$CentralDirectory['raw']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 20, 4));
|
||||
$CentralDirectory['raw']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 24, 4));
|
||||
$CentralDirectory['raw']['filename_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 28, 2));
|
||||
$CentralDirectory['raw']['extra_field_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 30, 2));
|
||||
$CentralDirectory['raw']['file_comment_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 32, 2));
|
||||
$CentralDirectory['raw']['disk_number_start'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 34, 2));
|
||||
$CentralDirectory['raw']['internal_file_attrib'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 36, 2));
|
||||
$CentralDirectory['raw']['external_file_attrib'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 38, 4));
|
||||
$CentralDirectory['raw']['local_header_offset'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 42, 4));
|
||||
|
||||
$CentralDirectory['entry_offset'] = $CentralDirectory['raw']['local_header_offset'];
|
||||
$CentralDirectory['create_version'] = sprintf('%1.1f', $CentralDirectory['raw']['create_version'] / 10);
|
||||
$CentralDirectory['extract_version'] = sprintf('%1.1f', $CentralDirectory['raw']['extract_version'] / 10);
|
||||
$CentralDirectory['host_os'] = $this->ZIPversionOSLookup(($CentralDirectory['raw']['extract_version'] & 0xFF00) >> 8);
|
||||
$CentralDirectory['compression_method'] = $this->ZIPcompressionMethodLookup($CentralDirectory['raw']['compression_method']);
|
||||
$CentralDirectory['compressed_size'] = $CentralDirectory['raw']['compressed_size'];
|
||||
$CentralDirectory['uncompressed_size'] = $CentralDirectory['raw']['uncompressed_size'];
|
||||
$CentralDirectory['flags'] = $this->ZIPparseGeneralPurposeFlags($CentralDirectory['raw']['general_flags'], $CentralDirectory['raw']['compression_method']);
|
||||
$CentralDirectory['last_modified_timestamp'] = $this->DOStime2UNIXtime($CentralDirectory['raw']['last_mod_file_date'], $CentralDirectory['raw']['last_mod_file_time']);
|
||||
|
||||
$FilenameExtrafieldCommentLength = $CentralDirectory['raw']['filename_length'] + $CentralDirectory['raw']['extra_field_length'] + $CentralDirectory['raw']['file_comment_length'];
|
||||
if ($FilenameExtrafieldCommentLength > 0) {
|
||||
$FilenameExtrafieldComment = $this->fread($FilenameExtrafieldCommentLength);
|
||||
|
||||
if ($CentralDirectory['raw']['filename_length'] > 0) {
|
||||
$CentralDirectory['filename'] = substr($FilenameExtrafieldComment, 0, $CentralDirectory['raw']['filename_length']);
|
||||
}
|
||||
if ($CentralDirectory['raw']['extra_field_length'] > 0) {
|
||||
$CentralDirectory['raw']['extra_field_data'] = substr($FilenameExtrafieldComment, $CentralDirectory['raw']['filename_length'], $CentralDirectory['raw']['extra_field_length']);
|
||||
}
|
||||
if ($CentralDirectory['raw']['file_comment_length'] > 0) {
|
||||
$CentralDirectory['file_comment'] = substr($FilenameExtrafieldComment, $CentralDirectory['raw']['filename_length'] + $CentralDirectory['raw']['extra_field_length'], $CentralDirectory['raw']['file_comment_length']);
|
||||
}
|
||||
}
|
||||
|
||||
return $CentralDirectory;
|
||||
}
|
||||
|
||||
public function ZIPparseEndOfCentralDirectory() {
|
||||
$EndOfCentralDirectory['offset'] = $this->ftell();
|
||||
|
||||
$ZIPendOfCentralDirectory = $this->fread(22);
|
||||
|
||||
$EndOfCentralDirectory['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 0, 4));
|
||||
if ($EndOfCentralDirectory['signature'] != 0x06054B50) {
|
||||
// invalid End Of Central Directory Signature
|
||||
$this->fseek($EndOfCentralDirectory['offset']); // seek back to where filepointer originally was so it can be handled properly
|
||||
return false;
|
||||
}
|
||||
$EndOfCentralDirectory['disk_number_current'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 4, 2));
|
||||
$EndOfCentralDirectory['disk_number_start_directory'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 6, 2));
|
||||
$EndOfCentralDirectory['directory_entries_this_disk'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 8, 2));
|
||||
$EndOfCentralDirectory['directory_entries_total'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 10, 2));
|
||||
$EndOfCentralDirectory['directory_size'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 12, 4));
|
||||
$EndOfCentralDirectory['directory_offset'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 16, 4));
|
||||
$EndOfCentralDirectory['comment_length'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 20, 2));
|
||||
|
||||
if ($EndOfCentralDirectory['comment_length'] > 0) {
|
||||
$EndOfCentralDirectory['comment'] = $this->fread($EndOfCentralDirectory['comment_length']);
|
||||
}
|
||||
|
||||
return $EndOfCentralDirectory;
|
||||
}
|
||||
|
||||
|
||||
public static function ZIPparseGeneralPurposeFlags($flagbytes, $compressionmethod) {
|
||||
// https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip-printable.html
|
||||
$ParsedFlags['encrypted'] = (bool) ($flagbytes & 0x0001);
|
||||
// 0x0002 -- see below
|
||||
// 0x0004 -- see below
|
||||
$ParsedFlags['data_descriptor_used'] = (bool) ($flagbytes & 0x0008);
|
||||
$ParsedFlags['enhanced_deflation'] = (bool) ($flagbytes & 0x0010);
|
||||
$ParsedFlags['compressed_patched_data'] = (bool) ($flagbytes & 0x0020);
|
||||
$ParsedFlags['strong_encryption'] = (bool) ($flagbytes & 0x0040);
|
||||
// 0x0080 - unused
|
||||
// 0x0100 - unused
|
||||
// 0x0200 - unused
|
||||
// 0x0400 - unused
|
||||
$ParsedFlags['language_encoding'] = (bool) ($flagbytes & 0x0800);
|
||||
// 0x1000 - reserved
|
||||
$ParsedFlags['mask_header_values'] = (bool) ($flagbytes & 0x2000);
|
||||
// 0x4000 - reserved
|
||||
// 0x8000 - reserved
|
||||
|
||||
switch ($compressionmethod) {
|
||||
case 6:
|
||||
$ParsedFlags['dictionary_size'] = (($flagbytes & 0x0002) ? 8192 : 4096);
|
||||
$ParsedFlags['shannon_fano_trees'] = (($flagbytes & 0x0004) ? 3 : 2);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
case 9:
|
||||
switch (($flagbytes & 0x0006) >> 1) {
|
||||
case 0:
|
||||
$ParsedFlags['compression_speed'] = 'normal';
|
||||
break;
|
||||
case 1:
|
||||
$ParsedFlags['compression_speed'] = 'maximum';
|
||||
break;
|
||||
case 2:
|
||||
$ParsedFlags['compression_speed'] = 'fast';
|
||||
break;
|
||||
case 3:
|
||||
$ParsedFlags['compression_speed'] = 'superfast';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $ParsedFlags;
|
||||
}
|
||||
|
||||
|
||||
public static function ZIPversionOSLookup($index) {
|
||||
static $ZIPversionOSLookup = array(
|
||||
0 => 'MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)',
|
||||
1 => 'Amiga',
|
||||
2 => 'OpenVMS',
|
||||
3 => 'Unix',
|
||||
4 => 'VM/CMS',
|
||||
5 => 'Atari ST',
|
||||
6 => 'OS/2 H.P.F.S.',
|
||||
7 => 'Macintosh',
|
||||
8 => 'Z-System',
|
||||
9 => 'CP/M',
|
||||
10 => 'Windows NTFS',
|
||||
11 => 'MVS',
|
||||
12 => 'VSE',
|
||||
13 => 'Acorn Risc',
|
||||
14 => 'VFAT',
|
||||
15 => 'Alternate MVS',
|
||||
16 => 'BeOS',
|
||||
17 => 'Tandem',
|
||||
18 => 'OS/400',
|
||||
19 => 'OS/X (Darwin)',
|
||||
);
|
||||
|
||||
return (isset($ZIPversionOSLookup[$index]) ? $ZIPversionOSLookup[$index] : '[unknown]');
|
||||
}
|
||||
|
||||
public static function ZIPcompressionMethodLookup($index) {
|
||||
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/ZIP.html
|
||||
static $ZIPcompressionMethodLookup = array(
|
||||
0 => 'store',
|
||||
1 => 'shrink',
|
||||
2 => 'reduce-1',
|
||||
3 => 'reduce-2',
|
||||
4 => 'reduce-3',
|
||||
5 => 'reduce-4',
|
||||
6 => 'implode',
|
||||
7 => 'tokenize',
|
||||
8 => 'deflate',
|
||||
9 => 'deflate64',
|
||||
10 => 'Imploded (old IBM TERSE)',
|
||||
11 => 'RESERVED[11]',
|
||||
12 => 'BZIP2',
|
||||
13 => 'RESERVED[13]',
|
||||
14 => 'LZMA (EFS)',
|
||||
15 => 'RESERVED[15]',
|
||||
16 => 'RESERVED[16]',
|
||||
17 => 'RESERVED[17]',
|
||||
18 => 'IBM TERSE (new)',
|
||||
19 => 'IBM LZ77 z Architecture (PFS)',
|
||||
96 => 'JPEG recompressed',
|
||||
97 => 'WavPack compressed',
|
||||
98 => 'PPMd version I, Rev 1',
|
||||
);
|
||||
|
||||
return (isset($ZIPcompressionMethodLookup[$index]) ? $ZIPcompressionMethodLookup[$index] : '[unknown]');
|
||||
}
|
||||
|
||||
public static function DOStime2UNIXtime($DOSdate, $DOStime) {
|
||||
// wFatDate
|
||||
// Specifies the MS-DOS date. The date is a packed 16-bit value with the following format:
|
||||
// Bits Contents
|
||||
// 0-4 Day of the month (1-31)
|
||||
// 5-8 Month (1 = January, 2 = February, and so on)
|
||||
// 9-15 Year offset from 1980 (add 1980 to get actual year)
|
||||
|
||||
$UNIXday = ($DOSdate & 0x001F);
|
||||
$UNIXmonth = (($DOSdate & 0x01E0) >> 5);
|
||||
$UNIXyear = (($DOSdate & 0xFE00) >> 9) + 1980;
|
||||
|
||||
// wFatTime
|
||||
// Specifies the MS-DOS time. The time is a packed 16-bit value with the following format:
|
||||
// Bits Contents
|
||||
// 0-4 Second divided by 2
|
||||
// 5-10 Minute (0-59)
|
||||
// 11-15 Hour (0-23 on a 24-hour clock)
|
||||
|
||||
$UNIXsecond = ($DOStime & 0x001F) * 2;
|
||||
$UNIXminute = (($DOStime & 0x07E0) >> 5);
|
||||
$UNIXhour = (($DOStime & 0xF800) >> 11);
|
||||
|
||||
return gmmktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,745 @@
|
|||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// also https://github.com/JamesHeinrich/getID3 //
|
||||
// //
|
||||
// FLV module by Seth Kaufman <sethØwhirl-i-gig*com> //
|
||||
// //
|
||||
// * version 0.1 (26 June 2005) //
|
||||
// //
|
||||
// //
|
||||
// * version 0.1.1 (15 July 2005) //
|
||||
// minor modifications by James Heinrich <info@getid3.org> //
|
||||
// //
|
||||
// * version 0.2 (22 February 2006) //
|
||||
// Support for On2 VP6 codec and meta information //
|
||||
// by Steve Webster <steve.websterØfeaturecreep*com> //
|
||||
// //
|
||||
// * version 0.3 (15 June 2006) //
|
||||
// Modified to not read entire file into memory //
|
||||
// by James Heinrich <info@getid3.org> //
|
||||
// //
|
||||
// * version 0.4 (07 December 2007) //
|
||||
// Bugfixes for incorrectly parsed FLV dimensions //
|
||||
// and incorrect parsing of onMetaTag //
|
||||
// by Evgeny Moysevich <moysevichØgmail*com> //
|
||||
// //
|
||||
// * version 0.5 (21 May 2009) //
|
||||
// Fixed parsing of audio tags and added additional codec //
|
||||
// details. The duration is now read from onMetaTag (if //
|
||||
// exists), rather than parsing whole file //
|
||||
// by Nigel Barnes <ngbarnesØhotmail*com> //
|
||||
// //
|
||||
// * version 0.6 (24 May 2009) //
|
||||
// Better parsing of files with h264 video //
|
||||
// by Evgeny Moysevich <moysevichØgmail*com> //
|
||||
// //
|
||||
// * version 0.6.1 (30 May 2011) //
|
||||
// prevent infinite loops in expGolombUe() //
|
||||
// //
|
||||
// * version 0.7.0 (16 Jul 2013) //
|
||||
// handle GETID3_FLV_VIDEO_VP6FLV_ALPHA //
|
||||
// improved AVCSequenceParameterSetReader::readData() //
|
||||
// by Xander Schouwerwou <schouwerwouØgmail*com> //
|
||||
// //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio-video.flv.php //
|
||||
// module for analyzing Shockwave Flash Video files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
define('GETID3_FLV_TAG_AUDIO', 8);
|
||||
define('GETID3_FLV_TAG_VIDEO', 9);
|
||||
define('GETID3_FLV_TAG_META', 18);
|
||||
|
||||
define('GETID3_FLV_VIDEO_H263', 2);
|
||||
define('GETID3_FLV_VIDEO_SCREEN', 3);
|
||||
define('GETID3_FLV_VIDEO_VP6FLV', 4);
|
||||
define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5);
|
||||
define('GETID3_FLV_VIDEO_SCREENV2', 6);
|
||||
define('GETID3_FLV_VIDEO_H264', 7);
|
||||
|
||||
define('H264_AVC_SEQUENCE_HEADER', 0);
|
||||
define('H264_PROFILE_BASELINE', 66);
|
||||
define('H264_PROFILE_MAIN', 77);
|
||||
define('H264_PROFILE_EXTENDED', 88);
|
||||
define('H264_PROFILE_HIGH', 100);
|
||||
define('H264_PROFILE_HIGH10', 110);
|
||||
define('H264_PROFILE_HIGH422', 122);
|
||||
define('H264_PROFILE_HIGH444', 144);
|
||||
define('H264_PROFILE_HIGH444_PREDICTIVE', 244);
|
||||
|
||||
class getid3_flv extends getid3_handler {
|
||||
|
||||
const magic = 'FLV';
|
||||
|
||||
public $max_frames = 100000; // break out of the loop if too many frames have been scanned; only scan this many if meta frame does not contain useful duration
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$this->fseek($info['avdataoffset']);
|
||||
|
||||
$FLVdataLength = $info['avdataend'] - $info['avdataoffset'];
|
||||
$FLVheader = $this->fread(5);
|
||||
|
||||
$info['fileformat'] = 'flv';
|
||||
$info['flv']['header']['signature'] = substr($FLVheader, 0, 3);
|
||||
$info['flv']['header']['version'] = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
|
||||
$TypeFlags = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));
|
||||
|
||||
if ($info['flv']['header']['signature'] != self::magic) {
|
||||
$info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(self::magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"';
|
||||
unset($info['flv'], $info['fileformat']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
|
||||
$info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);
|
||||
|
||||
$FrameSizeDataLength = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
$FLVheaderFrameLength = 9;
|
||||
if ($FrameSizeDataLength > $FLVheaderFrameLength) {
|
||||
$this->fseek($FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
|
||||
}
|
||||
$Duration = 0;
|
||||
$found_video = false;
|
||||
$found_audio = false;
|
||||
$found_meta = false;
|
||||
$found_valid_meta_playtime = false;
|
||||
$tagParseCount = 0;
|
||||
$info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0);
|
||||
$flv_framecount = &$info['flv']['framecount'];
|
||||
while ((($this->ftell() + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime)) {
|
||||
$ThisTagHeader = $this->fread(16);
|
||||
|
||||
$PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 0, 4));
|
||||
$TagType = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 4, 1));
|
||||
$DataLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 5, 3));
|
||||
$Timestamp = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 8, 3));
|
||||
$LastHeaderByte = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
|
||||
$NextOffset = $this->ftell() - 1 + $DataLength;
|
||||
if ($Timestamp > $Duration) {
|
||||
$Duration = $Timestamp;
|
||||
}
|
||||
|
||||
$flv_framecount['total']++;
|
||||
switch ($TagType) {
|
||||
case GETID3_FLV_TAG_AUDIO:
|
||||
$flv_framecount['audio']++;
|
||||
if (!$found_audio) {
|
||||
$found_audio = true;
|
||||
$info['flv']['audio']['audioFormat'] = ($LastHeaderByte >> 4) & 0x0F;
|
||||
$info['flv']['audio']['audioRate'] = ($LastHeaderByte >> 2) & 0x03;
|
||||
$info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01;
|
||||
$info['flv']['audio']['audioType'] = $LastHeaderByte & 0x01;
|
||||
}
|
||||
break;
|
||||
|
||||
case GETID3_FLV_TAG_VIDEO:
|
||||
$flv_framecount['video']++;
|
||||
if (!$found_video) {
|
||||
$found_video = true;
|
||||
$info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
|
||||
|
||||
$FLVvideoHeader = $this->fread(11);
|
||||
|
||||
if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
|
||||
// this code block contributed by: moysevichØgmail*com
|
||||
|
||||
$AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1));
|
||||
if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) {
|
||||
// read AVCDecoderConfigurationRecord
|
||||
$configurationVersion = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 1));
|
||||
$AVCProfileIndication = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 1));
|
||||
$profile_compatibility = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 1));
|
||||
$lengthSizeMinusOne = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 1));
|
||||
$numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 8, 1));
|
||||
|
||||
if (($numOfSequenceParameterSets & 0x1F) != 0) {
|
||||
// there is at least one SequenceParameterSet
|
||||
// read size of the first SequenceParameterSet
|
||||
//$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2));
|
||||
$spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2));
|
||||
// read the first SequenceParameterSet
|
||||
$sps = $this->fread($spsSize);
|
||||
if (strlen($sps) == $spsSize) { // make sure that whole SequenceParameterSet was red
|
||||
$spsReader = new AVCSequenceParameterSetReader($sps);
|
||||
$spsReader->readData();
|
||||
$info['video']['resolution_x'] = $spsReader->getWidth();
|
||||
$info['video']['resolution_y'] = $spsReader->getHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
// end: moysevichØgmail*com
|
||||
|
||||
} elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) {
|
||||
|
||||
$PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
|
||||
$PictureSizeType = $PictureSizeType & 0x0007;
|
||||
$info['flv']['header']['videoSizeType'] = $PictureSizeType;
|
||||
switch ($PictureSizeType) {
|
||||
case 0:
|
||||
//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
|
||||
//$PictureSizeEnc <<= 1;
|
||||
//$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
|
||||
//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
|
||||
//$PictureSizeEnc <<= 1;
|
||||
//$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;
|
||||
|
||||
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2)) >> 7;
|
||||
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)) >> 7;
|
||||
$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
|
||||
$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3)) >> 7;
|
||||
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3)) >> 7;
|
||||
$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
|
||||
$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$info['video']['resolution_x'] = 352;
|
||||
$info['video']['resolution_y'] = 288;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$info['video']['resolution_x'] = 176;
|
||||
$info['video']['resolution_y'] = 144;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
$info['video']['resolution_x'] = 128;
|
||||
$info['video']['resolution_y'] = 96;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
$info['video']['resolution_x'] = 320;
|
||||
$info['video']['resolution_y'] = 240;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
$info['video']['resolution_x'] = 160;
|
||||
$info['video']['resolution_y'] = 120;
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['video']['resolution_x'] = 0;
|
||||
$info['video']['resolution_y'] = 0;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
} elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_VP6FLV_ALPHA) {
|
||||
|
||||
/* contributed by schouwerwouØgmail*com */
|
||||
if (!isset($info['video']['resolution_x'])) { // only when meta data isn't set
|
||||
$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
|
||||
$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 2));
|
||||
$info['video']['resolution_x'] = ($PictureSizeEnc['x'] & 0xFF) << 3;
|
||||
$info['video']['resolution_y'] = ($PictureSizeEnc['y'] & 0xFF) << 3;
|
||||
}
|
||||
/* end schouwerwouØgmail*com */
|
||||
|
||||
}
|
||||
if (!empty($info['video']['resolution_x']) && !empty($info['video']['resolution_y'])) {
|
||||
$info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Meta tag
|
||||
case GETID3_FLV_TAG_META:
|
||||
if (!$found_meta) {
|
||||
$found_meta = true;
|
||||
$this->fseek(-1, SEEK_CUR);
|
||||
$datachunk = $this->fread($DataLength);
|
||||
$AMFstream = new AMFStream($datachunk);
|
||||
$reader = new AMFReader($AMFstream);
|
||||
$eventName = $reader->readData();
|
||||
$info['flv']['meta'][$eventName] = $reader->readData();
|
||||
unset($reader);
|
||||
|
||||
$copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate');
|
||||
foreach ($copykeys as $sourcekey => $destkey) {
|
||||
if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) {
|
||||
switch ($sourcekey) {
|
||||
case 'width':
|
||||
case 'height':
|
||||
$info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey]));
|
||||
break;
|
||||
case 'audiodatarate':
|
||||
$info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000));
|
||||
break;
|
||||
case 'videodatarate':
|
||||
case 'frame_rate':
|
||||
default:
|
||||
$info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
|
||||
$found_valid_meta_playtime = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// noop
|
||||
break;
|
||||
}
|
||||
$this->fseek($NextOffset);
|
||||
}
|
||||
|
||||
$info['playtime_seconds'] = $Duration / 1000;
|
||||
if ($info['playtime_seconds'] > 0) {
|
||||
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
}
|
||||
|
||||
if ($info['flv']['header']['hasAudio']) {
|
||||
$info['audio']['codec'] = self::audioFormatLookup($info['flv']['audio']['audioFormat']);
|
||||
$info['audio']['sample_rate'] = self::audioRateLookup($info['flv']['audio']['audioRate']);
|
||||
$info['audio']['bits_per_sample'] = self::audioBitDepthLookup($info['flv']['audio']['audioSampleSize']);
|
||||
|
||||
$info['audio']['channels'] = $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
|
||||
$info['audio']['lossless'] = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
|
||||
$info['audio']['dataformat'] = 'flv';
|
||||
}
|
||||
if (!empty($info['flv']['header']['hasVideo'])) {
|
||||
$info['video']['codec'] = self::videoCodecLookup($info['flv']['video']['videoCodec']);
|
||||
$info['video']['dataformat'] = 'flv';
|
||||
$info['video']['lossless'] = false;
|
||||
}
|
||||
|
||||
// Set information from meta
|
||||
if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
|
||||
$info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration'];
|
||||
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
}
|
||||
if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) {
|
||||
$info['audio']['codec'] = self::audioFormatLookup($info['flv']['meta']['onMetaData']['audiocodecid']);
|
||||
}
|
||||
if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) {
|
||||
$info['video']['codec'] = self::videoCodecLookup($info['flv']['meta']['onMetaData']['videocodecid']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static function audioFormatLookup($id) {
|
||||
static $lookup = array(
|
||||
0 => 'Linear PCM, platform endian',
|
||||
1 => 'ADPCM',
|
||||
2 => 'mp3',
|
||||
3 => 'Linear PCM, little endian',
|
||||
4 => 'Nellymoser 16kHz mono',
|
||||
5 => 'Nellymoser 8kHz mono',
|
||||
6 => 'Nellymoser',
|
||||
7 => 'G.711A-law logarithmic PCM',
|
||||
8 => 'G.711 mu-law logarithmic PCM',
|
||||
9 => 'reserved',
|
||||
10 => 'AAC',
|
||||
11 => 'Speex',
|
||||
12 => false, // unknown?
|
||||
13 => false, // unknown?
|
||||
14 => 'mp3 8kHz',
|
||||
15 => 'Device-specific sound',
|
||||
);
|
||||
return (isset($lookup[$id]) ? $lookup[$id] : false);
|
||||
}
|
||||
|
||||
public static function audioRateLookup($id) {
|
||||
static $lookup = array(
|
||||
0 => 5500,
|
||||
1 => 11025,
|
||||
2 => 22050,
|
||||
3 => 44100,
|
||||
);
|
||||
return (isset($lookup[$id]) ? $lookup[$id] : false);
|
||||
}
|
||||
|
||||
public static function audioBitDepthLookup($id) {
|
||||
static $lookup = array(
|
||||
0 => 8,
|
||||
1 => 16,
|
||||
);
|
||||
return (isset($lookup[$id]) ? $lookup[$id] : false);
|
||||
}
|
||||
|
||||
public static function videoCodecLookup($id) {
|
||||
static $lookup = array(
|
||||
GETID3_FLV_VIDEO_H263 => 'Sorenson H.263',
|
||||
GETID3_FLV_VIDEO_SCREEN => 'Screen video',
|
||||
GETID3_FLV_VIDEO_VP6FLV => 'On2 VP6',
|
||||
GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel',
|
||||
GETID3_FLV_VIDEO_SCREENV2 => 'Screen video v2',
|
||||
GETID3_FLV_VIDEO_H264 => 'Sorenson H.264',
|
||||
);
|
||||
return (isset($lookup[$id]) ? $lookup[$id] : false);
|
||||
}
|
||||
}
|
||||
|
||||
class AMFStream {
|
||||
public $bytes;
|
||||
public $pos;
|
||||
|
||||
public function __construct(&$bytes) {
|
||||
$this->bytes =& $bytes;
|
||||
$this->pos = 0;
|
||||
}
|
||||
|
||||
public function readByte() {
|
||||
return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
|
||||
}
|
||||
|
||||
public function readInt() {
|
||||
return ($this->readByte() << 8) + $this->readByte();
|
||||
}
|
||||
|
||||
public function readLong() {
|
||||
return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
|
||||
}
|
||||
|
||||
public function readDouble() {
|
||||
return getid3_lib::BigEndian2Float($this->read(8));
|
||||
}
|
||||
|
||||
public function readUTF() {
|
||||
$length = $this->readInt();
|
||||
return $this->read($length);
|
||||
}
|
||||
|
||||
public function readLongUTF() {
|
||||
$length = $this->readLong();
|
||||
return $this->read($length);
|
||||
}
|
||||
|
||||
public function read($length) {
|
||||
$val = substr($this->bytes, $this->pos, $length);
|
||||
$this->pos += $length;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekByte() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readByte();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekInt() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readInt();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekLong() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readLong();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekDouble() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readDouble();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekUTF() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readUTF();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
|
||||
public function peekLongUTF() {
|
||||
$pos = $this->pos;
|
||||
$val = $this->readLongUTF();
|
||||
$this->pos = $pos;
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
|
||||
class AMFReader {
|
||||
public $stream;
|
||||
|
||||
public function __construct(&$stream) {
|
||||
$this->stream =& $stream;
|
||||
}
|
||||
|
||||
public function readData() {
|
||||
$value = null;
|
||||
|
||||
$type = $this->stream->readByte();
|
||||
switch ($type) {
|
||||
|
||||
// Double
|
||||
case 0:
|
||||
$value = $this->readDouble();
|
||||
break;
|
||||
|
||||
// Boolean
|
||||
case 1:
|
||||
$value = $this->readBoolean();
|
||||
break;
|
||||
|
||||
// String
|
||||
case 2:
|
||||
$value = $this->readString();
|
||||
break;
|
||||
|
||||
// Object
|
||||
case 3:
|
||||
$value = $this->readObject();
|
||||
break;
|
||||
|
||||
// null
|
||||
case 6:
|
||||
return null;
|
||||
break;
|
||||
|
||||
// Mixed array
|
||||
case 8:
|
||||
$value = $this->readMixedArray();
|
||||
break;
|
||||
|
||||
// Array
|
||||
case 10:
|
||||
$value = $this->readArray();
|
||||
break;
|
||||
|
||||
// Date
|
||||
case 11:
|
||||
$value = $this->readDate();
|
||||
break;
|
||||
|
||||
// Long string
|
||||
case 13:
|
||||
$value = $this->readLongString();
|
||||
break;
|
||||
|
||||
// XML (handled as string)
|
||||
case 15:
|
||||
$value = $this->readXML();
|
||||
break;
|
||||
|
||||
// Typed object (handled as object)
|
||||
case 16:
|
||||
$value = $this->readTypedObject();
|
||||
break;
|
||||
|
||||
// Long string
|
||||
default:
|
||||
$value = '(unknown or unsupported data type)';
|
||||
break;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function readDouble() {
|
||||
return $this->stream->readDouble();
|
||||
}
|
||||
|
||||
public function readBoolean() {
|
||||
return $this->stream->readByte() == 1;
|
||||
}
|
||||
|
||||
public function readString() {
|
||||
return $this->stream->readUTF();
|
||||
}
|
||||
|
||||
public function readObject() {
|
||||
// Get highest numerical index - ignored
|
||||
// $highestIndex = $this->stream->readLong();
|
||||
|
||||
$data = array();
|
||||
|
||||
while ($key = $this->stream->readUTF()) {
|
||||
$data[$key] = $this->readData();
|
||||
}
|
||||
// Mixed array record ends with empty string (0x00 0x00) and 0x09
|
||||
if (($key == '') && ($this->stream->peekByte() == 0x09)) {
|
||||
// Consume byte
|
||||
$this->stream->readByte();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function readMixedArray() {
|
||||
// Get highest numerical index - ignored
|
||||
$highestIndex = $this->stream->readLong();
|
||||
|
||||
$data = array();
|
||||
|
||||
while ($key = $this->stream->readUTF()) {
|
||||
if (is_numeric($key)) {
|
||||
$key = (float) $key;
|
||||
}
|
||||
$data[$key] = $this->readData();
|
||||
}
|
||||
// Mixed array record ends with empty string (0x00 0x00) and 0x09
|
||||
if (($key == '') && ($this->stream->peekByte() == 0x09)) {
|
||||
// Consume byte
|
||||
$this->stream->readByte();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function readArray() {
|
||||
$length = $this->stream->readLong();
|
||||
$data = array();
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$data[] = $this->readData();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function readDate() {
|
||||
$timestamp = $this->stream->readDouble();
|
||||
$timezone = $this->stream->readInt();
|
||||
return $timestamp;
|
||||
}
|
||||
|
||||
public function readLongString() {
|
||||
return $this->stream->readLongUTF();
|
||||
}
|
||||
|
||||
public function readXML() {
|
||||
return $this->stream->readLongUTF();
|
||||
}
|
||||
|
||||
public function readTypedObject() {
|
||||
$className = $this->stream->readUTF();
|
||||
return $this->readObject();
|
||||
}
|
||||
}
|
||||
|
||||
class AVCSequenceParameterSetReader {
|
||||
public $sps;
|
||||
public $start = 0;
|
||||
public $currentBytes = 0;
|
||||
public $currentBits = 0;
|
||||
public $width;
|
||||
public $height;
|
||||
|
||||
public function __construct($sps) {
|
||||
$this->sps = $sps;
|
||||
}
|
||||
|
||||
public function readData() {
|
||||
$this->skipBits(8);
|
||||
$this->skipBits(8);
|
||||
$profile = $this->getBits(8); // read profile
|
||||
if ($profile > 0) {
|
||||
$this->skipBits(8);
|
||||
$level_idc = $this->getBits(8); // level_idc
|
||||
$this->expGolombUe(); // seq_parameter_set_id // sps
|
||||
$this->expGolombUe(); // log2_max_frame_num_minus4
|
||||
$picOrderType = $this->expGolombUe(); // pic_order_cnt_type
|
||||
if ($picOrderType == 0) {
|
||||
$this->expGolombUe(); // log2_max_pic_order_cnt_lsb_minus4
|
||||
} elseif ($picOrderType == 1) {
|
||||
$this->skipBits(1); // delta_pic_order_always_zero_flag
|
||||
$this->expGolombSe(); // offset_for_non_ref_pic
|
||||
$this->expGolombSe(); // offset_for_top_to_bottom_field
|
||||
$num_ref_frames_in_pic_order_cnt_cycle = $this->expGolombUe(); // num_ref_frames_in_pic_order_cnt_cycle
|
||||
for ($i = 0; $i < $num_ref_frames_in_pic_order_cnt_cycle; $i++) {
|
||||
$this->expGolombSe(); // offset_for_ref_frame[ i ]
|
||||
}
|
||||
}
|
||||
$this->expGolombUe(); // num_ref_frames
|
||||
$this->skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
||||
$pic_width_in_mbs_minus1 = $this->expGolombUe(); // pic_width_in_mbs_minus1
|
||||
$pic_height_in_map_units_minus1 = $this->expGolombUe(); // pic_height_in_map_units_minus1
|
||||
|
||||
$frame_mbs_only_flag = $this->getBits(1); // frame_mbs_only_flag
|
||||
if ($frame_mbs_only_flag == 0) {
|
||||
$this->skipBits(1); // mb_adaptive_frame_field_flag
|
||||
}
|
||||
$this->skipBits(1); // direct_8x8_inference_flag
|
||||
$frame_cropping_flag = $this->getBits(1); // frame_cropping_flag
|
||||
|
||||
$frame_crop_left_offset = 0;
|
||||
$frame_crop_right_offset = 0;
|
||||
$frame_crop_top_offset = 0;
|
||||
$frame_crop_bottom_offset = 0;
|
||||
|
||||
if ($frame_cropping_flag) {
|
||||
$frame_crop_left_offset = $this->expGolombUe(); // frame_crop_left_offset
|
||||
$frame_crop_right_offset = $this->expGolombUe(); // frame_crop_right_offset
|
||||
$frame_crop_top_offset = $this->expGolombUe(); // frame_crop_top_offset
|
||||
$frame_crop_bottom_offset = $this->expGolombUe(); // frame_crop_bottom_offset
|
||||
}
|
||||
$this->skipBits(1); // vui_parameters_present_flag
|
||||
// etc
|
||||
|
||||
$this->width = (($pic_width_in_mbs_minus1 + 1) * 16) - ($frame_crop_left_offset * 2) - ($frame_crop_right_offset * 2);
|
||||
$this->height = ((2 - $frame_mbs_only_flag) * ($pic_height_in_map_units_minus1 + 1) * 16) - ($frame_crop_top_offset * 2) - ($frame_crop_bottom_offset * 2);
|
||||
}
|
||||
}
|
||||
|
||||
public function skipBits($bits) {
|
||||
$newBits = $this->currentBits + $bits;
|
||||
$this->currentBytes += (int)floor($newBits / 8);
|
||||
$this->currentBits = $newBits % 8;
|
||||
}
|
||||
|
||||
public function getBit() {
|
||||
$result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01;
|
||||
$this->skipBits(1);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getBits($bits) {
|
||||
$result = 0;
|
||||
for ($i = 0; $i < $bits; $i++) {
|
||||
$result = ($result << 1) + $this->getBit();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function expGolombUe() {
|
||||
$significantBits = 0;
|
||||
$bit = $this->getBit();
|
||||
while ($bit == 0) {
|
||||
$significantBits++;
|
||||
$bit = $this->getBit();
|
||||
|
||||
if ($significantBits > 31) {
|
||||
// something is broken, this is an emergency escape to prevent infinite loops
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return (1 << $significantBits) + $this->getBits($significantBits) - 1;
|
||||
}
|
||||
|
||||
public function expGolombSe() {
|
||||
$result = $this->expGolombUe();
|
||||
if (($result & 0x01) == 0) {
|
||||
return -($result >> 1);
|
||||
} else {
|
||||
return ($result + 1) >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
public function getWidth() {
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
public function getHeight() {
|
||||
return $this->height;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,606 @@
|
|||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// also https://github.com/JamesHeinrich/getID3 //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio-video.mpeg.php //
|
||||
// module for analyzing MPEG files //
|
||||
// dependencies: module.audio.mp3.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true);
|
||||
|
||||
class getid3_mpeg extends getid3_handler {
|
||||
|
||||
const START_CODE_BASE = "\x00\x00\x01";
|
||||
const VIDEO_PICTURE_START = "\x00\x00\x01\x00";
|
||||
const VIDEO_USER_DATA_START = "\x00\x00\x01\xB2";
|
||||
const VIDEO_SEQUENCE_HEADER = "\x00\x00\x01\xB3";
|
||||
const VIDEO_SEQUENCE_ERROR = "\x00\x00\x01\xB4";
|
||||
const VIDEO_EXTENSION_START = "\x00\x00\x01\xB5";
|
||||
const VIDEO_SEQUENCE_END = "\x00\x00\x01\xB7";
|
||||
const VIDEO_GROUP_START = "\x00\x00\x01\xB8";
|
||||
const AUDIO_START = "\x00\x00\x01\xC0";
|
||||
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'mpeg';
|
||||
$this->fseek($info['avdataoffset']);
|
||||
|
||||
$MPEGstreamData = $this->fread($this->getid3->option_fread_buffer_size);
|
||||
$MPEGstreamBaseOffset = 0; // how far are we from the beginning of the file data ($info['avdataoffset'])
|
||||
$MPEGstreamDataOffset = 0; // how far are we from the beginning of the buffer data (~32kB)
|
||||
|
||||
$StartCodeValue = false;
|
||||
$prevStartCodeValue = false;
|
||||
|
||||
$GOPcounter = -1;
|
||||
$FramesByGOP = array();
|
||||
$ParsedAVchannels = array();
|
||||
|
||||
do {
|
||||
//echo $MPEGstreamDataOffset.' vs '.(strlen($MPEGstreamData) - 1024).'<Br>';
|
||||
if ($MPEGstreamDataOffset > (strlen($MPEGstreamData) - 16384)) {
|
||||
// buffer running low, get more data
|
||||
//echo 'reading more data<br>';
|
||||
$MPEGstreamData .= $this->fread($this->getid3->option_fread_buffer_size);
|
||||
if (strlen($MPEGstreamData) > $this->getid3->option_fread_buffer_size) {
|
||||
$MPEGstreamData = substr($MPEGstreamData, $MPEGstreamDataOffset);
|
||||
$MPEGstreamBaseOffset += $MPEGstreamDataOffset;
|
||||
$MPEGstreamDataOffset = 0;
|
||||
}
|
||||
}
|
||||
if (($StartCodeOffset = strpos($MPEGstreamData, self::START_CODE_BASE, $MPEGstreamDataOffset)) === false) {
|
||||
//echo 'no more start codes found.<br>';
|
||||
break;
|
||||
} else {
|
||||
$MPEGstreamDataOffset = $StartCodeOffset;
|
||||
$prevStartCodeValue = $StartCodeValue;
|
||||
$StartCodeValue = ord(substr($MPEGstreamData, $StartCodeOffset + 3, 1));
|
||||
//echo 'Found "'.strtoupper(dechex($StartCodeValue)).'" at offset '.($MPEGstreamBaseOffset + $StartCodeOffset).' ($MPEGstreamDataOffset = '.$MPEGstreamDataOffset.')<br>';
|
||||
}
|
||||
$MPEGstreamDataOffset += 4;
|
||||
switch ($StartCodeValue) {
|
||||
|
||||
case 0x00: // picture_start_code
|
||||
if (!empty($info['mpeg']['video']['bitrate_mode']) && ($info['mpeg']['video']['bitrate_mode'] == 'vbr')) {
|
||||
$bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 4));
|
||||
$bitstreamoffset = 0;
|
||||
|
||||
$PictureHeader = array();
|
||||
|
||||
$PictureHeader['temporal_reference'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 10); // 10-bit unsigned integer associated with each input picture. It is incremented by one, modulo 1024, for each input frame. When a frame is coded as two fields the temporal reference in the picture header of both fields is the same. Following a group start header the temporal reference of the earliest picture (in display order) shall be reset to zero.
|
||||
$PictureHeader['picture_coding_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_coding_type
|
||||
$PictureHeader['vbv_delay'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 16); // 16 bits for vbv_delay
|
||||
//... etc
|
||||
|
||||
$FramesByGOP[$GOPcounter][] = $PictureHeader;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xB3: // sequence_header_code
|
||||
/*
|
||||
Note: purposely doing the less-pretty (and probably a bit slower) method of using string of bits rather than bitwise operations.
|
||||
Mostly because PHP 32-bit doesn't handle unsigned integers well for bitwise operation.
|
||||
Also the MPEG stream is designed as a bitstream and often doesn't align nicely with byte boundaries.
|
||||
*/
|
||||
$info['video']['codec'] = 'MPEG-1'; // will be updated if extension_start_code found
|
||||
|
||||
$bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 8));
|
||||
$bitstreamoffset = 0;
|
||||
|
||||
$info['mpeg']['video']['raw']['horizontal_size_value'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for horizontal frame size. Note: horizontal_size_extension, if present, will add 2 most-significant bits to this value
|
||||
$info['mpeg']['video']['raw']['vertical_size_value'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for vertical frame size. Note: vertical_size_extension, if present, will add 2 most-significant bits to this value
|
||||
$info['mpeg']['video']['raw']['aspect_ratio_information'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for aspect_ratio_information
|
||||
$info['mpeg']['video']['raw']['frame_rate_code'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for Frame Rate id code
|
||||
$info['mpeg']['video']['raw']['bitrate'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 18); // 18 bits for bit_rate_value (18 set bits = VBR, otherwise bitrate = this value * 400)
|
||||
$marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation.
|
||||
$info['mpeg']['video']['raw']['vbv_buffer_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 10); // 10 bits vbv_buffer_size_value
|
||||
$info['mpeg']['video']['raw']['constrained_param_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: constrained_param_flag
|
||||
$info['mpeg']['video']['raw']['load_intra_quantiser_matrix'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: load_intra_quantiser_matrix
|
||||
|
||||
if ($info['mpeg']['video']['raw']['load_intra_quantiser_matrix']) {
|
||||
$bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 12, 64));
|
||||
for ($i = 0; $i < 64; $i++) {
|
||||
$info['mpeg']['video']['raw']['intra_quantiser_matrix'][$i] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8);
|
||||
}
|
||||
}
|
||||
$info['mpeg']['video']['raw']['load_non_intra_quantiser_matrix'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1);
|
||||
|
||||
if ($info['mpeg']['video']['raw']['load_non_intra_quantiser_matrix']) {
|
||||
$bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 12 + ($info['mpeg']['video']['raw']['load_intra_quantiser_matrix'] ? 64 : 0), 64));
|
||||
for ($i = 0; $i < 64; $i++) {
|
||||
$info['mpeg']['video']['raw']['non_intra_quantiser_matrix'][$i] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8);
|
||||
}
|
||||
}
|
||||
|
||||
$info['mpeg']['video']['pixel_aspect_ratio'] = self::videoAspectRatioLookup($info['mpeg']['video']['raw']['aspect_ratio_information']);
|
||||
$info['mpeg']['video']['pixel_aspect_ratio_text'] = self::videoAspectRatioTextLookup($info['mpeg']['video']['raw']['aspect_ratio_information']);
|
||||
$info['mpeg']['video']['frame_rate'] = self::videoFramerateLookup($info['mpeg']['video']['raw']['frame_rate_code']);
|
||||
if ($info['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits = VBR
|
||||
//$this->warning('This version of getID3() ['.$this->getid3->version().'] cannot determine average bitrate of VBR MPEG video files');
|
||||
$info['mpeg']['video']['bitrate_mode'] = 'vbr';
|
||||
} else {
|
||||
$info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400;
|
||||
$info['mpeg']['video']['bitrate_mode'] = 'cbr';
|
||||
$info['video']['bitrate'] = $info['mpeg']['video']['bitrate'];
|
||||
}
|
||||
$info['video']['resolution_x'] = $info['mpeg']['video']['raw']['horizontal_size_value'];
|
||||
$info['video']['resolution_y'] = $info['mpeg']['video']['raw']['vertical_size_value'];
|
||||
$info['video']['frame_rate'] = $info['mpeg']['video']['frame_rate'];
|
||||
$info['video']['bitrate_mode'] = $info['mpeg']['video']['bitrate_mode'];
|
||||
$info['video']['pixel_aspect_ratio'] = $info['mpeg']['video']['pixel_aspect_ratio'];
|
||||
$info['video']['lossless'] = false;
|
||||
$info['video']['bits_per_sample'] = 24;
|
||||
break;
|
||||
|
||||
case 0xB5: // extension_start_code
|
||||
$info['video']['codec'] = 'MPEG-2';
|
||||
|
||||
$bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 8)); // 48 bits for Sequence Extension ID; 61 bits for Sequence Display Extension ID; 59 bits for Sequence Scalable Extension ID
|
||||
$bitstreamoffset = 0;
|
||||
|
||||
$info['mpeg']['video']['raw']['extension_start_code_identifier'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for extension_start_code_identifier
|
||||
//echo $info['mpeg']['video']['raw']['extension_start_code_identifier'].'<br>';
|
||||
switch ($info['mpeg']['video']['raw']['extension_start_code_identifier']) {
|
||||
case 1: // 0001 Sequence Extension ID
|
||||
$info['mpeg']['video']['raw']['profile_and_level_indication'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for profile_and_level_indication
|
||||
$info['mpeg']['video']['raw']['progressive_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: progressive_sequence
|
||||
$info['mpeg']['video']['raw']['chroma_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for chroma_format
|
||||
$info['mpeg']['video']['raw']['horizontal_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for horizontal_size_extension
|
||||
$info['mpeg']['video']['raw']['vertical_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for vertical_size_extension
|
||||
$info['mpeg']['video']['raw']['bit_rate_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 12); // 12 bits for bit_rate_extension
|
||||
$marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation.
|
||||
$info['mpeg']['video']['raw']['vbv_buffer_size_extension'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for vbv_buffer_size_extension
|
||||
$info['mpeg']['video']['raw']['low_delay'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: low_delay
|
||||
$info['mpeg']['video']['raw']['frame_rate_extension_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for frame_rate_extension_n
|
||||
$info['mpeg']['video']['raw']['frame_rate_extension_d'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for frame_rate_extension_d
|
||||
|
||||
$info['video']['resolution_x'] = ($info['mpeg']['video']['raw']['horizontal_size_extension'] << 12) | $info['mpeg']['video']['raw']['horizontal_size_value'];
|
||||
$info['video']['resolution_y'] = ($info['mpeg']['video']['raw']['vertical_size_extension'] << 12) | $info['mpeg']['video']['raw']['vertical_size_value'];
|
||||
$info['video']['interlaced'] = !$info['mpeg']['video']['raw']['progressive_sequence'];
|
||||
$info['mpeg']['video']['interlaced'] = !$info['mpeg']['video']['raw']['progressive_sequence'];
|
||||
$info['mpeg']['video']['chroma_format'] = self::chromaFormatTextLookup($info['mpeg']['video']['raw']['chroma_format']);
|
||||
break;
|
||||
|
||||
case 2: // 0010 Sequence Display Extension ID
|
||||
$info['mpeg']['video']['raw']['video_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for video_format
|
||||
$info['mpeg']['video']['raw']['colour_description'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: colour_description
|
||||
if ($info['mpeg']['video']['raw']['colour_description']) {
|
||||
$info['mpeg']['video']['raw']['colour_primaries'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for colour_primaries
|
||||
$info['mpeg']['video']['raw']['transfer_characteristics'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for transfer_characteristics
|
||||
$info['mpeg']['video']['raw']['matrix_coefficients'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for matrix_coefficients
|
||||
}
|
||||
$info['mpeg']['video']['raw']['display_horizontal_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for display_horizontal_size
|
||||
$marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation.
|
||||
$info['mpeg']['video']['raw']['display_vertical_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for display_vertical_size
|
||||
|
||||
$info['mpeg']['video']['video_format'] = self::videoFormatTextLookup($info['mpeg']['video']['raw']['video_format']);
|
||||
break;
|
||||
|
||||
case 3: // 0011 Quant Matrix Extension ID
|
||||
break;
|
||||
|
||||
case 5: // 0101 Sequence Scalable Extension ID
|
||||
$info['mpeg']['video']['raw']['scalable_mode'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for scalable_mode
|
||||
$info['mpeg']['video']['raw']['layer_id'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for layer_id
|
||||
if ($info['mpeg']['video']['raw']['scalable_mode'] == 1) { // "spatial scalability"
|
||||
$info['mpeg']['video']['raw']['lower_layer_prediction_horizontal_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for lower_layer_prediction_horizontal_size
|
||||
$marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation.
|
||||
$info['mpeg']['video']['raw']['lower_layer_prediction_vertical_size'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 14); // 14 bits for lower_layer_prediction_vertical_size
|
||||
$info['mpeg']['video']['raw']['horizontal_subsampling_factor_m'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for horizontal_subsampling_factor_m
|
||||
$info['mpeg']['video']['raw']['horizontal_subsampling_factor_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for horizontal_subsampling_factor_n
|
||||
$info['mpeg']['video']['raw']['vertical_subsampling_factor_m'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for vertical_subsampling_factor_m
|
||||
$info['mpeg']['video']['raw']['vertical_subsampling_factor_n'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for vertical_subsampling_factor_n
|
||||
} elseif ($info['mpeg']['video']['raw']['scalable_mode'] == 3) { // "temporal scalability"
|
||||
$info['mpeg']['video']['raw']['picture_mux_enable'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: picture_mux_enable
|
||||
if ($info['mpeg']['video']['raw']['picture_mux_enable']) {
|
||||
$info['mpeg']['video']['raw']['mux_to_progressive_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: mux_to_progressive_sequence
|
||||
}
|
||||
$info['mpeg']['video']['raw']['picture_mux_order'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_mux_order
|
||||
$info['mpeg']['video']['raw']['picture_mux_factor'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for picture_mux_factor
|
||||
}
|
||||
|
||||
$info['mpeg']['video']['scalable_mode'] = self::scalableModeTextLookup($info['mpeg']['video']['raw']['scalable_mode']);
|
||||
break;
|
||||
|
||||
case 7: // 0111 Picture Display Extension ID
|
||||
break;
|
||||
|
||||
case 8: // 1000 Picture Coding Extension ID
|
||||
$info['mpeg']['video']['raw']['f_code_00'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[0][0] (forward horizontal)
|
||||
$info['mpeg']['video']['raw']['f_code_01'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[0][1] (forward vertical)
|
||||
$info['mpeg']['video']['raw']['f_code_10'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[1][0] (backward horizontal)
|
||||
$info['mpeg']['video']['raw']['f_code_11'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 4); // 4 bits for f_code[1][1] (backward vertical)
|
||||
$info['mpeg']['video']['raw']['intra_dc_precision'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for intra_dc_precision
|
||||
$info['mpeg']['video']['raw']['picture_structure'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for picture_structure
|
||||
$info['mpeg']['video']['raw']['top_field_first'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: top_field_first
|
||||
$info['mpeg']['video']['raw']['frame_pred_frame_dct'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: frame_pred_frame_dct
|
||||
$info['mpeg']['video']['raw']['concealment_motion_vectors'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: concealment_motion_vectors
|
||||
$info['mpeg']['video']['raw']['q_scale_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: q_scale_type
|
||||
$info['mpeg']['video']['raw']['intra_vlc_format'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: intra_vlc_format
|
||||
$info['mpeg']['video']['raw']['alternate_scan'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: alternate_scan
|
||||
$info['mpeg']['video']['raw']['repeat_first_field'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: repeat_first_field
|
||||
$info['mpeg']['video']['raw']['chroma_420_type'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: chroma_420_type
|
||||
$info['mpeg']['video']['raw']['progressive_frame'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: progressive_frame
|
||||
$info['mpeg']['video']['raw']['composite_display_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: composite_display_flag
|
||||
if ($info['mpeg']['video']['raw']['composite_display_flag']) {
|
||||
$info['mpeg']['video']['raw']['v_axis'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: v_axis
|
||||
$info['mpeg']['video']['raw']['field_sequence'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 3); // 3 bits for field_sequence
|
||||
$info['mpeg']['video']['raw']['sub_carrier'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: sub_carrier
|
||||
$info['mpeg']['video']['raw']['burst_amplitude'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 7); // 7 bits for burst_amplitude
|
||||
$info['mpeg']['video']['raw']['sub_carrier_phase'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 8 bits for sub_carrier_phase
|
||||
}
|
||||
|
||||
$info['mpeg']['video']['intra_dc_precision_bits'] = $info['mpeg']['video']['raw']['intra_dc_precision'] + 8;
|
||||
$info['mpeg']['video']['picture_structure'] = self::pictureStructureTextLookup($info['mpeg']['video']['raw']['picture_structure']);
|
||||
break;
|
||||
|
||||
case 9: // 1001 Picture Spatial Scalable Extension ID
|
||||
break;
|
||||
case 10: // 1010 Picture Temporal Scalable Extension ID
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->warning('Unexpected $info[mpeg][video][raw][extension_start_code_identifier] value of '.$info['mpeg']['video']['raw']['extension_start_code_identifier']);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 0xB8: // group_of_pictures_header
|
||||
$GOPcounter++;
|
||||
if ($info['mpeg']['video']['bitrate_mode'] == 'vbr') {
|
||||
$bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 4, 4)); // 27 bits needed for group_of_pictures_header
|
||||
$bitstreamoffset = 0;
|
||||
|
||||
$GOPheader = array();
|
||||
|
||||
$GOPheader['byte_offset'] = $MPEGstreamBaseOffset + $StartCodeOffset;
|
||||
$GOPheader['drop_frame_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: drop_frame_flag
|
||||
$GOPheader['time_code_hours'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 5); // 5 bits for time_code_hours
|
||||
$GOPheader['time_code_minutes'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_minutes
|
||||
$marker_bit = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // The term "marker_bit" indicates a one bit field in which the value zero is forbidden. These marker bits are introduced at several points in the syntax to avoid start code emulation.
|
||||
$GOPheader['time_code_seconds'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_seconds
|
||||
$GOPheader['time_code_pictures'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 6); // 6 bits for time_code_pictures
|
||||
$GOPheader['closed_gop'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: closed_gop
|
||||
$GOPheader['broken_link'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: broken_link
|
||||
|
||||
$time_code_separator = ($GOPheader['drop_frame_flag'] ? ';' : ':'); // While non-drop time code is displayed with colons separating the digit pairs—"HH:MM:SS:FF"—drop frame is usually represented with a semi-colon (;) or period (.) as the divider between all the digit pairs—"HH;MM;SS;FF", "HH.MM.SS.FF"
|
||||
$GOPheader['time_code'] = sprintf('%02d'.$time_code_separator.'%02d'.$time_code_separator.'%02d'.$time_code_separator.'%02d', $GOPheader['time_code_hours'], $GOPheader['time_code_minutes'], $GOPheader['time_code_seconds'], $GOPheader['time_code_pictures']);
|
||||
|
||||
$info['mpeg']['group_of_pictures'][] = $GOPheader;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xC0: // audio stream
|
||||
case 0xC1: // audio stream
|
||||
case 0xC2: // audio stream
|
||||
case 0xC3: // audio stream
|
||||
case 0xC4: // audio stream
|
||||
case 0xC5: // audio stream
|
||||
case 0xC6: // audio stream
|
||||
case 0xC7: // audio stream
|
||||
case 0xC8: // audio stream
|
||||
case 0xC9: // audio stream
|
||||
case 0xCA: // audio stream
|
||||
case 0xCB: // audio stream
|
||||
case 0xCC: // audio stream
|
||||
case 0xCD: // audio stream
|
||||
case 0xCE: // audio stream
|
||||
case 0xCF: // audio stream
|
||||
case 0xD0: // audio stream
|
||||
case 0xD1: // audio stream
|
||||
case 0xD2: // audio stream
|
||||
case 0xD3: // audio stream
|
||||
case 0xD4: // audio stream
|
||||
case 0xD5: // audio stream
|
||||
case 0xD6: // audio stream
|
||||
case 0xD7: // audio stream
|
||||
case 0xD8: // audio stream
|
||||
case 0xD9: // audio stream
|
||||
case 0xDA: // audio stream
|
||||
case 0xDB: // audio stream
|
||||
case 0xDC: // audio stream
|
||||
case 0xDD: // audio stream
|
||||
case 0xDE: // audio stream
|
||||
case 0xDF: // audio stream
|
||||
//case 0xE0: // video stream
|
||||
//case 0xE1: // video stream
|
||||
//case 0xE2: // video stream
|
||||
//case 0xE3: // video stream
|
||||
//case 0xE4: // video stream
|
||||
//case 0xE5: // video stream
|
||||
//case 0xE6: // video stream
|
||||
//case 0xE7: // video stream
|
||||
//case 0xE8: // video stream
|
||||
//case 0xE9: // video stream
|
||||
//case 0xEA: // video stream
|
||||
//case 0xEB: // video stream
|
||||
//case 0xEC: // video stream
|
||||
//case 0xED: // video stream
|
||||
//case 0xEE: // video stream
|
||||
//case 0xEF: // video stream
|
||||
if (isset($ParsedAVchannels[$StartCodeValue])) {
|
||||
break;
|
||||
}
|
||||
$ParsedAVchannels[$StartCodeValue] = $StartCodeValue;
|
||||
// http://en.wikipedia.org/wiki/Packetized_elementary_stream
|
||||
// http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
|
||||
/*
|
||||
$PackedElementaryStream = array();
|
||||
if ($StartCodeValue >= 0xE0) {
|
||||
$PackedElementaryStream['stream_type'] = 'video';
|
||||
$PackedElementaryStream['stream_id'] = $StartCodeValue - 0xE0;
|
||||
} else {
|
||||
$PackedElementaryStream['stream_type'] = 'audio';
|
||||
$PackedElementaryStream['stream_id'] = $StartCodeValue - 0xC0;
|
||||
}
|
||||
$PackedElementaryStream['packet_length'] = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $StartCodeOffset + 4, 2));
|
||||
|
||||
$bitstream = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 6, 3)); // more may be needed below
|
||||
$bitstreamoffset = 0;
|
||||
|
||||
$PackedElementaryStream['marker_bits'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for marker_bits -- should be "10" = 2
|
||||
echo 'marker_bits = '.$PackedElementaryStream['marker_bits'].'<br>';
|
||||
$PackedElementaryStream['scrambling_control'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 2); // 2 bits for scrambling_control -- 00 implies not scrambled
|
||||
$PackedElementaryStream['priority'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: priority
|
||||
$PackedElementaryStream['data_alignment_indicator'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: data_alignment_indicator -- 1 indicates that the PES packet header is immediately followed by the video start code or audio syncword
|
||||
$PackedElementaryStream['copyright'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: copyright -- 1 implies copyrighted
|
||||
$PackedElementaryStream['original_or_copy'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: original_or_copy -- 1 implies original
|
||||
$PackedElementaryStream['pts_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: pts_flag -- Presentation Time Stamp
|
||||
$PackedElementaryStream['dts_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: dts_flag -- Decode Time Stamp
|
||||
$PackedElementaryStream['escr_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: escr_flag -- Elementary Stream Clock Reference
|
||||
$PackedElementaryStream['es_rate_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: es_rate_flag -- Elementary Stream [data] Rate
|
||||
$PackedElementaryStream['dsm_trick_mode_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: dsm_trick_mode_flag -- DSM trick mode - not used by DVD
|
||||
$PackedElementaryStream['additional_copy_info_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: additional_copy_info_flag
|
||||
$PackedElementaryStream['crc_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: crc_flag
|
||||
$PackedElementaryStream['extension_flag'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 1); // 1 bit flag: extension_flag
|
||||
$PackedElementaryStream['pes_remain_header_length'] = self::readBitsFromStream($bitstream, $bitstreamoffset, 8); // 1 bit flag: priority
|
||||
|
||||
$additional_header_bytes = 0;
|
||||
$additional_header_bytes += ($PackedElementaryStream['pts_flag'] ? 5 : 0);
|
||||
$additional_header_bytes += ($PackedElementaryStream['dts_flag'] ? 5 : 0);
|
||||
$additional_header_bytes += ($PackedElementaryStream['escr_flag'] ? 6 : 0);
|
||||
$additional_header_bytes += ($PackedElementaryStream['es_rate_flag'] ? 3 : 0);
|
||||
$additional_header_bytes += ($PackedElementaryStream['additional_copy_info_flag'] ? 1 : 0);
|
||||
$additional_header_bytes += ($PackedElementaryStream['crc_flag'] ? 2 : 0);
|
||||
$additional_header_bytes += ($PackedElementaryStream['extension_flag'] ? 1 : 0);
|
||||
$PackedElementaryStream['additional_header_bytes'] = $additional_header_bytes;
|
||||
$bitstream .= getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $StartCodeOffset + 9, $additional_header_bytes));
|
||||
|
||||
$info['mpeg']['packed_elementary_streams'][$PackedElementaryStream['stream_type']][$PackedElementaryStream['stream_id']][] = $PackedElementaryStream;
|
||||
*/
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($this->getid3->filename);
|
||||
$getid3_temp->info = $info;
|
||||
$getid3_mp3 = new getid3_mp3($getid3_temp);
|
||||
for ($i = 0; $i <= 7; $i++) {
|
||||
// some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after
|
||||
// I have no idea why or what the difference is, so this is a stupid hack.
|
||||
// If anybody has any better idea of what's going on, please let me know - info@getid3.org
|
||||
$getid3_temp->info = $info; // only overwrite real data if valid header found
|
||||
//echo 'audio at? '.($MPEGstreamBaseOffset + $StartCodeOffset + 4 + 8 + $i).'<br>';
|
||||
if ($getid3_mp3->decodeMPEGaudioHeader($MPEGstreamBaseOffset + $StartCodeOffset + 4 + 8 + $i, $getid3_temp->info, false)) {
|
||||
//echo 'yes!<br>';
|
||||
$info = $getid3_temp->info;
|
||||
$info['audio']['bitrate_mode'] = 'cbr';
|
||||
$info['audio']['lossless'] = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
unset($getid3_temp, $getid3_mp3);
|
||||
break;
|
||||
|
||||
case 0xBC: // Program Stream Map
|
||||
case 0xBD: // Private stream 1 (non MPEG audio, subpictures)
|
||||
case 0xBE: // Padding stream
|
||||
case 0xBF: // Private stream 2 (navigation data)
|
||||
case 0xF0: // ECM stream
|
||||
case 0xF1: // EMM stream
|
||||
case 0xF2: // DSM-CC stream
|
||||
case 0xF3: // ISO/IEC_13522_stream
|
||||
case 0xF4: // ITU-I Rec. H.222.1 type A
|
||||
case 0xF5: // ITU-I Rec. H.222.1 type B
|
||||
case 0xF6: // ITU-I Rec. H.222.1 type C
|
||||
case 0xF7: // ITU-I Rec. H.222.1 type D
|
||||
case 0xF8: // ITU-I Rec. H.222.1 type E
|
||||
case 0xF9: // ancilliary stream
|
||||
case 0xFA: // ISO/IEC 14496-1 SL-packtized stream
|
||||
case 0xFB: // ISO/IEC 14496-1 FlexMux stream
|
||||
case 0xFC: // metadata stream
|
||||
case 0xFD: // extended stream ID
|
||||
case 0xFE: // reserved data stream
|
||||
case 0xFF: // program stream directory
|
||||
// ignore
|
||||
break;
|
||||
|
||||
default:
|
||||
// ignore
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
|
||||
|
||||
// // Temporary hack to account for interleaving overhead:
|
||||
// if (!empty($info['video']['bitrate']) && !empty($info['audio']['bitrate'])) {
|
||||
// $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['video']['bitrate'] + $info['audio']['bitrate']);
|
||||
//
|
||||
// // Interleaved MPEG audio/video files have a certain amount of overhead that varies
|
||||
// // by both video and audio bitrates, and not in any sensible, linear/logarithmic pattern
|
||||
// // Use interpolated lookup tables to approximately guess how much is overhead, because
|
||||
// // playtime is calculated as filesize / total-bitrate
|
||||
// $info['playtime_seconds'] *= self::systemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']);
|
||||
//
|
||||
// //switch ($info['video']['bitrate']) {
|
||||
// // case('5000000'):
|
||||
// // $multiplier = 0.93292642112380355828048824319889;
|
||||
// // break;
|
||||
// // case('5500000'):
|
||||
// // $multiplier = 0.93582895375200989965359777343219;
|
||||
// // break;
|
||||
// // case('6000000'):
|
||||
// // $multiplier = 0.93796247714820932532911373859139;
|
||||
// // break;
|
||||
// // case('7000000'):
|
||||
// // $multiplier = 0.9413264083635103463010117778776;
|
||||
// // break;
|
||||
// // default:
|
||||
// // $multiplier = 1;
|
||||
// // break;
|
||||
// //}
|
||||
// //$info['playtime_seconds'] *= $multiplier;
|
||||
// //$info['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.';
|
||||
// if ($info['video']['bitrate'] < 50000) {
|
||||
// $this->warning('Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.');
|
||||
// }
|
||||
// }
|
||||
//
|
||||
/*
|
||||
$time_prev = 0;
|
||||
$byte_prev = 0;
|
||||
$vbr_bitrates = array();
|
||||
foreach ($info['mpeg']['group_of_pictures'] as $gopkey => $gopdata) {
|
||||
$time_this = ($gopdata['time_code_hours'] * 3600) + ($gopdata['time_code_minutes'] * 60) + $gopdata['time_code_seconds'] + ($gopdata['time_code_seconds'] / 30);
|
||||
$byte_this = $gopdata['byte_offset'];
|
||||
if ($gopkey > 0) {
|
||||
if ($time_this > $time_prev) {
|
||||
$bytedelta = $byte_this - $byte_prev;
|
||||
$timedelta = $time_this - $time_prev;
|
||||
$this_bitrate = ($bytedelta * 8) / $timedelta;
|
||||
echo $gopkey.': ('.number_format($time_prev, 2).'-'.number_format($time_this, 2).') '.number_format($bytedelta).' bytes over '.number_format($timedelta, 3).' seconds = '.number_format($this_bitrate / 1000, 2).'kbps<br>';
|
||||
$time_prev = $time_this;
|
||||
$byte_prev = $byte_this;
|
||||
$vbr_bitrates[] = $this_bitrate;
|
||||
}
|
||||
}
|
||||
}
|
||||
echo 'average_File_bitrate = '.number_format(array_sum($vbr_bitrates) / count($vbr_bitrates), 1).'<br>';
|
||||
*/
|
||||
//echo '<pre>'.print_r($FramesByGOP, true).'</pre>';
|
||||
if (!empty($info['mpeg']['video']['bitrate_mode']) && ($info['mpeg']['video']['bitrate_mode'] == 'vbr')) {
|
||||
$last_GOP_id = max(array_keys($FramesByGOP));
|
||||
$frames_in_last_GOP = count($FramesByGOP[$last_GOP_id]);
|
||||
$gopdata = &$info['mpeg']['group_of_pictures'][$last_GOP_id];
|
||||
$info['playtime_seconds'] = ($gopdata['time_code_hours'] * 3600) + ($gopdata['time_code_minutes'] * 60) + $gopdata['time_code_seconds'] + (($gopdata['time_code_pictures'] + $frames_in_last_GOP + 1) / $info['mpeg']['video']['frame_rate']);
|
||||
if (!isset($info['video']['bitrate'])) {
|
||||
$overall_bitrate = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds'];
|
||||
$info['video']['bitrate'] = $overall_bitrate - (isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : 0);
|
||||
}
|
||||
unset($info['mpeg']['group_of_pictures']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function readBitsFromStream(&$bitstream, &$bitstreamoffset, $bits_to_read, $return_singlebit_as_boolean=true) {
|
||||
$return = bindec(substr($bitstream, $bitstreamoffset, $bits_to_read));
|
||||
$bitstreamoffset += $bits_to_read;
|
||||
if (($bits_to_read == 1) && $return_singlebit_as_boolean) {
|
||||
$return = (bool) $return;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
public static function systemNonOverheadPercentage($VideoBitrate, $AudioBitrate) {
|
||||
$OverheadPercentage = 0;
|
||||
|
||||
$AudioBitrate = max(min($AudioBitrate / 1000, 384), 32); // limit to range of 32kbps - 384kbps (should be only legal bitrates, but maybe VBR?)
|
||||
$VideoBitrate = max(min($VideoBitrate / 1000, 10000), 10); // limit to range of 10kbps - 10Mbps (beyond that curves flatten anyways, no big loss)
|
||||
|
||||
|
||||
//OMBB[audiobitrate] = array(video-10kbps, video-100kbps, video-1000kbps, video-10000kbps)
|
||||
$OverheadMultiplierByBitrate[32] = array(0, 0.9676287944368530, 0.9802276264360310, 0.9844916183244460, 0.9852821845179940);
|
||||
$OverheadMultiplierByBitrate[48] = array(0, 0.9779100089209830, 0.9787770035359320, 0.9846738664076130, 0.9852683013799960);
|
||||
$OverheadMultiplierByBitrate[56] = array(0, 0.9731249855367600, 0.9776624308938040, 0.9832606361852130, 0.9843922606633340);
|
||||
$OverheadMultiplierByBitrate[64] = array(0, 0.9755642683275760, 0.9795256705493390, 0.9836573009193170, 0.9851122539404470);
|
||||
$OverheadMultiplierByBitrate[96] = array(0, 0.9788025247497290, 0.9798553314148700, 0.9822956869792560, 0.9834815119124690);
|
||||
$OverheadMultiplierByBitrate[128] = array(0, 0.9816940050925480, 0.9821675936072120, 0.9829756927470870, 0.9839763420152050);
|
||||
$OverheadMultiplierByBitrate[160] = array(0, 0.9825894094561180, 0.9820913399073960, 0.9823907143253970, 0.9832821783651570);
|
||||
$OverheadMultiplierByBitrate[192] = array(0, 0.9832038474336260, 0.9825731694317960, 0.9821028622712400, 0.9828262076447620);
|
||||
$OverheadMultiplierByBitrate[224] = array(0, 0.9836516298538770, 0.9824718601823890, 0.9818302180625380, 0.9823735101626480);
|
||||
$OverheadMultiplierByBitrate[256] = array(0, 0.9845863022094920, 0.9837229411967540, 0.9824521662210830, 0.9828645172100790);
|
||||
$OverheadMultiplierByBitrate[320] = array(0, 0.9849565280263180, 0.9837683142805110, 0.9822885275960400, 0.9824424382727190);
|
||||
$OverheadMultiplierByBitrate[384] = array(0, 0.9856094774357600, 0.9844573394432720, 0.9825970399837330, 0.9824673808303890);
|
||||
|
||||
$BitrateToUseMin = 32;
|
||||
$BitrateToUseMax = 32;
|
||||
$previousBitrate = 32;
|
||||
foreach ($OverheadMultiplierByBitrate as $key => $value) {
|
||||
if ($AudioBitrate >= $previousBitrate) {
|
||||
$BitrateToUseMin = $previousBitrate;
|
||||
}
|
||||
if ($AudioBitrate < $key) {
|
||||
$BitrateToUseMax = $key;
|
||||
break;
|
||||
}
|
||||
$previousBitrate = $key;
|
||||
}
|
||||
$FactorA = ($BitrateToUseMax - $AudioBitrate) / ($BitrateToUseMax - $BitrateToUseMin);
|
||||
|
||||
$VideoBitrateLog10 = log10($VideoBitrate);
|
||||
$VideoFactorMin1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][floor($VideoBitrateLog10)];
|
||||
$VideoFactorMin2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][floor($VideoBitrateLog10)];
|
||||
$VideoFactorMax1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][ceil($VideoBitrateLog10)];
|
||||
$VideoFactorMax2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][ceil($VideoBitrateLog10)];
|
||||
$FactorV = $VideoBitrateLog10 - floor($VideoBitrateLog10);
|
||||
|
||||
$OverheadPercentage = $VideoFactorMin1 * $FactorA * $FactorV;
|
||||
$OverheadPercentage += $VideoFactorMin2 * (1 - $FactorA) * $FactorV;
|
||||
$OverheadPercentage += $VideoFactorMax1 * $FactorA * (1 - $FactorV);
|
||||
$OverheadPercentage += $VideoFactorMax2 * (1 - $FactorA) * (1 - $FactorV);
|
||||
|
||||
return $OverheadPercentage;
|
||||
}
|
||||
|
||||
|
||||
public static function videoFramerateLookup($rawframerate) {
|
||||
$lookup = array(0, 23.976, 24, 25, 29.97, 30, 50, 59.94, 60);
|
||||
return (isset($lookup[$rawframerate]) ? (float) $lookup[$rawframerate] : (float) 0);
|
||||
}
|
||||
|
||||
public static function videoAspectRatioLookup($rawaspectratio) {
|
||||
$lookup = array(0, 1, 0.6735, 0.7031, 0.7615, 0.8055, 0.8437, 0.8935, 0.9157, 0.9815, 1.0255, 1.0695, 1.0950, 1.1575, 1.2015, 0);
|
||||
return (isset($lookup[$rawaspectratio]) ? (float) $lookup[$rawaspectratio] : (float) 0);
|
||||
}
|
||||
|
||||
public static function videoAspectRatioTextLookup($rawaspectratio) {
|
||||
$lookup = array('forbidden', 'square pixels', '0.6735', '16:9, 625 line, PAL', '0.7615', '0.8055', '16:9, 525 line, NTSC', '0.8935', '4:3, 625 line, PAL, CCIR601', '0.9815', '1.0255', '1.0695', '4:3, 525 line, NTSC, CCIR601', '1.1575', '1.2015', 'reserved');
|
||||
return (isset($lookup[$rawaspectratio]) ? $lookup[$rawaspectratio] : '');
|
||||
}
|
||||
|
||||
public static function videoFormatTextLookup($video_format) {
|
||||
// ISO/IEC 13818-2, section 6.3.6, Table 6-6. Meaning of video_format
|
||||
$lookup = array('component', 'PAL', 'NTSC', 'SECAM', 'MAC', 'Unspecified video format', 'reserved(6)', 'reserved(7)');
|
||||
return (isset($lookup[$video_format]) ? $lookup[$video_format] : '');
|
||||
}
|
||||
|
||||
public static function scalableModeTextLookup($scalable_mode) {
|
||||
// ISO/IEC 13818-2, section 6.3.8, Table 6-10. Definition of scalable_mode
|
||||
$lookup = array('data partitioning', 'spatial scalability', 'SNR scalability', 'temporal scalability');
|
||||
return (isset($lookup[$scalable_mode]) ? $lookup[$scalable_mode] : '');
|
||||
}
|
||||
|
||||
public static function pictureStructureTextLookup($picture_structure) {
|
||||
// ISO/IEC 13818-2, section 6.3.11, Table 6-14 Meaning of picture_structure
|
||||
$lookup = array('reserved', 'Top Field', 'Bottom Field', 'Frame picture');
|
||||
return (isset($lookup[$picture_structure]) ? $lookup[$picture_structure] : '');
|
||||
}
|
||||
|
||||
public static function chromaFormatTextLookup($chroma_format) {
|
||||
// ISO/IEC 13818-2, section 6.3.11, Table 6-14 Meaning of picture_structure
|
||||
$lookup = array('reserved', '4:2:0', '4:2:2', '4:4:4');
|
||||
return (isset($lookup[$chroma_format]) ? $lookup[$chroma_format] : '');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,443 @@
|
|||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// also https://github.com/JamesHeinrich/getID3 //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.flac.php //
|
||||
// module for analyzing FLAC and OggFLAC audio files //
|
||||
// dependencies: module.audio.ogg.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true);
|
||||
|
||||
/**
|
||||
* @tutorial http://flac.sourceforge.net/format.html
|
||||
*/
|
||||
class getid3_flac extends getid3_handler
|
||||
{
|
||||
const syncword = 'fLaC';
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$this->fseek($info['avdataoffset']);
|
||||
$StreamMarker = $this->fread(4);
|
||||
if ($StreamMarker != self::syncword) {
|
||||
return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"');
|
||||
}
|
||||
$info['fileformat'] = 'flac';
|
||||
$info['audio']['dataformat'] = 'flac';
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
$info['audio']['lossless'] = true;
|
||||
|
||||
// parse flac container
|
||||
return $this->parseMETAdata();
|
||||
}
|
||||
|
||||
public function parseMETAdata() {
|
||||
$info = &$this->getid3->info;
|
||||
do {
|
||||
$BlockOffset = $this->ftell();
|
||||
$BlockHeader = $this->fread(4);
|
||||
$LBFBT = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1));
|
||||
$LastBlockFlag = (bool) ($LBFBT & 0x80);
|
||||
$BlockType = ($LBFBT & 0x7F);
|
||||
$BlockLength = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3));
|
||||
$BlockTypeText = self::metaBlockTypeLookup($BlockType);
|
||||
|
||||
if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) {
|
||||
$this->error('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file');
|
||||
break;
|
||||
}
|
||||
if ($BlockLength < 1) {
|
||||
$this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid');
|
||||
break;
|
||||
}
|
||||
|
||||
$info['flac'][$BlockTypeText]['raw'] = array();
|
||||
$BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw'];
|
||||
|
||||
$BlockTypeText_raw['offset'] = $BlockOffset;
|
||||
$BlockTypeText_raw['last_meta_block'] = $LastBlockFlag;
|
||||
$BlockTypeText_raw['block_type'] = $BlockType;
|
||||
$BlockTypeText_raw['block_type_text'] = $BlockTypeText;
|
||||
$BlockTypeText_raw['block_length'] = $BlockLength;
|
||||
if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically
|
||||
$BlockTypeText_raw['block_data'] = $this->fread($BlockLength);
|
||||
}
|
||||
|
||||
switch ($BlockTypeText) {
|
||||
case 'STREAMINFO': // 0x00
|
||||
if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PADDING': // 0x01
|
||||
unset($info['flac']['PADDING']); // ignore
|
||||
break;
|
||||
|
||||
case 'APPLICATION': // 0x02
|
||||
if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'SEEKTABLE': // 0x03
|
||||
if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'VORBIS_COMMENT': // 0x04
|
||||
if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'CUESHEET': // 0x05
|
||||
if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PICTURE': // 0x06
|
||||
if (!$this->parsePICTURE()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset);
|
||||
}
|
||||
|
||||
unset($info['flac'][$BlockTypeText]['raw']);
|
||||
$info['avdataoffset'] = $this->ftell();
|
||||
}
|
||||
while ($LastBlockFlag === false);
|
||||
|
||||
// handle tags
|
||||
if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) {
|
||||
$info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments'];
|
||||
}
|
||||
if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) {
|
||||
$info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']);
|
||||
}
|
||||
|
||||
// copy attachments to 'comments' array if nesesary
|
||||
if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) {
|
||||
foreach ($info['flac']['PICTURE'] as $entry) {
|
||||
if (!empty($entry['data'])) {
|
||||
$info['flac']['comments']['picture'][] = array('image_mime'=>$entry['image_mime'], 'data'=>$entry['data']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($info['flac']['STREAMINFO'])) {
|
||||
if (!$this->isDependencyFor('matroska')) {
|
||||
$info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset'];
|
||||
}
|
||||
$info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8);
|
||||
if ($info['flac']['uncompressed_audio_bytes'] == 0) {
|
||||
return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero');
|
||||
}
|
||||
if (!empty($info['flac']['compressed_audio_bytes'])) {
|
||||
$info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes'];
|
||||
}
|
||||
}
|
||||
|
||||
// set md5_data_source - built into flac 0.5+
|
||||
if (isset($info['flac']['STREAMINFO']['audio_signature'])) {
|
||||
|
||||
if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
|
||||
$this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
|
||||
}
|
||||
else {
|
||||
$info['md5_data_source'] = '';
|
||||
$md5 = $info['flac']['STREAMINFO']['audio_signature'];
|
||||
for ($i = 0; $i < strlen($md5); $i++) {
|
||||
$info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT);
|
||||
}
|
||||
if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
|
||||
unset($info['md5_data_source']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) {
|
||||
$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
|
||||
if ($info['audio']['bits_per_sample'] == 8) {
|
||||
// special case
|
||||
// must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
|
||||
// MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
|
||||
$this->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function parseSTREAMINFO($BlockData) {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['flac']['STREAMINFO'] = array();
|
||||
$streaminfo = &$info['flac']['STREAMINFO'];
|
||||
|
||||
$streaminfo['min_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2));
|
||||
$streaminfo['max_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2));
|
||||
$streaminfo['min_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3));
|
||||
$streaminfo['max_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3));
|
||||
|
||||
$SRCSBSS = getid3_lib::BigEndian2Bin(substr($BlockData, 10, 8));
|
||||
$streaminfo['sample_rate'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 0, 20));
|
||||
$streaminfo['channels'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 20, 3)) + 1;
|
||||
$streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1;
|
||||
$streaminfo['samples_stream'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36));
|
||||
|
||||
$streaminfo['audio_signature'] = substr($BlockData, 18, 16);
|
||||
|
||||
if (!empty($streaminfo['sample_rate'])) {
|
||||
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
$info['audio']['sample_rate'] = $streaminfo['sample_rate'];
|
||||
$info['audio']['channels'] = $streaminfo['channels'];
|
||||
$info['audio']['bits_per_sample'] = $streaminfo['bits_per_sample'];
|
||||
$info['playtime_seconds'] = $streaminfo['samples_stream'] / $streaminfo['sample_rate'];
|
||||
if ($info['playtime_seconds'] > 0) {
|
||||
if (!$this->isDependencyFor('matroska')) {
|
||||
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
|
||||
}
|
||||
else {
|
||||
$this->warning('Cannot determine audio bitrate because total stream size is unknown');
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
return $this->error('Corrupt METAdata block: STREAMINFO');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function parseAPPLICATION($BlockData) {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4));
|
||||
$info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID);
|
||||
$info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function parseSEEKTABLE($BlockData) {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$offset = 0;
|
||||
$BlockLength = strlen($BlockData);
|
||||
$placeholderpattern = str_repeat("\xFF", 8);
|
||||
while ($offset < $BlockLength) {
|
||||
$SampleNumberString = substr($BlockData, $offset, 8);
|
||||
$offset += 8;
|
||||
if ($SampleNumberString == $placeholderpattern) {
|
||||
|
||||
// placeholder point
|
||||
getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1);
|
||||
$offset += 10;
|
||||
|
||||
} else {
|
||||
|
||||
$SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString);
|
||||
$info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
|
||||
$offset += 8;
|
||||
$info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 2));
|
||||
$offset += 2;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function parseVORBIS_COMMENT($BlockData) {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$getid3_ogg = new getid3_ogg($this->getid3);
|
||||
if ($this->isDependencyFor('matroska')) {
|
||||
$getid3_ogg->setStringMode($this->data_string);
|
||||
}
|
||||
$getid3_ogg->ParseVorbisComments();
|
||||
if (isset($info['ogg'])) {
|
||||
unset($info['ogg']['comments_raw']);
|
||||
$info['flac']['VORBIS_COMMENT'] = $info['ogg'];
|
||||
unset($info['ogg']);
|
||||
}
|
||||
|
||||
unset($getid3_ogg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function parseCUESHEET($BlockData) {
|
||||
$info = &$this->getid3->info;
|
||||
$offset = 0;
|
||||
$info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($BlockData, $offset, 128), "\0");
|
||||
$offset += 128;
|
||||
$info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
|
||||
$offset += 8;
|
||||
$info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80);
|
||||
$offset += 1;
|
||||
|
||||
$offset += 258; // reserved
|
||||
|
||||
$info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
|
||||
$offset += 1;
|
||||
|
||||
for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) {
|
||||
$TrackSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
|
||||
$offset += 8;
|
||||
$TrackNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
|
||||
$offset += 1;
|
||||
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset;
|
||||
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($BlockData, $offset, 12);
|
||||
$offset += 12;
|
||||
|
||||
$TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
|
||||
$offset += 1;
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80);
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40);
|
||||
|
||||
$offset += 13; // reserved
|
||||
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
|
||||
$offset += 1;
|
||||
|
||||
for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) {
|
||||
$IndexSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
|
||||
$offset += 8;
|
||||
$IndexNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
|
||||
$offset += 1;
|
||||
|
||||
$offset += 3; // reserved
|
||||
|
||||
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse METADATA_BLOCK_PICTURE flac structure and extract attachment
|
||||
* External usage: audio.ogg
|
||||
*/
|
||||
public function parsePICTURE() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
$picture['type'] = self::pictureTypeLookup($picture['typeid']);
|
||||
$picture['image_mime'] = $this->fread(getid3_lib::BigEndian2Int($this->fread(4)));
|
||||
$descr_length = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
if ($descr_length) {
|
||||
$picture['description'] = $this->fread($descr_length);
|
||||
}
|
||||
$picture['width'] = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
$picture['height'] = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
$picture['color_depth'] = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
$picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
$data_length = getid3_lib::BigEndian2Int($this->fread(4));
|
||||
|
||||
if ($picture['image_mime'] == '-->') {
|
||||
$picture['data'] = $this->fread($data_length);
|
||||
} else {
|
||||
$picture['data'] = $this->saveAttachment(
|
||||
str_replace('/', '_', $picture['type']).'_'.$this->ftell(),
|
||||
$this->ftell(),
|
||||
$data_length,
|
||||
$picture['image_mime']);
|
||||
}
|
||||
|
||||
$info['flac']['PICTURE'][] = $picture;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function metaBlockTypeLookup($blocktype) {
|
||||
static $lookup = array(
|
||||
0 => 'STREAMINFO',
|
||||
1 => 'PADDING',
|
||||
2 => 'APPLICATION',
|
||||
3 => 'SEEKTABLE',
|
||||
4 => 'VORBIS_COMMENT',
|
||||
5 => 'CUESHEET',
|
||||
6 => 'PICTURE',
|
||||
);
|
||||
return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved');
|
||||
}
|
||||
|
||||
public static function applicationIDLookup($applicationid) {
|
||||
// http://flac.sourceforge.net/id.html
|
||||
static $lookup = array(
|
||||
0x41544348 => 'FlacFile', // "ATCH"
|
||||
0x42534F4C => 'beSolo', // "BSOL"
|
||||
0x42554753 => 'Bugs Player', // "BUGS"
|
||||
0x43756573 => 'GoldWave cue points (specification)', // "Cues"
|
||||
0x46696361 => 'CUE Splitter', // "Fica"
|
||||
0x46746F6C => 'flac-tools', // "Ftol"
|
||||
0x4D4F5442 => 'MOTB MetaCzar', // "MOTB"
|
||||
0x4D505345 => 'MP3 Stream Editor', // "MPSE"
|
||||
0x4D754D4C => 'MusicML: Music Metadata Language', // "MuML"
|
||||
0x52494646 => 'Sound Devices RIFF chunk storage', // "RIFF"
|
||||
0x5346464C => 'Sound Font FLAC', // "SFFL"
|
||||
0x534F4E59 => 'Sony Creative Software', // "SONY"
|
||||
0x5351455A => 'flacsqueeze', // "SQEZ"
|
||||
0x54745776 => 'TwistedWave', // "TtWv"
|
||||
0x55495453 => 'UITS Embedding tools', // "UITS"
|
||||
0x61696666 => 'FLAC AIFF chunk storage', // "aiff"
|
||||
0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks', // "imag"
|
||||
0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // "peem"
|
||||
0x71667374 => 'QFLAC Studio', // "qfst"
|
||||
0x72696666 => 'FLAC RIFF chunk storage', // "riff"
|
||||
0x74756E65 => 'TagTuner', // "tune"
|
||||
0x78626174 => 'XBAT', // "xbat"
|
||||
0x786D6364 => 'xmcd', // "xmcd"
|
||||
);
|
||||
return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved');
|
||||
}
|
||||
|
||||
public static function pictureTypeLookup($type_id) {
|
||||
static $lookup = array (
|
||||
0 => 'Other',
|
||||
1 => '32x32 pixels \'file icon\' (PNG only)',
|
||||
2 => 'Other file icon',
|
||||
3 => 'Cover (front)',
|
||||
4 => 'Cover (back)',
|
||||
5 => 'Leaflet page',
|
||||
6 => 'Media (e.g. label side of CD)',
|
||||
7 => 'Lead artist/lead performer/soloist',
|
||||
8 => 'Artist/performer',
|
||||
9 => 'Conductor',
|
||||
10 => 'Band/Orchestra',
|
||||
11 => 'Composer',
|
||||
12 => 'Lyricist/text writer',
|
||||
13 => 'Recording Location',
|
||||
14 => 'During recording',
|
||||
15 => 'During performance',
|
||||
16 => 'Movie/video screen capture',
|
||||
17 => 'A bright coloured fish',
|
||||
18 => 'Illustration',
|
||||
19 => 'Band/artist logotype',
|
||||
20 => 'Publisher/Studio logotype',
|
||||
);
|
||||
return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,756 @@
|
|||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// also https://github.com/JamesHeinrich/getID3 //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.audio.ogg.php //
|
||||
// module for analyzing Ogg Vorbis, OggFLAC and Speex files //
|
||||
// dependencies: module.audio.flac.php //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
|
||||
|
||||
class getid3_ogg extends getid3_handler
|
||||
{
|
||||
// http://xiph.org/vorbis/doc/Vorbis_I_spec.html
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'ogg';
|
||||
|
||||
// Warn about illegal tags - only vorbiscomments are allowed
|
||||
if (isset($info['id3v2'])) {
|
||||
$info['warning'][] = 'Illegal ID3v2 tag present.';
|
||||
}
|
||||
if (isset($info['id3v1'])) {
|
||||
$info['warning'][] = 'Illegal ID3v1 tag present.';
|
||||
}
|
||||
if (isset($info['ape'])) {
|
||||
$info['warning'][] = 'Illegal APE tag present.';
|
||||
}
|
||||
|
||||
|
||||
// Page 1 - Stream Header
|
||||
|
||||
$this->fseek($info['avdataoffset']);
|
||||
|
||||
$oggpageinfo = $this->ParseOggPageHeader();
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||
|
||||
if ($this->ftell() >= $this->getid3->fread_buffer_size()) {
|
||||
$info['error'][] = 'Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)';
|
||||
unset($info['fileformat']);
|
||||
unset($info['ogg']);
|
||||
return false;
|
||||
}
|
||||
|
||||
$filedata = $this->fread($oggpageinfo['page_length']);
|
||||
$filedataoffset = 0;
|
||||
|
||||
if (substr($filedata, 0, 4) == 'fLaC') {
|
||||
|
||||
$info['audio']['dataformat'] = 'flac';
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
$info['audio']['lossless'] = true;
|
||||
|
||||
} elseif (substr($filedata, 1, 6) == 'vorbis') {
|
||||
|
||||
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
|
||||
|
||||
} elseif (substr($filedata, 0, 8) == 'Speex ') {
|
||||
|
||||
// http://www.speex.org/manual/node10.html
|
||||
|
||||
$info['audio']['dataformat'] = 'speex';
|
||||
$info['mime_type'] = 'audio/speex';
|
||||
$info['audio']['bitrate_mode'] = 'abr';
|
||||
$info['audio']['lossless'] = false;
|
||||
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex '
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20);
|
||||
$filedataoffset += 20;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
|
||||
$info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
|
||||
$info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
|
||||
$info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
|
||||
$info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
|
||||
$info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
|
||||
|
||||
$info['audio']['sample_rate'] = $info['speex']['sample_rate'];
|
||||
$info['audio']['channels'] = $info['speex']['channels'];
|
||||
if ($info['speex']['vbr']) {
|
||||
$info['audio']['bitrate_mode'] = 'vbr';
|
||||
}
|
||||
|
||||
} elseif (substr($filedata, 0, 7) == "\x80".'theora') {
|
||||
|
||||
// http://www.theora.org/doc/Theora.pdf (section 6.2)
|
||||
|
||||
$info['ogg']['pageheader']['theora']['theora_magic'] = substr($filedata, $filedataoffset, 7); // hard-coded to "\x80.'theora'
|
||||
$filedataoffset += 7;
|
||||
$info['ogg']['pageheader']['theora']['version_major'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['ogg']['pageheader']['theora']['version_minor'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['ogg']['pageheader']['theora']['version_revision'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['ogg']['pageheader']['theora']['frame_width_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2));
|
||||
$filedataoffset += 2;
|
||||
$info['ogg']['pageheader']['theora']['frame_height_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2));
|
||||
$filedataoffset += 2;
|
||||
$info['ogg']['pageheader']['theora']['resolution_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3));
|
||||
$filedataoffset += 3;
|
||||
$info['ogg']['pageheader']['theora']['resolution_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3));
|
||||
$filedataoffset += 3;
|
||||
$info['ogg']['pageheader']['theora']['picture_offset_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['ogg']['pageheader']['theora']['picture_offset_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['ogg']['pageheader']['theora']['frame_rate_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader']['theora']['frame_rate_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3));
|
||||
$filedataoffset += 3;
|
||||
$info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3));
|
||||
$filedataoffset += 3;
|
||||
$info['ogg']['pageheader']['theora']['color_space_id'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['ogg']['pageheader']['theora']['nominal_bitrate'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3));
|
||||
$filedataoffset += 3;
|
||||
$info['ogg']['pageheader']['theora']['flags'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2));
|
||||
$filedataoffset += 2;
|
||||
|
||||
$info['ogg']['pageheader']['theora']['quality'] = ($info['ogg']['pageheader']['theora']['flags'] & 0xFC00) >> 10;
|
||||
$info['ogg']['pageheader']['theora']['kfg_shift'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x03E0) >> 5;
|
||||
$info['ogg']['pageheader']['theora']['pixel_format_id'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0018) >> 3;
|
||||
$info['ogg']['pageheader']['theora']['reserved'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0007) >> 0; // should be 0
|
||||
$info['ogg']['pageheader']['theora']['color_space'] = self::TheoraColorSpace($info['ogg']['pageheader']['theora']['color_space_id']);
|
||||
$info['ogg']['pageheader']['theora']['pixel_format'] = self::TheoraPixelFormat($info['ogg']['pageheader']['theora']['pixel_format_id']);
|
||||
|
||||
$info['video']['dataformat'] = 'theora';
|
||||
$info['mime_type'] = 'video/ogg';
|
||||
//$info['audio']['bitrate_mode'] = 'abr';
|
||||
//$info['audio']['lossless'] = false;
|
||||
$info['video']['resolution_x'] = $info['ogg']['pageheader']['theora']['resolution_x'];
|
||||
$info['video']['resolution_y'] = $info['ogg']['pageheader']['theora']['resolution_y'];
|
||||
if ($info['ogg']['pageheader']['theora']['frame_rate_denominator'] > 0) {
|
||||
$info['video']['frame_rate'] = (float) $info['ogg']['pageheader']['theora']['frame_rate_numerator'] / $info['ogg']['pageheader']['theora']['frame_rate_denominator'];
|
||||
}
|
||||
if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) {
|
||||
$info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'];
|
||||
}
|
||||
$info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable';
|
||||
|
||||
|
||||
} elseif (substr($filedata, 0, 8) == "fishead\x00") {
|
||||
|
||||
// Ogg Skeleton version 3.0 Format Specification
|
||||
// http://xiph.org/ogg/doc/skeleton.html
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
|
||||
$filedataoffset += 2;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
|
||||
$filedataoffset += 2;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
|
||||
$filedataoffset += 20;
|
||||
|
||||
$info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
|
||||
$info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
|
||||
$info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
|
||||
$info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc'];
|
||||
|
||||
|
||||
$counter = 0;
|
||||
do {
|
||||
$oggpageinfo = $this->ParseOggPageHeader();
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
|
||||
$filedata = $this->fread($oggpageinfo['page_length']);
|
||||
$this->fseek($oggpageinfo['page_end_offset']);
|
||||
|
||||
if (substr($filedata, 0, 8) == "fisbone\x00") {
|
||||
|
||||
$filedataoffset = 8;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3);
|
||||
$filedataoffset += 3;
|
||||
|
||||
} elseif (substr($filedata, 1, 6) == 'theora') {
|
||||
|
||||
$info['video']['dataformat'] = 'theora1';
|
||||
$info['error'][] = 'Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']';
|
||||
//break;
|
||||
|
||||
} elseif (substr($filedata, 1, 6) == 'vorbis') {
|
||||
|
||||
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
|
||||
|
||||
} else {
|
||||
$info['error'][] = 'unexpected';
|
||||
//break;
|
||||
}
|
||||
//} while ($oggpageinfo['page_seqno'] == 0);
|
||||
} while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
|
||||
|
||||
$this->fseek($oggpageinfo['page_start_offset']);
|
||||
|
||||
$info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']';
|
||||
//return false;
|
||||
|
||||
} else {
|
||||
|
||||
$info['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
|
||||
unset($info['ogg']);
|
||||
unset($info['mime_type']);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// Page 2 - Comment Header
|
||||
$oggpageinfo = $this->ParseOggPageHeader();
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||
|
||||
switch ($info['audio']['dataformat']) {
|
||||
case 'vorbis':
|
||||
$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis'
|
||||
|
||||
$this->ParseVorbisComments();
|
||||
break;
|
||||
|
||||
case 'flac':
|
||||
$flac = new getid3_flac($this->getid3);
|
||||
if (!$flac->parseMETAdata()) {
|
||||
$info['error'][] = 'Failed to parse FLAC headers';
|
||||
return false;
|
||||
}
|
||||
unset($flac);
|
||||
break;
|
||||
|
||||
case 'speex':
|
||||
$this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
|
||||
$this->ParseVorbisComments();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Last Page - Number of Samples
|
||||
if (!getid3_lib::intValueSupported($info['avdataend'])) {
|
||||
|
||||
$info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
|
||||
|
||||
} else {
|
||||
|
||||
$this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0));
|
||||
$LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size()));
|
||||
if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
|
||||
$this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO')));
|
||||
$info['avdataend'] = $this->ftell();
|
||||
$info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
|
||||
$info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
|
||||
if ($info['ogg']['samples'] == 0) {
|
||||
$info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
|
||||
return false;
|
||||
}
|
||||
if (!empty($info['audio']['sample_rate'])) {
|
||||
$info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!empty($info['ogg']['bitrate_average'])) {
|
||||
$info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
|
||||
} elseif (!empty($info['ogg']['bitrate_nominal'])) {
|
||||
$info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
|
||||
} elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
|
||||
$info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
|
||||
}
|
||||
if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
|
||||
if ($info['audio']['bitrate'] == 0) {
|
||||
$info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
|
||||
return false;
|
||||
}
|
||||
$info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
|
||||
}
|
||||
|
||||
if (isset($info['ogg']['vendor'])) {
|
||||
$info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
|
||||
|
||||
// Vorbis only
|
||||
if ($info['audio']['dataformat'] == 'vorbis') {
|
||||
|
||||
// Vorbis 1.0 starts with Xiph.Org
|
||||
if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
|
||||
|
||||
if ($info['audio']['bitrate_mode'] == 'abr') {
|
||||
|
||||
// Set -b 128 on abr files
|
||||
$info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
|
||||
|
||||
} elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
|
||||
// Set -q N on vbr files
|
||||
$info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
|
||||
$info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
|
||||
$info = &$this->getid3->info;
|
||||
$info['audio']['dataformat'] = 'vorbis';
|
||||
$info['audio']['lossless'] = false;
|
||||
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
|
||||
$filedataoffset += 6;
|
||||
$info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$info['audio']['channels'] = $info['ogg']['numberofchannels'];
|
||||
$info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
if ($info['ogg']['samplerate'] == 0) {
|
||||
$info['error'][] = 'Corrupt Ogg file: sample rate == zero';
|
||||
return false;
|
||||
}
|
||||
$info['audio']['sample_rate'] = $info['ogg']['samplerate'];
|
||||
$info['ogg']['samples'] = 0; // filled in later
|
||||
$info['ogg']['bitrate_average'] = 0; // filled in later
|
||||
$info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
|
||||
$info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
|
||||
$info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
|
||||
|
||||
$info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
|
||||
if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
|
||||
unset($info['ogg']['bitrate_max']);
|
||||
$info['audio']['bitrate_mode'] = 'abr';
|
||||
}
|
||||
if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
|
||||
unset($info['ogg']['bitrate_nominal']);
|
||||
}
|
||||
if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
|
||||
unset($info['ogg']['bitrate_min']);
|
||||
$info['audio']['bitrate_mode'] = 'abr';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function ParseOggPageHeader() {
|
||||
// http://xiph.org/ogg/vorbis/doc/framing.html
|
||||
$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
|
||||
|
||||
$filedata = $this->fread($this->getid3->fread_buffer_size());
|
||||
$filedataoffset = 0;
|
||||
while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
|
||||
if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
|
||||
// should be found before here
|
||||
return false;
|
||||
}
|
||||
if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
|
||||
if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) {
|
||||
// get some more data, unless eof, in which case fail
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
$filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
|
||||
|
||||
$oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
|
||||
$oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
|
||||
$oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
|
||||
|
||||
$oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
|
||||
$filedataoffset += 8;
|
||||
$oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
|
||||
$filedataoffset += 4;
|
||||
$oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$oggheader['page_length'] = 0;
|
||||
for ($i = 0; $i < $oggheader['page_segments']; $i++) {
|
||||
$oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
|
||||
$filedataoffset += 1;
|
||||
$oggheader['page_length'] += $oggheader['segment_table'][$i];
|
||||
}
|
||||
$oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
|
||||
$oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length'];
|
||||
$this->fseek($oggheader['header_end_offset']);
|
||||
|
||||
return $oggheader;
|
||||
}
|
||||
|
||||
// http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
|
||||
public function ParseVorbisComments() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$OriginalOffset = $this->ftell();
|
||||
$commentdataoffset = 0;
|
||||
$VorbisCommentPage = 1;
|
||||
|
||||
switch ($info['audio']['dataformat']) {
|
||||
case 'vorbis':
|
||||
case 'speex':
|
||||
$CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block
|
||||
$this->fseek($CommentStartOffset);
|
||||
$commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
|
||||
$commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
|
||||
|
||||
if ($info['audio']['dataformat'] == 'vorbis') {
|
||||
$commentdataoffset += (strlen('vorbis') + 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'flac':
|
||||
$CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
|
||||
$this->fseek($CommentStartOffset);
|
||||
$commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']);
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
$VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
|
||||
$commentdataoffset += 4;
|
||||
|
||||
$info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
|
||||
$commentdataoffset += $VendorSize;
|
||||
|
||||
$CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
|
||||
$commentdataoffset += 4;
|
||||
$info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
|
||||
|
||||
$basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
|
||||
$ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
|
||||
for ($i = 0; $i < $CommentsCount; $i++) {
|
||||
|
||||
$ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
|
||||
|
||||
if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
|
||||
if ($oggpageinfo = $this->ParseOggPageHeader()) {
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||
|
||||
$VorbisCommentPage++;
|
||||
|
||||
// First, save what we haven't read yet
|
||||
$AsYetUnusedData = substr($commentdata, $commentdataoffset);
|
||||
|
||||
// Then take that data off the end
|
||||
$commentdata = substr($commentdata, 0, $commentdataoffset);
|
||||
|
||||
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
|
||||
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||
|
||||
// Finally, stick the unused data back on the end
|
||||
$commentdata .= $AsYetUnusedData;
|
||||
|
||||
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
|
||||
$commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
|
||||
}
|
||||
|
||||
}
|
||||
$ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
|
||||
|
||||
// replace avdataoffset with position just after the last vorbiscomment
|
||||
$info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
|
||||
|
||||
$commentdataoffset += 4;
|
||||
while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
|
||||
if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
|
||||
$info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
|
||||
break 2;
|
||||
}
|
||||
|
||||
$VorbisCommentPage++;
|
||||
|
||||
$oggpageinfo = $this->ParseOggPageHeader();
|
||||
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
|
||||
|
||||
// First, save what we haven't read yet
|
||||
$AsYetUnusedData = substr($commentdata, $commentdataoffset);
|
||||
|
||||
// Then take that data off the end
|
||||
$commentdata = substr($commentdata, 0, $commentdataoffset);
|
||||
|
||||
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
|
||||
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
|
||||
|
||||
// Finally, stick the unused data back on the end
|
||||
$commentdata .= $AsYetUnusedData;
|
||||
|
||||
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
|
||||
if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
|
||||
$info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
|
||||
break;
|
||||
}
|
||||
$readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
|
||||
if ($readlength <= 0) {
|
||||
$info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell();
|
||||
break;
|
||||
}
|
||||
$commentdata .= $this->fread($readlength);
|
||||
|
||||
//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
|
||||
}
|
||||
$ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
|
||||
$commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
|
||||
$commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
|
||||
|
||||
if (!$commentstring) {
|
||||
|
||||
// no comment?
|
||||
$info['warning'][] = 'Blank Ogg comment ['.$i.']';
|
||||
|
||||
} elseif (strstr($commentstring, '=')) {
|
||||
|
||||
$commentexploded = explode('=', $commentstring, 2);
|
||||
$ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]);
|
||||
$ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
|
||||
|
||||
if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') {
|
||||
|
||||
// http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
|
||||
// The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard.
|
||||
// http://flac.sourceforge.net/format.html#metadata_block_picture
|
||||
$flac = new getid3_flac($this->getid3);
|
||||
$flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']));
|
||||
$flac->parsePICTURE();
|
||||
$info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0];
|
||||
unset($flac);
|
||||
|
||||
} elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') {
|
||||
|
||||
$data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
|
||||
$this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure');
|
||||
/** @todo use 'coverartmime' where available */
|
||||
$imageinfo = getid3_lib::GetDataImageSize($data);
|
||||
if ($imageinfo === false || !isset($imageinfo['mime'])) {
|
||||
$this->warning('COVERART vorbiscomment tag contains invalid image');
|
||||
continue;
|
||||
}
|
||||
|
||||
$ogg = new self($this->getid3);
|
||||
$ogg->setStringMode($data);
|
||||
$info['ogg']['comments']['picture'][] = array(
|
||||
'image_mime' => $imageinfo['mime'],
|
||||
'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),
|
||||
);
|
||||
unset($ogg);
|
||||
|
||||
} else {
|
||||
|
||||
$info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
|
||||
|
||||
}
|
||||
unset($ThisFileInfo_ogg_comments_raw[$i]);
|
||||
}
|
||||
unset($ThisFileInfo_ogg_comments_raw);
|
||||
|
||||
|
||||
// Replay Gain Adjustment
|
||||
// http://privatewww.essex.ac.uk/~djmrob/replaygain/
|
||||
if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
|
||||
foreach ($info['ogg']['comments'] as $index => $commentvalue) {
|
||||
switch ($index) {
|
||||
case 'rg_audiophile':
|
||||
case 'replaygain_album_gain':
|
||||
$info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
|
||||
unset($info['ogg']['comments'][$index]);
|
||||
break;
|
||||
|
||||
case 'rg_radio':
|
||||
case 'replaygain_track_gain':
|
||||
$info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
|
||||
unset($info['ogg']['comments'][$index]);
|
||||
break;
|
||||
|
||||
case 'replaygain_album_peak':
|
||||
$info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
|
||||
unset($info['ogg']['comments'][$index]);
|
||||
break;
|
||||
|
||||
case 'rg_peak':
|
||||
case 'replaygain_track_peak':
|
||||
$info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
|
||||
unset($info['ogg']['comments'][$index]);
|
||||
break;
|
||||
|
||||
case 'replaygain_reference_loudness':
|
||||
$info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
|
||||
unset($info['ogg']['comments'][$index]);
|
||||
break;
|
||||
|
||||
default:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->fseek($OriginalOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function SpeexBandModeLookup($mode) {
|
||||
static $SpeexBandModeLookup = array();
|
||||
if (empty($SpeexBandModeLookup)) {
|
||||
$SpeexBandModeLookup[0] = 'narrow';
|
||||
$SpeexBandModeLookup[1] = 'wide';
|
||||
$SpeexBandModeLookup[2] = 'ultra-wide';
|
||||
}
|
||||
return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
|
||||
}
|
||||
|
||||
|
||||
public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
|
||||
for ($i = 0; $i < $SegmentNumber; $i++) {
|
||||
$segmentlength = 0;
|
||||
foreach ($OggInfoArray['segment_table'] as $key => $value) {
|
||||
$segmentlength += $value;
|
||||
if ($value < 255) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $segmentlength;
|
||||
}
|
||||
|
||||
|
||||
public static function get_quality_from_nominal_bitrate($nominal_bitrate) {
|
||||
|
||||
// decrease precision
|
||||
$nominal_bitrate = $nominal_bitrate / 1000;
|
||||
|
||||
if ($nominal_bitrate < 128) {
|
||||
// q-1 to q4
|
||||
$qval = ($nominal_bitrate - 64) / 16;
|
||||
} elseif ($nominal_bitrate < 256) {
|
||||
// q4 to q8
|
||||
$qval = $nominal_bitrate / 32;
|
||||
} elseif ($nominal_bitrate < 320) {
|
||||
// q8 to q9
|
||||
$qval = ($nominal_bitrate + 256) / 64;
|
||||
} else {
|
||||
// q9 to q10
|
||||
$qval = ($nominal_bitrate + 1300) / 180;
|
||||
}
|
||||
//return $qval; // 5.031324
|
||||
//return intval($qval); // 5
|
||||
return round($qval, 1); // 5 or 4.9
|
||||
}
|
||||
|
||||
public static function TheoraColorSpace($colorspace_id) {
|
||||
// http://www.theora.org/doc/Theora.pdf (table 6.3)
|
||||
static $TheoraColorSpaceLookup = array();
|
||||
if (empty($TheoraColorSpaceLookup)) {
|
||||
$TheoraColorSpaceLookup[0] = 'Undefined';
|
||||
$TheoraColorSpaceLookup[1] = 'Rec. 470M';
|
||||
$TheoraColorSpaceLookup[2] = 'Rec. 470BG';
|
||||
$TheoraColorSpaceLookup[3] = 'Reserved';
|
||||
}
|
||||
return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null);
|
||||
}
|
||||
|
||||
public static function TheoraPixelFormat($pixelformat_id) {
|
||||
// http://www.theora.org/doc/Theora.pdf (table 6.4)
|
||||
static $TheoraPixelFormatLookup = array();
|
||||
if (empty($TheoraPixelFormatLookup)) {
|
||||
$TheoraPixelFormatLookup[0] = '4:2:0';
|
||||
$TheoraPixelFormatLookup[1] = 'Reserved';
|
||||
$TheoraPixelFormatLookup[2] = '4:2:2';
|
||||
$TheoraPixelFormatLookup[3] = '4:4:4';
|
||||
}
|
||||
return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,338 @@
|
|||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// also https://github.com/JamesHeinrich/getID3 //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.graphic.jpg.php //
|
||||
// module for analyzing JPEG Image files //
|
||||
// dependencies: PHP compiled with --enable-exif (optional) //
|
||||
// module.tag.xmp.php (optional) //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_jpg extends getid3_handler
|
||||
{
|
||||
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'jpg';
|
||||
$info['video']['dataformat'] = 'jpg';
|
||||
$info['video']['lossless'] = false;
|
||||
$info['video']['bits_per_sample'] = 24;
|
||||
$info['video']['pixel_aspect_ratio'] = (float) 1;
|
||||
|
||||
$this->fseek($info['avdataoffset']);
|
||||
|
||||
$imageinfo = array();
|
||||
//list($width, $height, $type) = getid3_lib::GetDataImageSize($this->fread($info['filesize']), $imageinfo);
|
||||
list($width, $height, $type) = getimagesize($info['filenamepath'], $imageinfo); // http://www.getid3.org/phpBB3/viewtopic.php?t=1474
|
||||
|
||||
|
||||
if (isset($imageinfo['APP13'])) {
|
||||
// http://php.net/iptcparse
|
||||
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
|
||||
$iptc_parsed = iptcparse($imageinfo['APP13']);
|
||||
if (is_array($iptc_parsed)) {
|
||||
foreach ($iptc_parsed as $iptc_key_raw => $iptc_values) {
|
||||
list($iptc_record, $iptc_tagkey) = explode('#', $iptc_key_raw);
|
||||
$iptc_tagkey = intval(ltrim($iptc_tagkey, '0'));
|
||||
foreach ($iptc_values as $key => $value) {
|
||||
$IPTCrecordName = $this->IPTCrecordName($iptc_record);
|
||||
$IPTCrecordTagName = $this->IPTCrecordTagName($iptc_record, $iptc_tagkey);
|
||||
if (isset($info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName])) {
|
||||
$info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName][] = $value;
|
||||
} else {
|
||||
$info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName] = array($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$returnOK = false;
|
||||
switch ($type) {
|
||||
case IMG_JPG:
|
||||
$info['video']['resolution_x'] = $width;
|
||||
$info['video']['resolution_y'] = $height;
|
||||
|
||||
if (isset($imageinfo['APP1'])) {
|
||||
if (function_exists('exif_read_data')) {
|
||||
if (substr($imageinfo['APP1'], 0, 4) == 'Exif') {
|
||||
//$info['warning'][] = 'known issue: https://bugs.php.net/bug.php?id=62523';
|
||||
//return false;
|
||||
$info['jpg']['exif'] = exif_read_data($info['filenamepath'], null, true, false);
|
||||
} else {
|
||||
$info['warning'][] = 'exif_read_data() cannot parse non-EXIF data in APP1 (expected "Exif", found "'.substr($imageinfo['APP1'], 0, 4).'")';
|
||||
}
|
||||
} else {
|
||||
$info['warning'][] = 'EXIF parsing only available when '.(GETID3_OS_ISWINDOWS ? 'php_exif.dll enabled' : 'compiled with --enable-exif');
|
||||
}
|
||||
}
|
||||
$returnOK = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
$cast_as_appropriate_keys = array('EXIF', 'IFD0', 'THUMBNAIL');
|
||||
foreach ($cast_as_appropriate_keys as $exif_key) {
|
||||
if (isset($info['jpg']['exif'][$exif_key])) {
|
||||
foreach ($info['jpg']['exif'][$exif_key] as $key => $value) {
|
||||
$info['jpg']['exif'][$exif_key][$key] = $this->CastAsAppropriate($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isset($info['jpg']['exif']['GPS'])) {
|
||||
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSVersion'])) {
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$version_subparts[$i] = ord(substr($info['jpg']['exif']['GPS']['GPSVersion'], $i, 1));
|
||||
}
|
||||
$info['jpg']['exif']['GPS']['computed']['version'] = 'v'.implode('.', $version_subparts);
|
||||
}
|
||||
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSDateStamp'])) {
|
||||
$explodedGPSDateStamp = explode(':', $info['jpg']['exif']['GPS']['GPSDateStamp']);
|
||||
$computed_time[5] = (isset($explodedGPSDateStamp[0]) ? $explodedGPSDateStamp[0] : '');
|
||||
$computed_time[3] = (isset($explodedGPSDateStamp[1]) ? $explodedGPSDateStamp[1] : '');
|
||||
$computed_time[4] = (isset($explodedGPSDateStamp[2]) ? $explodedGPSDateStamp[2] : '');
|
||||
|
||||
$computed_time = array(0=>0, 1=>0, 2=>0, 3=>0, 4=>0, 5=>0);
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSTimeStamp']) && is_array($info['jpg']['exif']['GPS']['GPSTimeStamp'])) {
|
||||
foreach ($info['jpg']['exif']['GPS']['GPSTimeStamp'] as $key => $value) {
|
||||
$computed_time[$key] = getid3_lib::DecimalizeFraction($value);
|
||||
}
|
||||
}
|
||||
$info['jpg']['exif']['GPS']['computed']['timestamp'] = gmmktime($computed_time[0], $computed_time[1], $computed_time[2], $computed_time[3], $computed_time[4], $computed_time[5]);
|
||||
}
|
||||
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSLatitude']) && is_array($info['jpg']['exif']['GPS']['GPSLatitude'])) {
|
||||
$direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLatitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLatitudeRef'] == 'S')) ? -1 : 1);
|
||||
foreach ($info['jpg']['exif']['GPS']['GPSLatitude'] as $key => $value) {
|
||||
$computed_latitude[$key] = getid3_lib::DecimalizeFraction($value);
|
||||
}
|
||||
$info['jpg']['exif']['GPS']['computed']['latitude'] = $direction_multiplier * ($computed_latitude[0] + ($computed_latitude[1] / 60) + ($computed_latitude[2] / 3600));
|
||||
}
|
||||
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSLongitude']) && is_array($info['jpg']['exif']['GPS']['GPSLongitude'])) {
|
||||
$direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLongitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLongitudeRef'] == 'W')) ? -1 : 1);
|
||||
foreach ($info['jpg']['exif']['GPS']['GPSLongitude'] as $key => $value) {
|
||||
$computed_longitude[$key] = getid3_lib::DecimalizeFraction($value);
|
||||
}
|
||||
$info['jpg']['exif']['GPS']['computed']['longitude'] = $direction_multiplier * ($computed_longitude[0] + ($computed_longitude[1] / 60) + ($computed_longitude[2] / 3600));
|
||||
}
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSAltitudeRef'])) {
|
||||
$info['jpg']['exif']['GPS']['GPSAltitudeRef'] = ord($info['jpg']['exif']['GPS']['GPSAltitudeRef']); // 0 = above sea level; 1 = below sea level
|
||||
}
|
||||
if (isset($info['jpg']['exif']['GPS']['GPSAltitude'])) {
|
||||
$direction_multiplier = (!empty($info['jpg']['exif']['GPS']['GPSAltitudeRef']) ? -1 : 1); // 0 = above sea level; 1 = below sea level
|
||||
$info['jpg']['exif']['GPS']['computed']['altitude'] = $direction_multiplier * getid3_lib::DecimalizeFraction($info['jpg']['exif']['GPS']['GPSAltitude']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.xmp.php', __FILE__, true);
|
||||
if (isset($info['filenamepath'])) {
|
||||
$image_xmp = new Image_XMP($info['filenamepath']);
|
||||
$xmp_raw = $image_xmp->getAllTags();
|
||||
foreach ($xmp_raw as $key => $value) {
|
||||
if (strpos($key, ':')) {
|
||||
list($subsection, $tagname) = explode(':', $key);
|
||||
$info['xmp'][$subsection][$tagname] = $this->CastAsAppropriate($value);
|
||||
} else {
|
||||
$info['warning'][] = 'XMP: expecting "<subsection>:<tagname>", found "'.$key.'"';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$returnOK) {
|
||||
unset($info['fileformat']);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function CastAsAppropriate($value) {
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
} elseif (preg_match('#^[0-9]+/[0-9]+$#', $value)) {
|
||||
return getid3_lib::DecimalizeFraction($value);
|
||||
} elseif (preg_match('#^[0-9]+$#', $value)) {
|
||||
return getid3_lib::CastAsInt($value);
|
||||
} elseif (preg_match('#^[0-9\.]+$#', $value)) {
|
||||
return (float) $value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
public function IPTCrecordName($iptc_record) {
|
||||
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
|
||||
static $IPTCrecordName = array();
|
||||
if (empty($IPTCrecordName)) {
|
||||
$IPTCrecordName = array(
|
||||
1 => 'IPTCEnvelope',
|
||||
2 => 'IPTCApplication',
|
||||
3 => 'IPTCNewsPhoto',
|
||||
7 => 'IPTCPreObjectData',
|
||||
8 => 'IPTCObjectData',
|
||||
9 => 'IPTCPostObjectData',
|
||||
);
|
||||
}
|
||||
return (isset($IPTCrecordName[$iptc_record]) ? $IPTCrecordName[$iptc_record] : '');
|
||||
}
|
||||
|
||||
|
||||
public function IPTCrecordTagName($iptc_record, $iptc_tagkey) {
|
||||
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
|
||||
static $IPTCrecordTagName = array();
|
||||
if (empty($IPTCrecordTagName)) {
|
||||
$IPTCrecordTagName = array(
|
||||
1 => array( // IPTC EnvelopeRecord Tags
|
||||
0 => 'EnvelopeRecordVersion',
|
||||
5 => 'Destination',
|
||||
20 => 'FileFormat',
|
||||
22 => 'FileVersion',
|
||||
30 => 'ServiceIdentifier',
|
||||
40 => 'EnvelopeNumber',
|
||||
50 => 'ProductID',
|
||||
60 => 'EnvelopePriority',
|
||||
70 => 'DateSent',
|
||||
80 => 'TimeSent',
|
||||
90 => 'CodedCharacterSet',
|
||||
100 => 'UniqueObjectName',
|
||||
120 => 'ARMIdentifier',
|
||||
122 => 'ARMVersion',
|
||||
),
|
||||
2 => array( // IPTC ApplicationRecord Tags
|
||||
0 => 'ApplicationRecordVersion',
|
||||
3 => 'ObjectTypeReference',
|
||||
4 => 'ObjectAttributeReference',
|
||||
5 => 'ObjectName',
|
||||
7 => 'EditStatus',
|
||||
8 => 'EditorialUpdate',
|
||||
10 => 'Urgency',
|
||||
12 => 'SubjectReference',
|
||||
15 => 'Category',
|
||||
20 => 'SupplementalCategories',
|
||||
22 => 'FixtureIdentifier',
|
||||
25 => 'Keywords',
|
||||
26 => 'ContentLocationCode',
|
||||
27 => 'ContentLocationName',
|
||||
30 => 'ReleaseDate',
|
||||
35 => 'ReleaseTime',
|
||||
37 => 'ExpirationDate',
|
||||
38 => 'ExpirationTime',
|
||||
40 => 'SpecialInstructions',
|
||||
42 => 'ActionAdvised',
|
||||
45 => 'ReferenceService',
|
||||
47 => 'ReferenceDate',
|
||||
50 => 'ReferenceNumber',
|
||||
55 => 'DateCreated',
|
||||
60 => 'TimeCreated',
|
||||
62 => 'DigitalCreationDate',
|
||||
63 => 'DigitalCreationTime',
|
||||
65 => 'OriginatingProgram',
|
||||
70 => 'ProgramVersion',
|
||||
75 => 'ObjectCycle',
|
||||
80 => 'By-line',
|
||||
85 => 'By-lineTitle',
|
||||
90 => 'City',
|
||||
92 => 'Sub-location',
|
||||
95 => 'Province-State',
|
||||
100 => 'Country-PrimaryLocationCode',
|
||||
101 => 'Country-PrimaryLocationName',
|
||||
103 => 'OriginalTransmissionReference',
|
||||
105 => 'Headline',
|
||||
110 => 'Credit',
|
||||
115 => 'Source',
|
||||
116 => 'CopyrightNotice',
|
||||
118 => 'Contact',
|
||||
120 => 'Caption-Abstract',
|
||||
121 => 'LocalCaption',
|
||||
122 => 'Writer-Editor',
|
||||
125 => 'RasterizedCaption',
|
||||
130 => 'ImageType',
|
||||
131 => 'ImageOrientation',
|
||||
135 => 'LanguageIdentifier',
|
||||
150 => 'AudioType',
|
||||
151 => 'AudioSamplingRate',
|
||||
152 => 'AudioSamplingResolution',
|
||||
153 => 'AudioDuration',
|
||||
154 => 'AudioOutcue',
|
||||
184 => 'JobID',
|
||||
185 => 'MasterDocumentID',
|
||||
186 => 'ShortDocumentID',
|
||||
187 => 'UniqueDocumentID',
|
||||
188 => 'OwnerID',
|
||||
200 => 'ObjectPreviewFileFormat',
|
||||
201 => 'ObjectPreviewFileVersion',
|
||||
202 => 'ObjectPreviewData',
|
||||
221 => 'Prefs',
|
||||
225 => 'ClassifyState',
|
||||
228 => 'SimilarityIndex',
|
||||
230 => 'DocumentNotes',
|
||||
231 => 'DocumentHistory',
|
||||
232 => 'ExifCameraInfo',
|
||||
),
|
||||
3 => array( // IPTC NewsPhoto Tags
|
||||
0 => 'NewsPhotoVersion',
|
||||
10 => 'IPTCPictureNumber',
|
||||
20 => 'IPTCImageWidth',
|
||||
30 => 'IPTCImageHeight',
|
||||
40 => 'IPTCPixelWidth',
|
||||
50 => 'IPTCPixelHeight',
|
||||
55 => 'SupplementalType',
|
||||
60 => 'ColorRepresentation',
|
||||
64 => 'InterchangeColorSpace',
|
||||
65 => 'ColorSequence',
|
||||
66 => 'ICC_Profile',
|
||||
70 => 'ColorCalibrationMatrix',
|
||||
80 => 'LookupTable',
|
||||
84 => 'NumIndexEntries',
|
||||
85 => 'ColorPalette',
|
||||
86 => 'IPTCBitsPerSample',
|
||||
90 => 'SampleStructure',
|
||||
100 => 'ScanningDirection',
|
||||
102 => 'IPTCImageRotation',
|
||||
110 => 'DataCompressionMethod',
|
||||
120 => 'QuantizationMethod',
|
||||
125 => 'EndPoints',
|
||||
130 => 'ExcursionTolerance',
|
||||
135 => 'BitsPerComponent',
|
||||
140 => 'MaximumDensityRange',
|
||||
145 => 'GammaCompensatedValue',
|
||||
),
|
||||
7 => array( // IPTC PreObjectData Tags
|
||||
10 => 'SizeMode',
|
||||
20 => 'MaxSubfileSize',
|
||||
90 => 'ObjectSizeAnnounced',
|
||||
95 => 'MaximumObjectSize',
|
||||
),
|
||||
8 => array( // IPTC ObjectData Tags
|
||||
10 => 'SubFile',
|
||||
),
|
||||
9 => array( // IPTC PostObjectData Tags
|
||||
10 => 'ConfirmedObjectSize',
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
return (isset($IPTCrecordTagName[$iptc_record][$iptc_tagkey]) ? $IPTCrecordTagName[$iptc_record][$iptc_tagkey] : $iptc_tagkey);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// also https://github.com/JamesHeinrich/getID3 //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.misc.pdf.php //
|
||||
// module for analyzing PDF files //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_pdf extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
$info['fileformat'] = 'pdf';
|
||||
|
||||
$info['error'][] = 'PDF parsing not enabled in this version of getID3() ['.$this->getid3->version().']';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,371 @@
|
|||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// also https://github.com/JamesHeinrich/getID3 //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.tag.apetag.php //
|
||||
// module for analyzing APE tags //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
class getid3_apetag extends getid3_handler
|
||||
{
|
||||
public $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
|
||||
public $overrideendoffset = 0;
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||
$info['warning'][] = 'Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||
return false;
|
||||
}
|
||||
|
||||
$id3v1tagsize = 128;
|
||||
$apetagheadersize = 32;
|
||||
$lyrics3tagsize = 10;
|
||||
|
||||
if ($this->overrideendoffset == 0) {
|
||||
|
||||
$this->fseek(0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
|
||||
$APEfooterID3v1 = $this->fread($id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
|
||||
|
||||
//if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
|
||||
if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
|
||||
|
||||
// APE tag found before ID3v1
|
||||
$info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
|
||||
|
||||
//} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
|
||||
} elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
|
||||
|
||||
// APE tag found, no ID3v1
|
||||
$info['ape']['tag_offset_end'] = $info['filesize'];
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$this->fseek($this->overrideendoffset - $apetagheadersize);
|
||||
if ($this->fread(8) == 'APETAGEX') {
|
||||
$info['ape']['tag_offset_end'] = $this->overrideendoffset;
|
||||
}
|
||||
|
||||
}
|
||||
if (!isset($info['ape']['tag_offset_end'])) {
|
||||
|
||||
// APE tag not found
|
||||
unset($info['ape']);
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
// shortcut
|
||||
$thisfile_ape = &$info['ape'];
|
||||
|
||||
$this->fseek($thisfile_ape['tag_offset_end'] - $apetagheadersize);
|
||||
$APEfooterData = $this->fread(32);
|
||||
if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
|
||||
$info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end'];
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
|
||||
$this->fseek($thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize);
|
||||
$thisfile_ape['tag_offset_start'] = $this->ftell();
|
||||
$APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
|
||||
} else {
|
||||
$thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
|
||||
$this->fseek($thisfile_ape['tag_offset_start']);
|
||||
$APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize']);
|
||||
}
|
||||
$info['avdataend'] = $thisfile_ape['tag_offset_start'];
|
||||
|
||||
if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
|
||||
$info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data';
|
||||
unset($info['id3v1']);
|
||||
foreach ($info['warning'] as $key => $value) {
|
||||
if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
|
||||
unset($info['warning'][$key]);
|
||||
sort($info['warning']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$offset = 0;
|
||||
if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
|
||||
if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
|
||||
$offset += $apetagheadersize;
|
||||
} else {
|
||||
$info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start'];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// shortcut
|
||||
$info['replay_gain'] = array();
|
||||
$thisfile_replaygain = &$info['replay_gain'];
|
||||
|
||||
for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
|
||||
$value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
|
||||
$offset += 4;
|
||||
$item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
|
||||
$offset += 4;
|
||||
if (strstr(substr($APEtagData, $offset), "\x00") === false) {
|
||||
$info['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset);
|
||||
return false;
|
||||
}
|
||||
$ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
|
||||
$item_key = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
|
||||
|
||||
// shortcut
|
||||
$thisfile_ape['items'][$item_key] = array();
|
||||
$thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];
|
||||
|
||||
$thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;
|
||||
|
||||
$offset += ($ItemKeyLength + 1); // skip 0x00 terminator
|
||||
$thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
|
||||
$offset += $value_size;
|
||||
|
||||
$thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
|
||||
switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
|
||||
case 0: // UTF-8
|
||||
case 3: // Locator (URL, filename, etc), UTF-8 encoded
|
||||
$thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data']));
|
||||
break;
|
||||
|
||||
default: // binary data
|
||||
break;
|
||||
}
|
||||
|
||||
switch (strtolower($item_key)) {
|
||||
case 'replaygain_track_gain':
|
||||
$thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||
$thisfile_replaygain['track']['originator'] = 'unspecified';
|
||||
break;
|
||||
|
||||
case 'replaygain_track_peak':
|
||||
$thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||
$thisfile_replaygain['track']['originator'] = 'unspecified';
|
||||
if ($thisfile_replaygain['track']['peak'] <= 0) {
|
||||
$info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'replaygain_album_gain':
|
||||
$thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||
$thisfile_replaygain['album']['originator'] = 'unspecified';
|
||||
break;
|
||||
|
||||
case 'replaygain_album_peak':
|
||||
$thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
|
||||
$thisfile_replaygain['album']['originator'] = 'unspecified';
|
||||
if ($thisfile_replaygain['album']['peak'] <= 0) {
|
||||
$info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'mp3gain_undo':
|
||||
list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
|
||||
$thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left);
|
||||
$thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
|
||||
$thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false);
|
||||
break;
|
||||
|
||||
case 'mp3gain_minmax':
|
||||
list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
|
||||
$thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
|
||||
$thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
|
||||
break;
|
||||
|
||||
case 'mp3gain_album_minmax':
|
||||
list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
|
||||
$thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
|
||||
$thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
|
||||
break;
|
||||
|
||||
case 'tracknumber':
|
||||
if (is_array($thisfile_ape_items_current['data'])) {
|
||||
foreach ($thisfile_ape_items_current['data'] as $comment) {
|
||||
$thisfile_ape['comments']['track'][] = $comment;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'cover art (artist)':
|
||||
case 'cover art (back)':
|
||||
case 'cover art (band logo)':
|
||||
case 'cover art (band)':
|
||||
case 'cover art (colored fish)':
|
||||
case 'cover art (composer)':
|
||||
case 'cover art (conductor)':
|
||||
case 'cover art (front)':
|
||||
case 'cover art (icon)':
|
||||
case 'cover art (illustration)':
|
||||
case 'cover art (lead)':
|
||||
case 'cover art (leaflet)':
|
||||
case 'cover art (lyricist)':
|
||||
case 'cover art (media)':
|
||||
case 'cover art (movie scene)':
|
||||
case 'cover art (other icon)':
|
||||
case 'cover art (other)':
|
||||
case 'cover art (performance)':
|
||||
case 'cover art (publisher logo)':
|
||||
case 'cover art (recording)':
|
||||
case 'cover art (studio)':
|
||||
// list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
|
||||
list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
|
||||
$thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
|
||||
$thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
|
||||
|
||||
$thisfile_ape_items_current['image_mime'] = '';
|
||||
$imageinfo = array();
|
||||
$imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
|
||||
$thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
|
||||
|
||||
do {
|
||||
if ($this->inline_attachments === false) {
|
||||
// skip entirely
|
||||
unset($thisfile_ape_items_current['data']);
|
||||
break;
|
||||
}
|
||||
if ($this->inline_attachments === true) {
|
||||
// great
|
||||
} elseif (is_int($this->inline_attachments)) {
|
||||
if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
|
||||
// too big, skip
|
||||
$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)';
|
||||
unset($thisfile_ape_items_current['data']);
|
||||
break;
|
||||
}
|
||||
} elseif (is_string($this->inline_attachments)) {
|
||||
$this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
|
||||
if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
|
||||
// cannot write, skip
|
||||
$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
|
||||
unset($thisfile_ape_items_current['data']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if we get this far, must be OK
|
||||
if (is_string($this->inline_attachments)) {
|
||||
$destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
|
||||
if (!file_exists($destination_filename) || is_writable($destination_filename)) {
|
||||
file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
|
||||
} else {
|
||||
$info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
|
||||
}
|
||||
$thisfile_ape_items_current['data_filename'] = $destination_filename;
|
||||
unset($thisfile_ape_items_current['data']);
|
||||
} else {
|
||||
if (!isset($info['ape']['comments']['picture'])) {
|
||||
$info['ape']['comments']['picture'] = array();
|
||||
}
|
||||
$info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']);
|
||||
}
|
||||
} while (false);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (is_array($thisfile_ape_items_current['data'])) {
|
||||
foreach ($thisfile_ape_items_current['data'] as $comment) {
|
||||
$thisfile_ape['comments'][strtolower($item_key)][] = $comment;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (empty($thisfile_replaygain)) {
|
||||
unset($info['replay_gain']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function parseAPEheaderFooter($APEheaderFooterData) {
|
||||
// http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
|
||||
|
||||
// shortcut
|
||||
$headerfooterinfo['raw'] = array();
|
||||
$headerfooterinfo_raw = &$headerfooterinfo['raw'];
|
||||
|
||||
$headerfooterinfo_raw['footer_tag'] = substr($APEheaderFooterData, 0, 8);
|
||||
if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
|
||||
return false;
|
||||
}
|
||||
$headerfooterinfo_raw['version'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 8, 4));
|
||||
$headerfooterinfo_raw['tagsize'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
|
||||
$headerfooterinfo_raw['tag_items'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
|
||||
$headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
|
||||
$headerfooterinfo_raw['reserved'] = substr($APEheaderFooterData, 24, 8);
|
||||
|
||||
$headerfooterinfo['tag_version'] = $headerfooterinfo_raw['version'] / 1000;
|
||||
if ($headerfooterinfo['tag_version'] >= 2) {
|
||||
$headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
|
||||
}
|
||||
return $headerfooterinfo;
|
||||
}
|
||||
|
||||
public function parseAPEtagFlags($rawflagint) {
|
||||
// "Note: APE Tags 1.0 do not use any of the APE Tag flags.
|
||||
// All are set to zero on creation and ignored on reading."
|
||||
// http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html
|
||||
$flags['header'] = (bool) ($rawflagint & 0x80000000);
|
||||
$flags['footer'] = (bool) ($rawflagint & 0x40000000);
|
||||
$flags['this_is_header'] = (bool) ($rawflagint & 0x20000000);
|
||||
$flags['item_contents_raw'] = ($rawflagint & 0x00000006) >> 1;
|
||||
$flags['read_only'] = (bool) ($rawflagint & 0x00000001);
|
||||
|
||||
$flags['item_contents'] = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);
|
||||
|
||||
return $flags;
|
||||
}
|
||||
|
||||
public function APEcontentTypeFlagLookup($contenttypeid) {
|
||||
static $APEcontentTypeFlagLookup = array(
|
||||
0 => 'utf-8',
|
||||
1 => 'binary',
|
||||
2 => 'external',
|
||||
3 => 'reserved'
|
||||
);
|
||||
return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
|
||||
}
|
||||
|
||||
public function APEtagItemIsUTF8Lookup($itemkey) {
|
||||
static $APEtagItemIsUTF8Lookup = array(
|
||||
'title',
|
||||
'subtitle',
|
||||
'artist',
|
||||
'album',
|
||||
'debut album',
|
||||
'publisher',
|
||||
'conductor',
|
||||
'track',
|
||||
'composer',
|
||||
'comment',
|
||||
'copyright',
|
||||
'publicationright',
|
||||
'file',
|
||||
'year',
|
||||
'record date',
|
||||
'record location',
|
||||
'genre',
|
||||
'media',
|
||||
'related',
|
||||
'isrc',
|
||||
'abstract',
|
||||
'language',
|
||||
'bibliography'
|
||||
);
|
||||
return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,360 @@
|
|||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// also https://github.com/JamesHeinrich/getID3 //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// module.tag.id3v1.php //
|
||||
// module for analyzing ID3v1 tags //
|
||||
// dependencies: NONE //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_id3v1 extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||
$info['warning'][] = 'Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->fseek(-256, SEEK_END);
|
||||
$preid3v1 = $this->fread(128);
|
||||
$id3v1tag = $this->fread(128);
|
||||
|
||||
if (substr($id3v1tag, 0, 3) == 'TAG') {
|
||||
|
||||
$info['avdataend'] = $info['filesize'] - 128;
|
||||
|
||||
$ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30));
|
||||
$ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30));
|
||||
$ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30));
|
||||
$ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4));
|
||||
$ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them
|
||||
$ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1));
|
||||
|
||||
// If second-last byte of comment field is null and last byte of comment field is non-null
|
||||
// then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
|
||||
if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) {
|
||||
$ParsedID3v1['track'] = ord(substr($ParsedID3v1['comment'], 29, 1));
|
||||
$ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28);
|
||||
}
|
||||
$ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
|
||||
|
||||
$ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
|
||||
if (!empty($ParsedID3v1['genre'])) {
|
||||
unset($ParsedID3v1['genreid']);
|
||||
}
|
||||
if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
|
||||
unset($ParsedID3v1['genre']);
|
||||
}
|
||||
|
||||
foreach ($ParsedID3v1 as $key => $value) {
|
||||
$ParsedID3v1['comments'][$key][0] = $value;
|
||||
}
|
||||
|
||||
// ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
|
||||
$GoodFormatID3v1tag = $this->GenerateID3v1Tag(
|
||||
$ParsedID3v1['title'],
|
||||
$ParsedID3v1['artist'],
|
||||
$ParsedID3v1['album'],
|
||||
$ParsedID3v1['year'],
|
||||
(isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
|
||||
$ParsedID3v1['comment'],
|
||||
(!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : ''));
|
||||
$ParsedID3v1['padding_valid'] = true;
|
||||
if ($id3v1tag !== $GoodFormatID3v1tag) {
|
||||
$ParsedID3v1['padding_valid'] = false;
|
||||
$info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding';
|
||||
}
|
||||
|
||||
$ParsedID3v1['tag_offset_end'] = $info['filesize'];
|
||||
$ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
|
||||
|
||||
$info['id3v1'] = $ParsedID3v1;
|
||||
}
|
||||
|
||||
if (substr($preid3v1, 0, 3) == 'TAG') {
|
||||
// The way iTunes handles tags is, well, brain-damaged.
|
||||
// It completely ignores v1 if ID3v2 is present.
|
||||
// This goes as far as adding a new v1 tag *even if there already is one*
|
||||
|
||||
// A suspected double-ID3v1 tag has been detected, but it could be that
|
||||
// the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
|
||||
if (substr($preid3v1, 96, 8) == 'APETAGEX') {
|
||||
// an APE tag footer was found before the last ID3v1, assume false "TAG" synch
|
||||
} elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
|
||||
// a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
|
||||
} else {
|
||||
// APE and Lyrics3 footers not found - assume double ID3v1
|
||||
$info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes';
|
||||
$info['avdataend'] -= 128;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function cutfield($str) {
|
||||
return trim(substr($str, 0, strcspn($str, "\x00")));
|
||||
}
|
||||
|
||||
public static function ArrayOfGenres($allowSCMPXextended=false) {
|
||||
static $GenreLookup = array(
|
||||
0 => 'Blues',
|
||||
1 => 'Classic Rock',
|
||||
2 => 'Country',
|
||||
3 => 'Dance',
|
||||
4 => 'Disco',
|
||||
5 => 'Funk',
|
||||
6 => 'Grunge',
|
||||
7 => 'Hip-Hop',
|
||||
8 => 'Jazz',
|
||||
9 => 'Metal',
|
||||
10 => 'New Age',
|
||||
11 => 'Oldies',
|
||||
12 => 'Other',
|
||||
13 => 'Pop',
|
||||
14 => 'R&B',
|
||||
15 => 'Rap',
|
||||
16 => 'Reggae',
|
||||
17 => 'Rock',
|
||||
18 => 'Techno',
|
||||
19 => 'Industrial',
|
||||
20 => 'Alternative',
|
||||
21 => 'Ska',
|
||||
22 => 'Death Metal',
|
||||
23 => 'Pranks',
|
||||
24 => 'Soundtrack',
|
||||
25 => 'Euro-Techno',
|
||||
26 => 'Ambient',
|
||||
27 => 'Trip-Hop',
|
||||
28 => 'Vocal',
|
||||
29 => 'Jazz+Funk',
|
||||
30 => 'Fusion',
|
||||
31 => 'Trance',
|
||||
32 => 'Classical',
|
||||
33 => 'Instrumental',
|
||||
34 => 'Acid',
|
||||
35 => 'House',
|
||||
36 => 'Game',
|
||||
37 => 'Sound Clip',
|
||||
38 => 'Gospel',
|
||||
39 => 'Noise',
|
||||
40 => 'Alt. Rock',
|
||||
41 => 'Bass',
|
||||
42 => 'Soul',
|
||||
43 => 'Punk',
|
||||
44 => 'Space',
|
||||
45 => 'Meditative',
|
||||
46 => 'Instrumental Pop',
|
||||
47 => 'Instrumental Rock',
|
||||
48 => 'Ethnic',
|
||||
49 => 'Gothic',
|
||||
50 => 'Darkwave',
|
||||
51 => 'Techno-Industrial',
|
||||
52 => 'Electronic',
|
||||
53 => 'Pop-Folk',
|
||||
54 => 'Eurodance',
|
||||
55 => 'Dream',
|
||||
56 => 'Southern Rock',
|
||||
57 => 'Comedy',
|
||||
58 => 'Cult',
|
||||
59 => 'Gangsta Rap',
|
||||
60 => 'Top 40',
|
||||
61 => 'Christian Rap',
|
||||
62 => 'Pop/Funk',
|
||||
63 => 'Jungle',
|
||||
64 => 'Native American',
|
||||
65 => 'Cabaret',
|
||||
66 => 'New Wave',
|
||||
67 => 'Psychedelic',
|
||||
68 => 'Rave',
|
||||
69 => 'Showtunes',
|
||||
70 => 'Trailer',
|
||||
71 => 'Lo-Fi',
|
||||
72 => 'Tribal',
|
||||
73 => 'Acid Punk',
|
||||
74 => 'Acid Jazz',
|
||||
75 => 'Polka',
|
||||
76 => 'Retro',
|
||||
77 => 'Musical',
|
||||
78 => 'Rock & Roll',
|
||||
79 => 'Hard Rock',
|
||||
80 => 'Folk',
|
||||
81 => 'Folk/Rock',
|
||||
82 => 'National Folk',
|
||||
83 => 'Swing',
|
||||
84 => 'Fast-Fusion',
|
||||
85 => 'Bebob',
|
||||
86 => 'Latin',
|
||||
87 => 'Revival',
|
||||
88 => 'Celtic',
|
||||
89 => 'Bluegrass',
|
||||
90 => 'Avantgarde',
|
||||
91 => 'Gothic Rock',
|
||||
92 => 'Progressive Rock',
|
||||
93 => 'Psychedelic Rock',
|
||||
94 => 'Symphonic Rock',
|
||||
95 => 'Slow Rock',
|
||||
96 => 'Big Band',
|
||||
97 => 'Chorus',
|
||||
98 => 'Easy Listening',
|
||||
99 => 'Acoustic',
|
||||
100 => 'Humour',
|
||||
101 => 'Speech',
|
||||
102 => 'Chanson',
|
||||
103 => 'Opera',
|
||||
104 => 'Chamber Music',
|
||||
105 => 'Sonata',
|
||||
106 => 'Symphony',
|
||||
107 => 'Booty Bass',
|
||||
108 => 'Primus',
|
||||
109 => 'Porn Groove',
|
||||
110 => 'Satire',
|
||||
111 => 'Slow Jam',
|
||||
112 => 'Club',
|
||||
113 => 'Tango',
|
||||
114 => 'Samba',
|
||||
115 => 'Folklore',
|
||||
116 => 'Ballad',
|
||||
117 => 'Power Ballad',
|
||||
118 => 'Rhythmic Soul',
|
||||
119 => 'Freestyle',
|
||||
120 => 'Duet',
|
||||
121 => 'Punk Rock',
|
||||
122 => 'Drum Solo',
|
||||
123 => 'A Cappella',
|
||||
124 => 'Euro-House',
|
||||
125 => 'Dance Hall',
|
||||
126 => 'Goa',
|
||||
127 => 'Drum & Bass',
|
||||
128 => 'Club-House',
|
||||
129 => 'Hardcore',
|
||||
130 => 'Terror',
|
||||
131 => 'Indie',
|
||||
132 => 'BritPop',
|
||||
133 => 'Negerpunk',
|
||||
134 => 'Polsk Punk',
|
||||
135 => 'Beat',
|
||||
136 => 'Christian Gangsta Rap',
|
||||
137 => 'Heavy Metal',
|
||||
138 => 'Black Metal',
|
||||
139 => 'Crossover',
|
||||
140 => 'Contemporary Christian',
|
||||
141 => 'Christian Rock',
|
||||
142 => 'Merengue',
|
||||
143 => 'Salsa',
|
||||
144 => 'Thrash Metal',
|
||||
145 => 'Anime',
|
||||
146 => 'JPop',
|
||||
147 => 'Synthpop',
|
||||
|
||||
255 => 'Unknown',
|
||||
|
||||
'CR' => 'Cover',
|
||||
'RX' => 'Remix'
|
||||
);
|
||||
|
||||
static $GenreLookupSCMPX = array();
|
||||
if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
|
||||
$GenreLookupSCMPX = $GenreLookup;
|
||||
// http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
|
||||
// Extended ID3v1 genres invented by SCMPX
|
||||
// Note that 255 "Japanese Anime" conflicts with standard "Unknown"
|
||||
$GenreLookupSCMPX[240] = 'Sacred';
|
||||
$GenreLookupSCMPX[241] = 'Northern Europe';
|
||||
$GenreLookupSCMPX[242] = 'Irish & Scottish';
|
||||
$GenreLookupSCMPX[243] = 'Scotland';
|
||||
$GenreLookupSCMPX[244] = 'Ethnic Europe';
|
||||
$GenreLookupSCMPX[245] = 'Enka';
|
||||
$GenreLookupSCMPX[246] = 'Children\'s Song';
|
||||
$GenreLookupSCMPX[247] = 'Japanese Sky';
|
||||
$GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
|
||||
$GenreLookupSCMPX[249] = 'Japanese Doom Rock';
|
||||
$GenreLookupSCMPX[250] = 'Japanese J-POP';
|
||||
$GenreLookupSCMPX[251] = 'Japanese Seiyu';
|
||||
$GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
|
||||
$GenreLookupSCMPX[253] = 'Japanese Moemoe';
|
||||
$GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
|
||||
//$GenreLookupSCMPX[255] = 'Japanese Anime';
|
||||
}
|
||||
|
||||
return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
|
||||
}
|
||||
|
||||
public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
|
||||
switch ($genreid) {
|
||||
case 'RX':
|
||||
case 'CR':
|
||||
break;
|
||||
default:
|
||||
if (!is_numeric($genreid)) {
|
||||
return false;
|
||||
}
|
||||
$genreid = intval($genreid); // to handle 3 or '3' or '03'
|
||||
break;
|
||||
}
|
||||
$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
|
||||
return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
|
||||
}
|
||||
|
||||
public static function LookupGenreID($genre, $allowSCMPXextended=false) {
|
||||
$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
|
||||
$LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
|
||||
foreach ($GenreLookup as $key => $value) {
|
||||
if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function StandardiseID3v1GenreName($OriginalGenre) {
|
||||
if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
|
||||
return self::LookupGenreName($GenreID);
|
||||
}
|
||||
return $OriginalGenre;
|
||||
}
|
||||
|
||||
public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
|
||||
$ID3v1Tag = 'TAG';
|
||||
$ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||
$ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||
$ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||
$ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT);
|
||||
if (!empty($track) && ($track > 0) && ($track <= 255)) {
|
||||
$ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
|
||||
$ID3v1Tag .= "\x00";
|
||||
if (gettype($track) == 'string') {
|
||||
$track = (int) $track;
|
||||
}
|
||||
$ID3v1Tag .= chr($track);
|
||||
} else {
|
||||
$ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
|
||||
}
|
||||
if (($genreid < 0) || ($genreid > 147)) {
|
||||
$genreid = 255; // 'unknown' genre
|
||||
}
|
||||
switch (gettype($genreid)) {
|
||||
case 'string':
|
||||
case 'integer':
|
||||
$ID3v1Tag .= chr(intval($genreid));
|
||||
break;
|
||||
default:
|
||||
$ID3v1Tag .= chr(255); // 'unknown' genre
|
||||
break;
|
||||
}
|
||||
|
||||
return $ID3v1Tag;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,294 @@
|
|||
<?php
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// getID3() by James Heinrich <info@getid3.org> //
|
||||
// available at http://getid3.sourceforge.net //
|
||||
// or http://www.getid3.org //
|
||||
// also https://github.com/JamesHeinrich/getID3 //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// See readme.txt for more details //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
/// //
|
||||
// module.tag.lyrics3.php //
|
||||
// module for analyzing Lyrics3 tags //
|
||||
// dependencies: module.tag.apetag.php (optional) //
|
||||
// ///
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class getid3_lyrics3 extends getid3_handler
|
||||
{
|
||||
|
||||
public function Analyze() {
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
// http://www.volweb.cz/str/tags.htm
|
||||
|
||||
if (!getid3_lib::intValueSupported($info['filesize'])) {
|
||||
$info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->fseek((0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size]
|
||||
$lyrics3_id3v1 = $this->fread(128 + 9 + 6);
|
||||
$lyrics3lsz = substr($lyrics3_id3v1, 0, 6); // Lyrics3size
|
||||
$lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200
|
||||
$id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1
|
||||
|
||||
if ($lyrics3end == 'LYRICSEND') {
|
||||
// Lyrics3v1, ID3v1, no APE
|
||||
|
||||
$lyrics3size = 5100;
|
||||
$lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
|
||||
$lyrics3version = 1;
|
||||
|
||||
} elseif ($lyrics3end == 'LYRICS200') {
|
||||
// Lyrics3v2, ID3v1, no APE
|
||||
|
||||
// LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
|
||||
$lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200');
|
||||
$lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
|
||||
$lyrics3version = 2;
|
||||
|
||||
} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) {
|
||||
// Lyrics3v1, no ID3v1, no APE
|
||||
|
||||
$lyrics3size = 5100;
|
||||
$lyrics3offset = $info['filesize'] - $lyrics3size;
|
||||
$lyrics3version = 1;
|
||||
$lyrics3offset = $info['filesize'] - $lyrics3size;
|
||||
|
||||
} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) {
|
||||
|
||||
// Lyrics3v2, no ID3v1, no APE
|
||||
|
||||
$lyrics3size = strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
|
||||
$lyrics3offset = $info['filesize'] - $lyrics3size;
|
||||
$lyrics3version = 2;
|
||||
|
||||
} else {
|
||||
|
||||
if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) {
|
||||
|
||||
$this->fseek($info['ape']['tag_offset_start'] - 15);
|
||||
$lyrics3lsz = $this->fread(6);
|
||||
$lyrics3end = $this->fread(9);
|
||||
|
||||
if ($lyrics3end == 'LYRICSEND') {
|
||||
// Lyrics3v1, APE, maybe ID3v1
|
||||
|
||||
$lyrics3size = 5100;
|
||||
$lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
|
||||
$info['avdataend'] = $lyrics3offset;
|
||||
$lyrics3version = 1;
|
||||
$info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability';
|
||||
|
||||
} elseif ($lyrics3end == 'LYRICS200') {
|
||||
// Lyrics3v2, APE, maybe ID3v1
|
||||
|
||||
$lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
|
||||
$lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
|
||||
$lyrics3version = 2;
|
||||
$info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (isset($lyrics3offset)) {
|
||||
$info['avdataend'] = $lyrics3offset;
|
||||
$this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size);
|
||||
|
||||
if (!isset($info['ape'])) {
|
||||
$GETID3_ERRORARRAY = &$info['warning'];
|
||||
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true);
|
||||
$getid3_temp = new getID3();
|
||||
$getid3_temp->openfile($this->getid3->filename);
|
||||
$getid3_apetag = new getid3_apetag($getid3_temp);
|
||||
$getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start'];
|
||||
$getid3_apetag->Analyze();
|
||||
if (!empty($getid3_temp->info['ape'])) {
|
||||
$info['ape'] = $getid3_temp->info['ape'];
|
||||
}
|
||||
if (!empty($getid3_temp->info['replay_gain'])) {
|
||||
$info['replay_gain'] = $getid3_temp->info['replay_gain'];
|
||||
}
|
||||
unset($getid3_temp, $getid3_apetag);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getLyrics3Data($endoffset, $version, $length) {
|
||||
// http://www.volweb.cz/str/tags.htm
|
||||
|
||||
$info = &$this->getid3->info;
|
||||
|
||||
if (!getid3_lib::intValueSupported($endoffset)) {
|
||||
$info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->fseek($endoffset);
|
||||
if ($length <= 0) {
|
||||
return false;
|
||||
}
|
||||
$rawdata = $this->fread($length);
|
||||
|
||||
$ParsedLyrics3['raw']['lyrics3version'] = $version;
|
||||
$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
|
||||
$ParsedLyrics3['tag_offset_start'] = $endoffset;
|
||||
$ParsedLyrics3['tag_offset_end'] = $endoffset + $length - 1;
|
||||
|
||||
if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') {
|
||||
if (strpos($rawdata, 'LYRICSBEGIN') !== false) {
|
||||
|
||||
$info['warning'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version;
|
||||
$info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN');
|
||||
$rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN'));
|
||||
$length = strlen($rawdata);
|
||||
$ParsedLyrics3['tag_offset_start'] = $info['avdataend'];
|
||||
$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
|
||||
|
||||
} else {
|
||||
|
||||
$info['error'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead';
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch ($version) {
|
||||
|
||||
case 1:
|
||||
if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICSEND') {
|
||||
$ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9));
|
||||
$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
|
||||
} else {
|
||||
$info['error'][] = '"LYRICSEND" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead';
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICS200') {
|
||||
$ParsedLyrics3['raw']['unparsed'] = substr($rawdata, 11, strlen($rawdata) - 11 - 9 - 6); // LYRICSBEGIN + LYRICS200 + LSZ
|
||||
$rawdata = $ParsedLyrics3['raw']['unparsed'];
|
||||
while (strlen($rawdata) > 0) {
|
||||
$fieldname = substr($rawdata, 0, 3);
|
||||
$fieldsize = (int) substr($rawdata, 3, 5);
|
||||
$ParsedLyrics3['raw'][$fieldname] = substr($rawdata, 8, $fieldsize);
|
||||
$rawdata = substr($rawdata, 3 + 5 + $fieldsize);
|
||||
}
|
||||
|
||||
if (isset($ParsedLyrics3['raw']['IND'])) {
|
||||
$i = 0;
|
||||
$flagnames = array('lyrics', 'timestamps', 'inhibitrandom');
|
||||
foreach ($flagnames as $flagname) {
|
||||
if (strlen($ParsedLyrics3['raw']['IND']) > $i++) {
|
||||
$ParsedLyrics3['flags'][$flagname] = $this->IntString2Bool(substr($ParsedLyrics3['raw']['IND'], $i, 1 - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fieldnametranslation = array('ETT'=>'title', 'EAR'=>'artist', 'EAL'=>'album', 'INF'=>'comment', 'AUT'=>'author');
|
||||
foreach ($fieldnametranslation as $key => $value) {
|
||||
if (isset($ParsedLyrics3['raw'][$key])) {
|
||||
$ParsedLyrics3['comments'][$value][] = trim($ParsedLyrics3['raw'][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($ParsedLyrics3['raw']['IMG'])) {
|
||||
$imagestrings = explode("\r\n", $ParsedLyrics3['raw']['IMG']);
|
||||
foreach ($imagestrings as $key => $imagestring) {
|
||||
if (strpos($imagestring, '||') !== false) {
|
||||
$imagearray = explode('||', $imagestring);
|
||||
$ParsedLyrics3['images'][$key]['filename'] = (isset($imagearray[0]) ? $imagearray[0] : '');
|
||||
$ParsedLyrics3['images'][$key]['description'] = (isset($imagearray[1]) ? $imagearray[1] : '');
|
||||
$ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(isset($imagearray[2]) ? $imagearray[2] : '');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($ParsedLyrics3['raw']['LYR'])) {
|
||||
$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
|
||||
}
|
||||
} else {
|
||||
$info['error'][] = '"LYRICS200" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead';
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'][] = 'Cannot process Lyrics3 version '.$version.' (only v1 and v2)';
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end'])) {
|
||||
$info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data';
|
||||
unset($info['id3v1']);
|
||||
foreach ($info['warning'] as $key => $value) {
|
||||
if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
|
||||
unset($info['warning'][$key]);
|
||||
sort($info['warning']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$info['lyrics3'] = $ParsedLyrics3;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function Lyrics3Timestamp2Seconds($rawtimestamp) {
|
||||
if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) {
|
||||
return (int) (($regs[1] * 60) + $regs[2]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function Lyrics3LyricsTimestampParse(&$Lyrics3data) {
|
||||
$lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']);
|
||||
foreach ($lyricsarray as $key => $lyricline) {
|
||||
$regs = array();
|
||||
unset($thislinetimestamps);
|
||||
while (preg_match('#^(\\[[0-9]{2}:[0-9]{2}\\])#', $lyricline, $regs)) {
|
||||
$thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]);
|
||||
$lyricline = str_replace($regs[0], '', $lyricline);
|
||||
}
|
||||
$notimestamplyricsarray[$key] = $lyricline;
|
||||
if (isset($thislinetimestamps) && is_array($thislinetimestamps)) {
|
||||
sort($thislinetimestamps);
|
||||
foreach ($thislinetimestamps as $timestampkey => $timestamp) {
|
||||
if (isset($Lyrics3data['synchedlyrics'][$timestamp])) {
|
||||
// timestamps only have a 1-second resolution, it's possible that multiple lines
|
||||
// could have the same timestamp, if so, append
|
||||
$Lyrics3data['synchedlyrics'][$timestamp] .= "\r\n".$lyricline;
|
||||
} else {
|
||||
$Lyrics3data['synchedlyrics'][$timestamp] = $lyricline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$Lyrics3data['unsynchedlyrics'] = implode("\r\n", $notimestamplyricsarray);
|
||||
if (isset($Lyrics3data['synchedlyrics']) && is_array($Lyrics3data['synchedlyrics'])) {
|
||||
ksort($Lyrics3data['synchedlyrics']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function IntString2Bool($char) {
|
||||
if ($char == '1') {
|
||||
return true;
|
||||
} elseif ($char == '0') {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PodcastGenerator>
|
||||
|
||||
<category>
|
||||
<id></id>
|
||||
<description></description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Arts</id>
|
||||
<description>Arts</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Arts:Design</id>
|
||||
<description>Arts:Design</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Arts:Fashion & Beauty</id>
|
||||
<description>Arts:Fashion & Beauty</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Arts:Food</id>
|
||||
<description>Arts:Food</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Arts:Literature</id>
|
||||
<description>Arts:Literature</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Arts:Performing Arts</id>
|
||||
<description>Arts:Performing Arts</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Arts:Visual Arts</id>
|
||||
<description>Arts:Visual Arts</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Business</id>
|
||||
<description>Business</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Business:Business News</id>
|
||||
<description>Business:Business News</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Business:Careers</id>
|
||||
<description>Business:Careers</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Business:Investing</id>
|
||||
<description>Business:Investing</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Business:Management & Marketing</id>
|
||||
<description>Business:Management & Marketing</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Business:Shopping</id>
|
||||
<description>Business:Shopping</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Comedy</id>
|
||||
<description>Comedy</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Education</id>
|
||||
<description>Education</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Education:Education Technology</id>
|
||||
<description>Education:Education Technology</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Education:Higher Education</id>
|
||||
<description>Education:Higher Education</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Education:K-12</id>
|
||||
<description>Education:K-12</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Education:Language Courses</id>
|
||||
<description>Education:Language Courses</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Education:Training</id>
|
||||
<description>Education:Training</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Games & Hobbies</id>
|
||||
<description>Games & Hobbies</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Games & Hobbies:Automotive</id>
|
||||
<description>Games & Hobbies:Automotive</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Games & Hobbies:Aviation</id>
|
||||
<description>Games & Hobbies:Aviation</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Games & Hobbies:Hobbies</id>
|
||||
<description>Games & Hobbies:Hobbies</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Games & Hobbies:Other Games</id>
|
||||
<description>Games & Hobbies:Other Games</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Games & Hobbies:Video Games</id>
|
||||
<description>Games & Hobbies:Video Games</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Government & Organizations</id>
|
||||
<description>Government & Organizations</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Government & Organizations:Local</id>
|
||||
<description>Government & Organizations:Local</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Government & Organizations:National</id>
|
||||
<description>Government & Organizations:National</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Government & Organizations:Non-Profit</id>
|
||||
<description>Government & Organizations:Non-Profit</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Government & Organizations:Regional</id>
|
||||
<description>Government & Organizations:Regional</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Health</id>
|
||||
<description>Health</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Health:Alternative Health</id>
|
||||
<description>Health:Alternative Health</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Health:Fitness & Nutrition</id>
|
||||
<description>Health:Fitness & Nutrition</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Health:Self-Help</id>
|
||||
<description>Health:Self-Help</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Health:Sexuality</id>
|
||||
<description>Health:Sexuality</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Kids & Family</id>
|
||||
<description>Kids & Family</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Music</id>
|
||||
<description>Music</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>News & Politics</id>
|
||||
<description>News & Politics</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Religion & Spirituality</id>
|
||||
<description>Religion & Spirituality</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Religion & Spirituality:Buddhism</id>
|
||||
<description>Religion & Spirituality:Buddhism</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Religion & Spirituality:Christianity</id>
|
||||
<description>Religion & Spirituality:Christianity</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Religion & Spirituality:Hinduism</id>
|
||||
<description>Religion & Spirituality:Hinduism</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Religion & Spirituality:Islam</id>
|
||||
<description>Religion & Spirituality:Islam</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Religion & Spirituality:Judaism</id>
|
||||
<description>Religion & Spirituality:Judaism</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Religion & Spirituality:Other</id>
|
||||
<description>Religion & Spirituality:Other</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Religion & Spirituality:Spirituality</id>
|
||||
<description>Religion & Spirituality:Spirituality</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Science & Medicine</id>
|
||||
<description>Science & Medicine</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Science & Medicine:Medicine</id>
|
||||
<description>Science & Medicine:Medicine</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Science & Medicine:Natural Sciences</id>
|
||||
<description>Science & Medicine:Natural Sciences</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Science & Medicine:Social Sciences</id>
|
||||
<description>Science & Medicine:Social Sciences</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Society & Culture</id>
|
||||
<description>Society & Culture</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Society & Culture:History</id>
|
||||
<description>Society & Culture:History</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Society & Culture:Personal Journals</id>
|
||||
<description>Society & Culture:Personal Journals</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Society & Culture:Philosophy</id>
|
||||
<description>Society & Culture:Philosophy</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Society & Culture:Places & Travel</id>
|
||||
<description>Society & Culture:Places & Travel</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Sports & Recreation</id>
|
||||
<description>Sports & Recreation</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Sports & Recreation:Amateur</id>
|
||||
<description>Sports & Recreation:Amateur</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Sports & Recreation:College & High School</id>
|
||||
<description>Sports & Recreation:College & High School</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Sports & Recreation:Outdoor</id>
|
||||
<description>Sports & Recreation:Outdoor</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Sports & Recreation:Professional</id>
|
||||
<description>Sports & Recreation:Professional</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>Technology</id>
|
||||
<description>Technology</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Technology:Gadgets</id>
|
||||
<description>Technology:Gadgets</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Technology:Tech News</id>
|
||||
<description>Technology:Tech News</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Technology:Podcasting</id>
|
||||
<description>Technology:Podcasting</description>
|
||||
</category>
|
||||
<category>
|
||||
<id>Technology:Software How-To</id>
|
||||
<description>Technology:Software How-To</description>
|
||||
</category>
|
||||
|
||||
<category>
|
||||
<id>TV & Film</id>
|
||||
<description>TV & Film</description>
|
||||
</category>
|
||||
|
||||
</PodcastGenerator>
|
|
@ -0,0 +1,86 @@
|
|||
function cnt(w,x){
|
||||
var y=w.value;
|
||||
var r = 0;
|
||||
a=y.replace(/\s/g,' ');
|
||||
a=a.split(',');
|
||||
for (z=0; z<a.length; z++) {if (a[z].length > 0) r++;}
|
||||
x.value=r;
|
||||
}
|
||||
|
||||
|
||||
function limitText(limitField, limitCount, limitNum) {
|
||||
if (limitField.value.length > limitNum) {
|
||||
limitField.value = limitField.value.substring(0, limitNum);
|
||||
} else {
|
||||
limitCount.value = limitNum - limitField.value.length;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function checkMaxSelected (select, maxSelected, displ_error_nummaxcat) {
|
||||
if (!select.storeSelections) {
|
||||
select.storeSelections = new Array(select.options.length);
|
||||
select.optionsSelected = 0;
|
||||
}
|
||||
|
||||
for (var i = 0; i < select.options.length; i++) {
|
||||
if (select.options[i].selected && !select.storeSelections[i]) {
|
||||
if (select.optionsSelected < maxSelected) {
|
||||
select.storeSelections[i] = true;
|
||||
select.optionsSelected++;
|
||||
}
|
||||
else {
|
||||
alert(displ_error_nummaxcat + maxSelected);
|
||||
select.options[i].selected = false;
|
||||
}
|
||||
}
|
||||
else if (!select.options[i].selected && select.storeSelections[i]) {
|
||||
select.storeSelections[i] = false;
|
||||
select.optionsSelected--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////
|
||||
// DISPLAY LOADER
|
||||
|
||||
function getScrollTop() {
|
||||
if ( document.documentElement.scrollTop )
|
||||
return document.documentElement.scrollTop;
|
||||
return document.body.scrollTop;
|
||||
}
|
||||
|
||||
function showNotify( str ) {
|
||||
var elem = document.getElementById('status_notification');
|
||||
elem.style.display = 'block';
|
||||
elem.style.visibility = 'visible';
|
||||
|
||||
if ( elem.currentStyle && elem.currentStyle.position == 'absolute' ) {
|
||||
elem.style.top = getScrollTop();
|
||||
}
|
||||
|
||||
elem.innerHTML = str;
|
||||
}
|
||||
|
||||
function hideNotify() {
|
||||
var elem = document.getElementById('status_notification');
|
||||
elem.style.display = 'none';
|
||||
elem.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
window.onscroll = function () {
|
||||
var elem = document.getElementById('status_notification');
|
||||
if ( !elem.currentStyle || elem.currentStyle.position != 'absolute' ) {
|
||||
window.onscroll = null;
|
||||
} else {
|
||||
window.onscroll = function () { elem.style.top = getScrollTop(); };
|
||||
document.getElementById('status_notification').style.top = getScrollTop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// END - DISPLAY LOADER
|
||||
///////////////////////
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
<?php
|
||||
/*
|
||||
======================================================================
|
||||
lastRSS 0.9.1
|
||||
|
||||
Simple yet powerfull PHP class to parse RSS files.
|
||||
|
||||
by Vojtech Semecky, webmaster @ webdot . cz
|
||||
|
||||
Latest version, features, manual and examples:
|
||||
http://lastrss.webdot.cz/
|
||||
|
||||
----------------------------------------------------------------------
|
||||
LICENSE
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License (GPL)
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
To read the license please visit http://www.gnu.org/copyleft/gpl.html
|
||||
======================================================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* lastRSS
|
||||
* Simple yet powerfull PHP class to parse RSS files.
|
||||
*/
|
||||
class lastRSS {
|
||||
// -------------------------------------------------------------------
|
||||
// Public properties
|
||||
// -------------------------------------------------------------------
|
||||
var $default_cp = 'UTF-8';
|
||||
var $CDATA = 'nochange';
|
||||
var $cp = '';
|
||||
var $items_limit = 1;
|
||||
var $stripHTML = False;
|
||||
var $date_format = 'M d, Y g:i A';
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Private variables
|
||||
// -------------------------------------------------------------------
|
||||
var $channeltags = array ('title', 'link', 'description', 'language', 'copyright', 'managingEditor', 'webMaster', 'lastBuildDate', 'rating', 'docs');
|
||||
var $itemtags = array('title', 'link', 'description', 'author', 'category', 'comments', 'enclosure', 'guid', 'pubDate', 'source');
|
||||
var $imagetags = array('title', 'url', 'link', 'width', 'height');
|
||||
var $textinputtags = array('title', 'description', 'name', 'link');
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Parse RSS file and returns associative array.
|
||||
// -------------------------------------------------------------------
|
||||
function Get ($rss_url) {
|
||||
// If CACHE ENABLED
|
||||
if ($this->cache_dir != '') {
|
||||
$cache_file = $this->cache_dir . '/rsscache_' . md5($rss_url);
|
||||
$timedif = @(time() - filemtime($cache_file));
|
||||
if ($timedif < $this->cache_time) {
|
||||
// cached file is fresh enough, return cached array
|
||||
$result = unserialize(join('', file($cache_file)));
|
||||
// set 'cached' to 1 only if cached file is correct
|
||||
if ($result) $result['cached'] = 1;
|
||||
} else {
|
||||
// cached file is too old, create new
|
||||
$result = $this->Parse($rss_url);
|
||||
$serialized = serialize($result);
|
||||
if ($f = @fopen($cache_file, 'w')) {
|
||||
fwrite ($f, $serialized, strlen($serialized));
|
||||
fclose($f);
|
||||
}
|
||||
if ($result) $result['cached'] = 0;
|
||||
}
|
||||
}
|
||||
// If CACHE DISABLED >> load and parse the file directly
|
||||
else {
|
||||
$result = $this->Parse($rss_url);
|
||||
if ($result) $result['cached'] = 0;
|
||||
}
|
||||
// return result
|
||||
return $result;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Modification of preg_match(); return trimed field with index 1
|
||||
// from 'classic' preg_match() array output
|
||||
// -------------------------------------------------------------------
|
||||
function my_preg_match ($pattern, $subject) {
|
||||
// start regullar expression
|
||||
preg_match($pattern, $subject, $out);
|
||||
|
||||
// if there is some result... process it and return it
|
||||
if(isset($out[1])) {
|
||||
// Process CDATA (if present)
|
||||
if ($this->CDATA == 'content') { // Get CDATA content (without CDATA tag)
|
||||
$out[1] = strtr($out[1], array('<![CDATA['=>'', ']]>'=>''));
|
||||
} elseif ($this->CDATA == 'strip') { // Strip CDATA
|
||||
$out[1] = strtr($out[1], array('<![CDATA['=>'', ']]>'=>''));
|
||||
}
|
||||
|
||||
// If code page is set convert character encoding to required
|
||||
if ($this->cp != '')
|
||||
//$out[1] = $this->MyConvertEncoding($this->rsscp, $this->cp, $out[1]);
|
||||
$out[1] = iconv($this->rsscp, $this->cp.'//TRANSLIT', $out[1]);
|
||||
// Return result
|
||||
return trim($out[1]);
|
||||
} else {
|
||||
// if there is NO result, return empty string
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Replace HTML entities &something; by real characters
|
||||
// -------------------------------------------------------------------
|
||||
function unhtmlentities ($string) {
|
||||
// Get HTML entities table
|
||||
$trans_tbl = get_html_translation_table (HTML_ENTITIES, ENT_QUOTES);
|
||||
// Flip keys<==>values
|
||||
$trans_tbl = array_flip ($trans_tbl);
|
||||
// Add support for ' entity (missing in HTML_ENTITIES)
|
||||
$trans_tbl += array(''' => "'");
|
||||
// Replace entities by values
|
||||
return strtr ($string, $trans_tbl);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Parse() is private method used by Get() to load and parse RSS file.
|
||||
// Don't use Parse() in your scripts - use Get($rss_file) instead.
|
||||
// -------------------------------------------------------------------
|
||||
function Parse ($rss_url) {
|
||||
// Open and load RSS file
|
||||
if ($f = @fopen($rss_url, 'r')) {
|
||||
$rss_content = '';
|
||||
while (!feof($f)) {
|
||||
$rss_content .= fgets($f, 4096);
|
||||
}
|
||||
fclose($f);
|
||||
|
||||
// Parse document encoding
|
||||
$result['encoding'] = $this->my_preg_match("'encoding=[\'\"](.*?)[\'\"]'si", $rss_content);
|
||||
// if document codepage is specified, use it
|
||||
if ($result['encoding'] != '')
|
||||
{ $this->rsscp = $result['encoding']; } // This is used in my_preg_match()
|
||||
// otherwise use the default codepage
|
||||
else
|
||||
{ $this->rsscp = $this->default_cp; } // This is used in my_preg_match()
|
||||
|
||||
// Parse CHANNEL info
|
||||
preg_match("'<channel.*?>(.*?)</channel>'si", $rss_content, $out_channel);
|
||||
foreach($this->channeltags as $channeltag)
|
||||
{
|
||||
$temp = $this->my_preg_match("'<$channeltag.*?>(.*?)</$channeltag>'si", $out_channel[1]);
|
||||
if ($temp != '') $result[$channeltag] = $temp; // Set only if not empty
|
||||
}
|
||||
// If date_format is specified and lastBuildDate is valid
|
||||
if ($this->date_format != '' && ($timestamp = strtotime($result['lastBuildDate'])) !==-1) {
|
||||
// convert lastBuildDate to specified date format
|
||||
$result['lastBuildDate'] = date($this->date_format, $timestamp);
|
||||
}
|
||||
|
||||
// Parse TEXTINPUT info
|
||||
preg_match("'<textinput(|[^>]*[^/])>(.*?)</textinput>'si", $rss_content, $out_textinfo);
|
||||
// This a little strange regexp means:
|
||||
// Look for tag <textinput> with or without any attributes, but skip truncated version <textinput /> (it's not beggining tag)
|
||||
if (isset($out_textinfo[2])) {
|
||||
foreach($this->textinputtags as $textinputtag) {
|
||||
$temp = $this->my_preg_match("'<$textinputtag.*?>(.*?)</$textinputtag>'si", $out_textinfo[2]);
|
||||
if ($temp != '') $result['textinput_'.$textinputtag] = $temp; // Set only if not empty
|
||||
}
|
||||
}
|
||||
// Parse IMAGE info
|
||||
preg_match("'<image.*?>(.*?)</image>'si", $rss_content, $out_imageinfo);
|
||||
if (isset($out_imageinfo[1])) {
|
||||
foreach($this->imagetags as $imagetag) {
|
||||
$temp = $this->my_preg_match("'<$imagetag.*?>(.*?)</$imagetag>'si", $out_imageinfo[1]);
|
||||
if ($temp != '') $result['image_'.$imagetag] = $temp; // Set only if not empty
|
||||
}
|
||||
}
|
||||
// Parse ITEMS
|
||||
preg_match_all("'<item(| .*?)>(.*?)</item>'si", $rss_content, $items);
|
||||
$rss_items = $items[2];
|
||||
$i = 0;
|
||||
$result['items'] = array(); // create array even if there are no items
|
||||
foreach($rss_items as $rss_item) {
|
||||
// If number of items is lower then limit: Parse one item
|
||||
if ($i < $this->items_limit || $this->items_limit == 0) {
|
||||
foreach($this->itemtags as $itemtag) {
|
||||
$temp = $this->my_preg_match("'<$itemtag.*?>(.*?)</$itemtag>'si", $rss_item);
|
||||
if ($temp != '') $result['items'][$i][$itemtag] = $temp; // Set only if not empty
|
||||
}
|
||||
// Strip HTML tags and other bullshit from DESCRIPTION
|
||||
if ($this->stripHTML && $result['items'][$i]['description'])
|
||||
$result['items'][$i]['description'] = strip_tags($this->unhtmlentities(strip_tags($result['items'][$i]['description'])));
|
||||
// Strip HTML tags and other bullshit from TITLE
|
||||
if ($this->stripHTML && $result['items'][$i]['title'])
|
||||
$result['items'][$i]['title'] = strip_tags($this->unhtmlentities(strip_tags($result['items'][$i]['title'])));
|
||||
// If date_format is specified and pubDate is valid
|
||||
if ($this->date_format != '' && ($timestamp = strtotime($result['items'][$i]['pubDate'])) !==-1) {
|
||||
// convert pubDate to specified date format
|
||||
$result['items'][$i]['pubDate'] = date($this->date_format, $timestamp);
|
||||
}
|
||||
// Item counter
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
$result['items_count'] = $i;
|
||||
return $result;
|
||||
}
|
||||
else // Error in opening return False
|
||||
{
|
||||
return False;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,2 @@
|
|||
See this online document:
|
||||
http://www.podcastgenerator.net/documentation/FAQ-localization
|
|
@ -0,0 +1,259 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PodcastGenerator>
|
||||
|
||||
<language>
|
||||
<id>ar_SA</id>
|
||||
<description>العربية</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>as_IN</id>
|
||||
<description>অসমীয়া</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>ast_ES</id>
|
||||
<description>Asturianu</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>bg_BG</id>
|
||||
<description>Български</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>bn_IN</id>
|
||||
<description>বাংলা</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>bs_BA</id>
|
||||
<description>Bosanski</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>ca_ES</id>
|
||||
<description>Català</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>cs_CS</id>
|
||||
<description>Česky</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>da_DK</id>
|
||||
<description>Dansk</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>de_DE</id>
|
||||
<description>Deutsch</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>ee_ET</id>
|
||||
<description>Eesti</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>el_GR</id>
|
||||
<description>ελληνικά</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>en_EN</id>
|
||||
<description>English</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>es_ES</id>
|
||||
<description>Español</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>fa_IR</id>
|
||||
<description>فارسی</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>fi_FI</id>
|
||||
<description>Suomi</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>fr_FR</id>
|
||||
<description>Français</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>gl_ES</id>
|
||||
<description>Galego</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>gu_IN</id>
|
||||
<description>ગુજરાતી</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>he_IL</id>
|
||||
<description>עברית</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>hi_IN</id>
|
||||
<description>हिन्दी</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>hr_HR</id>
|
||||
<description>Hrvatski</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>hu_HU</id>
|
||||
<description>Magyar</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>id_ID</id>
|
||||
<description>Bahasa Indonesia</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>is_IS</id>
|
||||
<description>Icelandic</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>it_IT</id>
|
||||
<description>Italiano</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>ja_JP</id>
|
||||
<description>日本語</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>kn_IN</id>
|
||||
<description>Kannada</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>ko_KR</id>
|
||||
<description>한국어</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>lv_LV</id>
|
||||
<description>Latvian</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>ml_IN</id>
|
||||
<description>Malayalam</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>mr_IN</id>
|
||||
<description>Marathi</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>nb_NO</id>
|
||||
<description>Norsk</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>nl_NL</id>
|
||||
<description>Nederlands</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>or_IN</id>
|
||||
<description>ଓଡ଼ିଆ</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>pa_IN</id>
|
||||
<description>ਪੰਜਾਬੀ</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>pl_PL</id>
|
||||
<description>Polski</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>pt_BR</id>
|
||||
<description>Português (Brazil)</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>pt_PT</id>
|
||||
<description>Português (Portugal)</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>ro_RO</id>
|
||||
<description>Română</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>ru_RU</id>
|
||||
<description>Русский язык</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>si_LK</id>
|
||||
<description>සිංහල</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>sk_SK</id>
|
||||
<description>Slovenčina</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>sr_RS</id>
|
||||
<description>српски</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>sv_SE</id>
|
||||
<description>Svenska</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>ta_IN</id>
|
||||
<description>தமிழ்</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>te_IN</id>
|
||||
<description>తెలుగు</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>th_TH</id>
|
||||
<description>ไทย</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>tr_TR</id>
|
||||
<description>Türkçe</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>uk_UA</id>
|
||||
<description>Українська</description>
|
||||
</language>
|
||||
|
||||
<language>
|
||||
<id>zh_CN</id>
|
||||
<description>中文</description>
|
||||
</language>
|
||||
|
||||
</PodcastGenerator>
|
|
@ -0,0 +1,536 @@
|
|||
<?php
|
||||
/*
|
||||
Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch>
|
||||
Copyright (c) 2009 Danilo Segan <danilo@kvota.net>
|
||||
|
||||
Drop in replacement for native gettext.
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
*/
|
||||
/*
|
||||
LC_CTYPE 0
|
||||
LC_NUMERIC 1
|
||||
LC_TIME 2
|
||||
LC_COLLATE 3
|
||||
LC_MONETARY 4
|
||||
LC_MESSAGES 5
|
||||
LC_ALL 6
|
||||
*/
|
||||
|
||||
// LC_MESSAGES is not available if php-gettext is not loaded
|
||||
// while the other constants are already available from session extension.
|
||||
if (!defined('LC_MESSAGES')) {
|
||||
define('LC_MESSAGES', 5);
|
||||
}
|
||||
|
||||
require('streams.php');
|
||||
require('gettext.php');
|
||||
|
||||
|
||||
// Variables
|
||||
|
||||
global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE;
|
||||
$text_domains = array();
|
||||
$default_domain = 'messages';
|
||||
$LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL');
|
||||
$EMULATEGETTEXT = 0;
|
||||
$CURRENTLOCALE = '';
|
||||
|
||||
/* Class to hold a single domain included in $text_domains. */
|
||||
class domain {
|
||||
var $l10n;
|
||||
var $path;
|
||||
var $codeset;
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
/**
|
||||
* Return a list of locales to try for any POSIX-style locale specification.
|
||||
*/
|
||||
function get_list_of_locales($locale) {
|
||||
/* Figure out all possible locale names and start with the most
|
||||
* specific ones. I.e. for sr_CS.UTF-8@latin, look through all of
|
||||
* sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr.
|
||||
*/
|
||||
$locale_names = array();
|
||||
$lang = NULL;
|
||||
$country = NULL;
|
||||
$charset = NULL;
|
||||
$modifier = NULL;
|
||||
if ($locale) {
|
||||
if (preg_match("/^(?P<lang>[a-z]{2,3})" // language code
|
||||
."(?:_(?P<country>[A-Z]{2}))?" // country code
|
||||
."(?:\.(?P<charset>[-A-Za-z0-9_]+))?" // charset
|
||||
."(?:@(?P<modifier>[-A-Za-z0-9_]+))?$/", // @ modifier
|
||||
$locale, $matches)) {
|
||||
|
||||
if (isset($matches["lang"])) $lang = $matches["lang"];
|
||||
if (isset($matches["country"])) $country = $matches["country"];
|
||||
if (isset($matches["charset"])) $charset = $matches["charset"];
|
||||
if (isset($matches["modifier"])) $modifier = $matches["modifier"];
|
||||
|
||||
if ($modifier) {
|
||||
if ($country) {
|
||||
if ($charset)
|
||||
array_push($locale_names, "${lang}_$country.$charset@$modifier");
|
||||
array_push($locale_names, "${lang}_$country@$modifier");
|
||||
} elseif ($charset)
|
||||
array_push($locale_names, "${lang}.$charset@$modifier");
|
||||
array_push($locale_names, "$lang@$modifier");
|
||||
}
|
||||
if ($country) {
|
||||
if ($charset)
|
||||
array_push($locale_names, "${lang}_$country.$charset");
|
||||
array_push($locale_names, "${lang}_$country");
|
||||
} elseif ($charset)
|
||||
array_push($locale_names, "${lang}.$charset");
|
||||
array_push($locale_names, $lang);
|
||||
}
|
||||
|
||||
// If the locale name doesn't match POSIX style, just include it as-is.
|
||||
if (!in_array($locale, $locale_names))
|
||||
array_push($locale_names, $locale);
|
||||
}
|
||||
return $locale_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to get a StreamReader for the given text domain.
|
||||
*/
|
||||
function _get_reader($domain=null, $category=5, $enable_cache=true) {
|
||||
global $text_domains, $default_domain, $LC_CATEGORIES;
|
||||
if (!isset($domain)) $domain = $default_domain;
|
||||
if (!isset($text_domains[$domain]->l10n)) {
|
||||
// get the current locale
|
||||
$locale = _setlocale(LC_MESSAGES, 0);
|
||||
$bound_path = isset($text_domains[$domain]->path) ?
|
||||
$text_domains[$domain]->path : './';
|
||||
$subpath = $LC_CATEGORIES[$category] ."/$domain.mo";
|
||||
|
||||
$locale_names = get_list_of_locales($locale);
|
||||
$input = null;
|
||||
foreach ($locale_names as $locale) {
|
||||
$full_path = $bound_path . $locale . "/" . $subpath;
|
||||
if (file_exists($full_path)) {
|
||||
$input = new FileReader($full_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists($domain, $text_domains)) {
|
||||
// Initialize an empty domain object.
|
||||
$text_domains[$domain] = new domain();
|
||||
}
|
||||
$text_domains[$domain]->l10n = new gettext_reader($input,
|
||||
$enable_cache);
|
||||
}
|
||||
return $text_domains[$domain]->l10n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we are using our emulated gettext API or PHP built-in one.
|
||||
*/
|
||||
function locale_emulation() {
|
||||
global $EMULATEGETTEXT;
|
||||
return $EMULATEGETTEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current locale is supported on this system.
|
||||
*/
|
||||
function _check_locale_and_function($function=false) {
|
||||
global $EMULATEGETTEXT;
|
||||
if ($function and !function_exists($function))
|
||||
return false;
|
||||
return !$EMULATEGETTEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the codeset for the given domain.
|
||||
*/
|
||||
function _get_codeset($domain=null) {
|
||||
global $text_domains, $default_domain, $LC_CATEGORIES;
|
||||
if (!isset($domain)) $domain = $default_domain;
|
||||
return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given string to the encoding set by bind_textdomain_codeset.
|
||||
*/
|
||||
function _encode($text) {
|
||||
$source_encoding = mb_detect_encoding($text);
|
||||
$target_encoding = _get_codeset();
|
||||
if ($source_encoding != $target_encoding) {
|
||||
return mb_convert_encoding($text, $target_encoding, $source_encoding);
|
||||
}
|
||||
else {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Custom implementation of the standard gettext related functions
|
||||
|
||||
/**
|
||||
* Returns passed in $locale, or environment variable $LANG if $locale == ''.
|
||||
*/
|
||||
function _get_default_locale($locale) {
|
||||
if ($locale == '') // emulate variable support
|
||||
return getenv('LANG');
|
||||
else
|
||||
return $locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a requested locale, if needed emulates it.
|
||||
*/
|
||||
function _setlocale($category, $locale) {
|
||||
global $CURRENTLOCALE, $EMULATEGETTEXT;
|
||||
if ($locale === 0) { // use === to differentiate between string "0"
|
||||
if ($CURRENTLOCALE != '')
|
||||
return $CURRENTLOCALE;
|
||||
else
|
||||
// obey LANG variable, maybe extend to support all of LC_* vars
|
||||
// even if we tried to read locale without setting it first
|
||||
return _setlocale($category, $CURRENTLOCALE);
|
||||
} else {
|
||||
if (function_exists('setlocale')) {
|
||||
$ret = setlocale($category, $locale);
|
||||
if (($locale == '' and !$ret) or // failed setting it by env
|
||||
($locale != '' and $ret != $locale)) { // failed setting it
|
||||
// Failed setting it according to environment.
|
||||
$CURRENTLOCALE = _get_default_locale($locale);
|
||||
$EMULATEGETTEXT = 1;
|
||||
} else {
|
||||
$CURRENTLOCALE = $ret;
|
||||
$EMULATEGETTEXT = 0;
|
||||
}
|
||||
} else {
|
||||
// No function setlocale(), emulate it all.
|
||||
$CURRENTLOCALE = _get_default_locale($locale);
|
||||
$EMULATEGETTEXT = 1;
|
||||
}
|
||||
// Allow locale to be changed on the go for one translation domain.
|
||||
global $text_domains, $default_domain;
|
||||
if (array_key_exists($default_domain, $text_domains)) {
|
||||
unset($text_domains[$default_domain]->l10n);
|
||||
}
|
||||
return $CURRENTLOCALE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path for a domain.
|
||||
*/
|
||||
function _bindtextdomain($domain, $path) {
|
||||
global $text_domains;
|
||||
// ensure $path ends with a slash ('/' should work for both, but lets still play nice)
|
||||
if (substr(php_uname(), 0, 7) == "Windows") {
|
||||
if ($path[strlen($path)-1] != '\\' and $path[strlen($path)-1] != '/')
|
||||
$path .= '\\';
|
||||
} else {
|
||||
if ($path[strlen($path)-1] != '/')
|
||||
$path .= '/';
|
||||
}
|
||||
if (!array_key_exists($domain, $text_domains)) {
|
||||
// Initialize an empty domain object.
|
||||
$text_domains[$domain] = new domain();
|
||||
}
|
||||
$text_domains[$domain]->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the character encoding in which the messages from the DOMAIN message catalog will be returned.
|
||||
*/
|
||||
function _bind_textdomain_codeset($domain, $codeset) {
|
||||
global $text_domains;
|
||||
$text_domains[$domain]->codeset = $codeset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default domain.
|
||||
*/
|
||||
function _textdomain($domain) {
|
||||
global $default_domain;
|
||||
$default_domain = $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a message in the current domain.
|
||||
*/
|
||||
function _gettext($msgid) {
|
||||
$l10n = _get_reader();
|
||||
return _encode($l10n->translate($msgid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias for gettext.
|
||||
*/
|
||||
function __($msgid) {
|
||||
return _gettext($msgid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of gettext.
|
||||
*/
|
||||
function _ngettext($singular, $plural, $number) {
|
||||
$l10n = _get_reader();
|
||||
return _encode($l10n->ngettext($singular, $plural, $number));
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the current domain.
|
||||
*/
|
||||
function _dgettext($domain, $msgid) {
|
||||
$l10n = _get_reader($domain);
|
||||
return _encode($l10n->translate($msgid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of dgettext.
|
||||
*/
|
||||
function _dngettext($domain, $singular, $plural, $number) {
|
||||
$l10n = _get_reader($domain);
|
||||
return _encode($l10n->ngettext($singular, $plural, $number));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the domain and category for a single lookup.
|
||||
*/
|
||||
function _dcgettext($domain, $msgid, $category) {
|
||||
$l10n = _get_reader($domain, $category);
|
||||
return _encode($l10n->translate($msgid));
|
||||
}
|
||||
/**
|
||||
* Plural version of dcgettext.
|
||||
*/
|
||||
function _dcngettext($domain, $singular, $plural, $number, $category) {
|
||||
$l10n = _get_reader($domain, $category);
|
||||
return _encode($l10n->ngettext($singular, $plural, $number));
|
||||
}
|
||||
|
||||
/**
|
||||
* Context version of gettext.
|
||||
*/
|
||||
function _pgettext($context, $msgid) {
|
||||
$l10n = _get_reader();
|
||||
return _encode($l10n->pgettext($context, $msgid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the current domain in a context gettext call.
|
||||
*/
|
||||
function _dpgettext($domain, $context, $msgid) {
|
||||
$l10n = _get_reader($domain);
|
||||
return _encode($l10n->pgettext($context, $msgid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the domain and category for a single context-based lookup.
|
||||
*/
|
||||
function _dcpgettext($domain, $context, $msgid, $category) {
|
||||
$l10n = _get_reader($domain, $category);
|
||||
return _encode($l10n->pgettext($context, $msgid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Context version of ngettext.
|
||||
*/
|
||||
function _npgettext($context, $singular, $plural) {
|
||||
$l10n = _get_reader();
|
||||
return _encode($l10n->npgettext($context, $singular, $plural));
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the current domain in a context ngettext call.
|
||||
*/
|
||||
function _dnpgettext($domain, $context, $singular, $plural) {
|
||||
$l10n = _get_reader($domain);
|
||||
return _encode($l10n->npgettext($context, $singular, $plural));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the domain and category for a plural context-based lookup.
|
||||
*/
|
||||
function _dcnpgettext($domain, $context, $singular, $plural, $category) {
|
||||
$l10n = _get_reader($domain, $category);
|
||||
return _encode($l10n->npgettext($context, $singular, $plural));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Wrappers to use if the standard gettext functions are available,
|
||||
// but the current locale is not supported by the system.
|
||||
// Use the standard impl if the current locale is supported, use the
|
||||
// custom impl otherwise.
|
||||
|
||||
function T_setlocale($category, $locale) {
|
||||
return _setlocale($category, $locale);
|
||||
}
|
||||
|
||||
function T_bindtextdomain($domain, $path) {
|
||||
if (_check_locale_and_function()) return bindtextdomain($domain, $path);
|
||||
else return _bindtextdomain($domain, $path);
|
||||
}
|
||||
function T_bind_textdomain_codeset($domain, $codeset) {
|
||||
// bind_textdomain_codeset is available only in PHP 4.2.0+
|
||||
if (_check_locale_and_function('bind_textdomain_codeset'))
|
||||
return bind_textdomain_codeset($domain, $codeset);
|
||||
else return _bind_textdomain_codeset($domain, $codeset);
|
||||
}
|
||||
function T_textdomain($domain) {
|
||||
if (_check_locale_and_function()) return textdomain($domain);
|
||||
else return _textdomain($domain);
|
||||
}
|
||||
function T_gettext($msgid) {
|
||||
if (_check_locale_and_function()) return gettext($msgid);
|
||||
else return _gettext($msgid);
|
||||
}
|
||||
function T_($msgid) {
|
||||
if (_check_locale_and_function()) return _($msgid);
|
||||
return __($msgid);
|
||||
}
|
||||
function T_ngettext($singular, $plural, $number) {
|
||||
if (_check_locale_and_function())
|
||||
return ngettext($singular, $plural, $number);
|
||||
else return _ngettext($singular, $plural, $number);
|
||||
}
|
||||
function T_dgettext($domain, $msgid) {
|
||||
if (_check_locale_and_function()) return dgettext($domain, $msgid);
|
||||
else return _dgettext($domain, $msgid);
|
||||
}
|
||||
function T_dngettext($domain, $singular, $plural, $number) {
|
||||
if (_check_locale_and_function())
|
||||
return dngettext($domain, $singular, $plural, $number);
|
||||
else return _dngettext($domain, $singular, $plural, $number);
|
||||
}
|
||||
function T_dcgettext($domain, $msgid, $category) {
|
||||
if (_check_locale_and_function())
|
||||
return dcgettext($domain, $msgid, $category);
|
||||
else return _dcgettext($domain, $msgid, $category);
|
||||
}
|
||||
function T_dcngettext($domain, $singular, $plural, $number, $category) {
|
||||
if (_check_locale_and_function())
|
||||
return dcngettext($domain, $singular, $plural, $number, $category);
|
||||
else return _dcngettext($domain, $singular, $plural, $number, $category);
|
||||
}
|
||||
|
||||
function T_pgettext($context, $msgid) {
|
||||
if (_check_locale_and_function('pgettext'))
|
||||
return pgettext($context, $msgid);
|
||||
else
|
||||
return _pgettext($context, $msgid);
|
||||
}
|
||||
|
||||
function T_dpgettext($domain, $context, $msgid) {
|
||||
if (_check_locale_and_function('dpgettext'))
|
||||
return dpgettext($domain, $context, $msgid);
|
||||
else
|
||||
return _dpgettext($domain, $context, $msgid);
|
||||
}
|
||||
|
||||
function T_dcpgettext($domain, $context, $msgid, $category) {
|
||||
if (_check_locale_and_function('dcpgettext'))
|
||||
return dcpgettext($domain, $context, $msgid, $category);
|
||||
else
|
||||
return _dcpgettext($domain, $context, $msgid, $category);
|
||||
}
|
||||
|
||||
function T_npgettext($context, $singular, $plural, $number) {
|
||||
if (_check_locale_and_function('npgettext'))
|
||||
return npgettext($context, $singular, $plural, $number);
|
||||
else
|
||||
return _npgettext($context, $singular, $plural, $number);
|
||||
}
|
||||
|
||||
function T_dnpgettext($domain, $context, $singular, $plural, $number) {
|
||||
if (_check_locale_and_function('dnpgettext'))
|
||||
return dnpgettext($domain, $context, $singular, $plural, $number);
|
||||
else
|
||||
return _dnpgettext($domain, $context, $singular, $plural, $number);
|
||||
}
|
||||
|
||||
function T_dcnpgettext($domain, $context, $singular, $plural,
|
||||
$number, $category) {
|
||||
if (_check_locale_and_function('dcnpgettext'))
|
||||
return dcnpgettext($domain, $context, $singular,
|
||||
$plural, $number, $category);
|
||||
else
|
||||
return _dcnpgettext($domain, $context, $singular,
|
||||
$plural, $number, $category);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Wrappers used as a drop in replacement for the standard gettext functions
|
||||
|
||||
if (!function_exists('gettext')) {
|
||||
function bindtextdomain($domain, $path) {
|
||||
return _bindtextdomain($domain, $path);
|
||||
}
|
||||
function bind_textdomain_codeset($domain, $codeset) {
|
||||
return _bind_textdomain_codeset($domain, $codeset);
|
||||
}
|
||||
function textdomain($domain) {
|
||||
return _textdomain($domain);
|
||||
}
|
||||
function gettext($msgid) {
|
||||
return _gettext($msgid);
|
||||
}
|
||||
function _($msgid) {
|
||||
return __($msgid);
|
||||
}
|
||||
function ngettext($singular, $plural, $number) {
|
||||
return _ngettext($singular, $plural, $number);
|
||||
}
|
||||
function dgettext($domain, $msgid) {
|
||||
return _dgettext($domain, $msgid);
|
||||
}
|
||||
function dngettext($domain, $singular, $plural, $number) {
|
||||
return _dngettext($domain, $singular, $plural, $number);
|
||||
}
|
||||
function dcgettext($domain, $msgid, $category) {
|
||||
return _dcgettext($domain, $msgid, $category);
|
||||
}
|
||||
function dcngettext($domain, $singular, $plural, $number, $category) {
|
||||
return _dcngettext($domain, $singular, $plural, $number, $category);
|
||||
}
|
||||
function pgettext($context, $msgid) {
|
||||
return _pgettext($context, $msgid);
|
||||
}
|
||||
function npgettext($context, $singular, $plural, $number) {
|
||||
return _npgettext($context, $singular, $plural, $number);
|
||||
}
|
||||
function dpgettext($domain, $context, $msgid) {
|
||||
return _dpgettext($domain, $context, $msgid);
|
||||
}
|
||||
function dnpgettext($domain, $context, $singular, $plural, $number) {
|
||||
return _dnpgettext($domain, $context, $singular, $plural, $number);
|
||||
}
|
||||
function dcpgettext($domain, $context, $msgid, $category) {
|
||||
return _dcpgettext($domain, $context, $msgid, $category);
|
||||
}
|
||||
function dcnpgettext($domain, $context, $singular, $plural,
|
||||
$number, $category) {
|
||||
return _dcnpgettext($domain, $context, $singular, $plural,
|
||||
$number, $category);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,432 @@
|
|||
<?php
|
||||
/*
|
||||
Copyright (c) 2003, 2009 Danilo Segan <danilo@kvota.net>.
|
||||
Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a simple gettext replacement that works independently from
|
||||
* the system's gettext abilities.
|
||||
* It can read MO files and use them for translating strings.
|
||||
* The files are passed to gettext_reader as a Stream (see streams.php)
|
||||
*
|
||||
* This version has the ability to cache all strings and translations to
|
||||
* speed up the string lookup.
|
||||
* While the cache is enabled by default, it can be switched off with the
|
||||
* second parameter in the constructor (e.g. whenusing very large MO files
|
||||
* that you don't want to keep in memory)
|
||||
*/
|
||||
class gettext_reader {
|
||||
//public:
|
||||
var $error = 0; // public variable that holds error code (0 if no error)
|
||||
|
||||
//private:
|
||||
var $BYTEORDER = 0; // 0: low endian, 1: big endian
|
||||
var $STREAM = NULL;
|
||||
var $short_circuit = false;
|
||||
var $enable_cache = false;
|
||||
var $originals = NULL; // offset of original table
|
||||
var $translations = NULL; // offset of translation table
|
||||
var $pluralheader = NULL; // cache header field for plural forms
|
||||
var $total = 0; // total string count
|
||||
var $table_originals = NULL; // table for original strings (offsets)
|
||||
var $table_translations = NULL; // table for translated strings (offsets)
|
||||
var $cache_translations = NULL; // original -> translation mapping
|
||||
|
||||
|
||||
/* Methods */
|
||||
|
||||
|
||||
/**
|
||||
* Reads a 32bit Integer from the Stream
|
||||
*
|
||||
* @access private
|
||||
* @return Integer from the Stream
|
||||
*/
|
||||
function readint() {
|
||||
if ($this->BYTEORDER == 0) {
|
||||
// low endian
|
||||
$input=unpack('V', $this->STREAM->read(4));
|
||||
return array_shift($input);
|
||||
} else {
|
||||
// big endian
|
||||
$input=unpack('N', $this->STREAM->read(4));
|
||||
return array_shift($input);
|
||||
}
|
||||
}
|
||||
|
||||
function read($bytes) {
|
||||
return $this->STREAM->read($bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an array of Integers from the Stream
|
||||
*
|
||||
* @param int count How many elements should be read
|
||||
* @return Array of Integers
|
||||
*/
|
||||
function readintarray($count) {
|
||||
if ($this->BYTEORDER == 0) {
|
||||
// low endian
|
||||
return unpack('V'.$count, $this->STREAM->read(4 * $count));
|
||||
} else {
|
||||
// big endian
|
||||
return unpack('N'.$count, $this->STREAM->read(4 * $count));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param object Reader the StreamReader object
|
||||
* @param boolean enable_cache Enable or disable caching of strings (default on)
|
||||
*/
|
||||
function __construct($Reader, $enable_cache = true) {
|
||||
// If there isn't a StreamReader, turn on short circuit mode.
|
||||
if (! $Reader || isset($Reader->error) ) {
|
||||
$this->short_circuit = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Caching can be turned off
|
||||
$this->enable_cache = $enable_cache;
|
||||
|
||||
$MAGIC1 = "\x95\x04\x12\xde";
|
||||
$MAGIC2 = "\xde\x12\x04\x95";
|
||||
|
||||
$this->STREAM = $Reader;
|
||||
$magic = $this->read(4);
|
||||
if ($magic == $MAGIC1) {
|
||||
$this->BYTEORDER = 1;
|
||||
} elseif ($magic == $MAGIC2) {
|
||||
$this->BYTEORDER = 0;
|
||||
} else {
|
||||
$this->error = 1; // not MO file
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: Do we care about revision? We should.
|
||||
$revision = $this->readint();
|
||||
|
||||
$this->total = $this->readint();
|
||||
$this->originals = $this->readint();
|
||||
$this->translations = $this->readint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the translation tables from the MO file into the cache
|
||||
* If caching is enabled, also loads all strings into a cache
|
||||
* to speed up translation lookups
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function load_tables() {
|
||||
if (is_array($this->cache_translations) &&
|
||||
is_array($this->table_originals) &&
|
||||
is_array($this->table_translations))
|
||||
return;
|
||||
|
||||
/* get original and translations tables */
|
||||
if (!is_array($this->table_originals)) {
|
||||
$this->STREAM->seekto($this->originals);
|
||||
$this->table_originals = $this->readintarray($this->total * 2);
|
||||
}
|
||||
if (!is_array($this->table_translations)) {
|
||||
$this->STREAM->seekto($this->translations);
|
||||
$this->table_translations = $this->readintarray($this->total * 2);
|
||||
}
|
||||
|
||||
if ($this->enable_cache) {
|
||||
$this->cache_translations = array ();
|
||||
/* read all strings in the cache */
|
||||
for ($i = 0; $i < $this->total; $i++) {
|
||||
$this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
|
||||
$original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
|
||||
$this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
|
||||
$translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
|
||||
$this->cache_translations[$original] = $translation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string from the "originals" table
|
||||
*
|
||||
* @access private
|
||||
* @param int num Offset number of original string
|
||||
* @return string Requested string if found, otherwise ''
|
||||
*/
|
||||
function get_original_string($num) {
|
||||
$length = $this->table_originals[$num * 2 + 1];
|
||||
$offset = $this->table_originals[$num * 2 + 2];
|
||||
if (! $length)
|
||||
return '';
|
||||
$this->STREAM->seekto($offset);
|
||||
$data = $this->STREAM->read($length);
|
||||
return (string)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string from the "translations" table
|
||||
*
|
||||
* @access private
|
||||
* @param int num Offset number of original string
|
||||
* @return string Requested string if found, otherwise ''
|
||||
*/
|
||||
function get_translation_string($num) {
|
||||
$length = $this->table_translations[$num * 2 + 1];
|
||||
$offset = $this->table_translations[$num * 2 + 2];
|
||||
if (! $length)
|
||||
return '';
|
||||
$this->STREAM->seekto($offset);
|
||||
$data = $this->STREAM->read($length);
|
||||
return (string)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search for string
|
||||
*
|
||||
* @access private
|
||||
* @param string string
|
||||
* @param int start (internally used in recursive function)
|
||||
* @param int end (internally used in recursive function)
|
||||
* @return int string number (offset in originals table)
|
||||
*/
|
||||
function find_string($string, $start = -1, $end = -1) {
|
||||
if (($start == -1) or ($end == -1)) {
|
||||
// find_string is called with only one parameter, set start end end
|
||||
$start = 0;
|
||||
$end = $this->total;
|
||||
}
|
||||
if (abs($start - $end) <= 1) {
|
||||
// We're done, now we either found the string, or it doesn't exist
|
||||
$txt = $this->get_original_string($start);
|
||||
if ($string == $txt)
|
||||
return $start;
|
||||
else
|
||||
return -1;
|
||||
} else if ($start > $end) {
|
||||
// start > end -> turn around and start over
|
||||
return $this->find_string($string, $end, $start);
|
||||
} else {
|
||||
// Divide table in two parts
|
||||
$half = (int)(($start + $end) / 2);
|
||||
$cmp = strcmp($string, $this->get_original_string($half));
|
||||
if ($cmp == 0)
|
||||
// string is exactly in the middle => return it
|
||||
return $half;
|
||||
else if ($cmp < 0)
|
||||
// The string is in the upper half
|
||||
return $this->find_string($string, $start, $half);
|
||||
else
|
||||
// The string is in the lower half
|
||||
return $this->find_string($string, $half, $end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string
|
||||
*
|
||||
* @access public
|
||||
* @param string string to be translated
|
||||
* @return string translated string (or original, if not found)
|
||||
*/
|
||||
function translate($string) {
|
||||
if ($this->short_circuit)
|
||||
return $string;
|
||||
$this->load_tables();
|
||||
|
||||
if ($this->enable_cache) {
|
||||
// Caching enabled, get translated string from cache
|
||||
if (array_key_exists($string, $this->cache_translations))
|
||||
return $this->cache_translations[$string];
|
||||
else
|
||||
return $string;
|
||||
} else {
|
||||
// Caching not enabled, try to find string
|
||||
$num = $this->find_string($string);
|
||||
if ($num == -1)
|
||||
return $string;
|
||||
else
|
||||
return $this->get_translation_string($num);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize plural form expression for use in PHP eval call.
|
||||
*
|
||||
* @access private
|
||||
* @return string sanitized plural form expression
|
||||
*/
|
||||
function sanitize_plural_expression($expr) {
|
||||
// Get rid of disallowed characters.
|
||||
$expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr);
|
||||
|
||||
// Add parenthesis for tertiary '?' operator.
|
||||
$expr .= ';';
|
||||
$res = '';
|
||||
$p = 0;
|
||||
for ($i = 0; $i < strlen($expr); $i++) {
|
||||
$ch = $expr[$i];
|
||||
switch ($ch) {
|
||||
case '?':
|
||||
$res .= ' ? (';
|
||||
$p++;
|
||||
break;
|
||||
case ':':
|
||||
$res .= ') : (';
|
||||
break;
|
||||
case ';':
|
||||
$res .= str_repeat( ')', $p) . ';';
|
||||
$p = 0;
|
||||
break;
|
||||
default:
|
||||
$res .= $ch;
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse full PO header and extract only plural forms line.
|
||||
*
|
||||
* @access private
|
||||
* @return string verbatim plural form header field
|
||||
*/
|
||||
function extract_plural_forms_header_from_po_header($header) {
|
||||
if (preg_match("/(^|\n)plural-forms: ([^\n]*)\n/i", $header, $regs))
|
||||
$expr = $regs[2];
|
||||
else
|
||||
$expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
|
||||
return $expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get possible plural forms from MO header
|
||||
*
|
||||
* @access private
|
||||
* @return string plural form header
|
||||
*/
|
||||
function get_plural_forms() {
|
||||
// lets assume message number 0 is header
|
||||
// this is true, right?
|
||||
$this->load_tables();
|
||||
|
||||
// cache header field for plural forms
|
||||
if (! is_string($this->pluralheader)) {
|
||||
if ($this->enable_cache) {
|
||||
$header = $this->cache_translations[""];
|
||||
} else {
|
||||
$header = $this->get_translation_string(0);
|
||||
}
|
||||
$expr = $this->extract_plural_forms_header_from_po_header($header);
|
||||
$this->pluralheader = $this->sanitize_plural_expression($expr);
|
||||
}
|
||||
return $this->pluralheader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects which plural form to take
|
||||
*
|
||||
* @access private
|
||||
* @param n count
|
||||
* @return int array index of the right plural form
|
||||
*/
|
||||
function select_string($n) {
|
||||
$string = $this->get_plural_forms();
|
||||
$string = str_replace('nplurals',"\$total",$string);
|
||||
$string = str_replace("n",$n,$string);
|
||||
$string = str_replace('plural',"\$plural",$string);
|
||||
|
||||
$total = 0;
|
||||
$plural = 0;
|
||||
|
||||
eval("$string");
|
||||
if ($plural >= $total) $plural = $total - 1;
|
||||
return $plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of gettext
|
||||
*
|
||||
* @access public
|
||||
* @param string single
|
||||
* @param string plural
|
||||
* @param string number
|
||||
* @return translated plural form
|
||||
*/
|
||||
function ngettext($single, $plural, $number) {
|
||||
if ($this->short_circuit) {
|
||||
if ($number != 1)
|
||||
return $plural;
|
||||
else
|
||||
return $single;
|
||||
}
|
||||
|
||||
// find out the appropriate form
|
||||
$select = $this->select_string($number);
|
||||
|
||||
// this should contains all strings separated by NULLs
|
||||
$key = $single . chr(0) . $plural;
|
||||
|
||||
|
||||
if ($this->enable_cache) {
|
||||
if (! array_key_exists($key, $this->cache_translations)) {
|
||||
return ($number != 1) ? $plural : $single;
|
||||
} else {
|
||||
$result = $this->cache_translations[$key];
|
||||
$list = explode(chr(0), $result);
|
||||
return $list[$select];
|
||||
}
|
||||
} else {
|
||||
$num = $this->find_string($key);
|
||||
if ($num == -1) {
|
||||
return ($number != 1) ? $plural : $single;
|
||||
} else {
|
||||
$result = $this->get_translation_string($num);
|
||||
$list = explode(chr(0), $result);
|
||||
return $list[$select];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pgettext($context, $msgid) {
|
||||
$key = $context . chr(4) . $msgid;
|
||||
$ret = $this->translate($key);
|
||||
if (strpos($ret, "\004") !== FALSE) {
|
||||
return $msgid;
|
||||
} else {
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
||||
function npgettext($context, $singular, $plural, $number) {
|
||||
$key = $context . chr(4) . $singular;
|
||||
$ret = $this->ngettext($key, $plural, $number);
|
||||
if (strpos($ret, "\004") !== FALSE) {
|
||||
return $singular;
|
||||
} else {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,167 @@
|
|||
<?php
|
||||
/*
|
||||
Copyright (c) 2003, 2005, 2006, 2009 Danilo Segan <danilo@kvota.net>.
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
*/
|
||||
|
||||
|
||||
// Simple class to wrap file streams, string streams, etc.
|
||||
// seek is essential, and it should be byte stream
|
||||
class StreamReader {
|
||||
// should return a string [FIXME: perhaps return array of bytes?]
|
||||
function read($bytes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// should return new position
|
||||
function seekto($position) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// returns current position
|
||||
function currentpos() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// returns length of entire stream (limit for seekto()s)
|
||||
function length() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class StringReader {
|
||||
var $_pos;
|
||||
var $_str;
|
||||
|
||||
function __construct($str='') {
|
||||
$this->_str = $str;
|
||||
$this->_pos = 0;
|
||||
}
|
||||
|
||||
function read($bytes) {
|
||||
$data = substr($this->_str, $this->_pos, $bytes);
|
||||
$this->_pos += $bytes;
|
||||
if (strlen($this->_str)<$this->_pos)
|
||||
$this->_pos = strlen($this->_str);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function seekto($pos) {
|
||||
$this->_pos = $pos;
|
||||
if (strlen($this->_str)<$this->_pos)
|
||||
$this->_pos = strlen($this->_str);
|
||||
return $this->_pos;
|
||||
}
|
||||
|
||||
function currentpos() {
|
||||
return $this->_pos;
|
||||
}
|
||||
|
||||
function length() {
|
||||
return strlen($this->_str);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
class FileReader {
|
||||
var $_pos;
|
||||
var $_fd;
|
||||
var $_length;
|
||||
|
||||
function __construct($filename) {
|
||||
if (file_exists($filename)) {
|
||||
|
||||
$this->_length=filesize($filename);
|
||||
$this->_pos = 0;
|
||||
$this->_fd = fopen($filename,'rb');
|
||||
if (!$this->_fd) {
|
||||
$this->error = 3; // Cannot read file, probably permissions
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->error = 2; // File doesn't exist
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function read($bytes) {
|
||||
if ($bytes) {
|
||||
fseek($this->_fd, $this->_pos);
|
||||
|
||||
// PHP 5.1.1 does not read more than 8192 bytes in one fread()
|
||||
// the discussions at PHP Bugs suggest it's the intended behaviour
|
||||
$data = '';
|
||||
while ($bytes > 0) {
|
||||
$chunk = fread($this->_fd, $bytes);
|
||||
$data .= $chunk;
|
||||
$bytes -= strlen($chunk);
|
||||
}
|
||||
$this->_pos = ftell($this->_fd);
|
||||
|
||||
return $data;
|
||||
} else return '';
|
||||
}
|
||||
|
||||
function seekto($pos) {
|
||||
fseek($this->_fd, $pos);
|
||||
$this->_pos = ftell($this->_fd);
|
||||
return $this->_pos;
|
||||
}
|
||||
|
||||
function currentpos() {
|
||||
return $this->_pos;
|
||||
}
|
||||
|
||||
function length() {
|
||||
return $this->_length;
|
||||
}
|
||||
|
||||
function close() {
|
||||
fclose($this->_fd);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Preloads entire file in memory first, then creates a StringReader
|
||||
// over it (it assumes knowledge of StringReader internals)
|
||||
class CachedFileReader extends StringReader {
|
||||
function __construct($filename) {
|
||||
if (file_exists($filename)) {
|
||||
|
||||
$length=filesize($filename);
|
||||
$fd = fopen($filename,'rb');
|
||||
|
||||
if (!$fd) {
|
||||
$this->error = 3; // Cannot read file, probably permissions
|
||||
return false;
|
||||
}
|
||||
$this->_str = fread($fd, $length);
|
||||
fclose($fd);
|
||||
|
||||
} else {
|
||||
$this->error = 2; // File doesn't exist
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
?>
|
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PodcastGenerator>
|
||||
|
||||
<mediaFile>
|
||||
<extension>mp3</extension>
|
||||
<mimetype>audio/mpeg</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>mpg</extension>
|
||||
<mimetype>video/mpeg</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>mpeg</extension>
|
||||
<mimetype>video/mpeg</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>mov</extension>
|
||||
<mimetype>video/quicktime</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>wav</extension>
|
||||
<mimetype>audio/x-wav</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>wma</extension>
|
||||
<mimetype>audio/x-ms-wma</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>wmv</extension>
|
||||
<mimetype>video/x-ms-wmv</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>ogg</extension>
|
||||
<mimetype>application/ogg</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>mp4</extension>
|
||||
<mimetype>video/mp4</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>asf</extension>
|
||||
<mimetype>video/x-ms-asf</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>avi</extension>
|
||||
<mimetype>video/x-msvideo</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>flv</extension>
|
||||
<mimetype>video/x-flv</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>jpg</extension>
|
||||
<mimetype>image/jpeg</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>jpeg</extension>
|
||||
<mimetype>image/jpeg</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>pdf</extension>
|
||||
<mimetype>application/pdf</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>aif</extension>
|
||||
<mimetype>audio/x-aiff</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>aiff</extension>
|
||||
<mimetype>audio/x-aiff</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>m4a</extension>
|
||||
<mimetype>audio/x-m4a</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>m4v</extension>
|
||||
<mimetype>video/x-m4v</mimetype>
|
||||
</mediaFile>
|
||||
<mediaFile>
|
||||
<extension>epub</extension>
|
||||
<mimetype>application/epub+zip</mimetype>
|
||||
</mediaFile>
|
||||
|
||||
</PodcastGenerator>
|