From a5f917bbc55e295896b8084f6657eb8b6abaf8a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Fabian=20Silva=20Delgado?= Date: Fri, 15 Jul 2016 15:33:36 -0300 Subject: Add TimedMediaHandler extension that allows display audio and video files in wiki pages, using the same syntax as for image files --- extensions/TimedMediaHandler/.gitignore | 6 + extensions/TimedMediaHandler/.gitreview | 5 + extensions/TimedMediaHandler/.jscsrc | 3 + extensions/TimedMediaHandler/.jshintignore | 1 + extensions/TimedMediaHandler/.jshintrc | 44 + extensions/TimedMediaHandler/ApiQueryVideoInfo.php | 268 ++ extensions/TimedMediaHandler/ApiTranscodeReset.php | 184 + .../TimedMediaHandler/ApiTranscodeStatus.php | 79 + extensions/TimedMediaHandler/COPYING | 342 ++ extensions/TimedMediaHandler/Gruntfile.js | 34 + .../EmbedPlayer/EmbedPlayer.config.php | 194 + .../EmbedPlayer/EmbedPlayer.loader.js | 69 + .../MwEmbedModules/EmbedPlayer/EmbedPlayer.php | 103 + .../binPlayers/kaltura-player/LightDoodleskin.swf | Bin 0 -> 190545 bytes .../EmbedPlayer/binPlayers/kaltura-player/README | 20 + .../binPlayers/kaltura-player/config.xml | 9 + .../binPlayers/kaltura-player/expressInstall.swf | Bin 0 -> 727 bytes .../binPlayers/kaltura-player/kdp3.3.5.27.swf | Bin 0 -> 272405 bytes .../EmbedPlayer/binPlayers/kaltura-player/kdp3.swf | Bin 0 -> 256262 bytes .../EmbedPlayer/binPlayers/kaltura-player/skin.swf | Bin 0 -> 67435 bytes .../EmbedPlayer/binPlayers/ogv.js/COPYING | 21 + .../EmbedPlayer/binPlayers/ogv.js/COPYING-ogg.txt | 28 + .../EmbedPlayer/binPlayers/ogv.js/COPYING-opus.txt | 44 + .../binPlayers/ogv.js/COPYING-theora.txt | 28 + .../binPlayers/ogv.js/COPYING-vorbis.txt | 28 + .../EmbedPlayer/binPlayers/ogv.js/dynamicaudio.swf | Bin 0 -> 1865 bytes .../binPlayers/ogv.js/ogv-decoder-audio-opus.js | 24 + .../binPlayers/ogv.js/ogv-decoder-audio-vorbis.js | 23 + .../binPlayers/ogv.js/ogv-decoder-video-theora.js | 22 + .../binPlayers/ogv.js/ogv-demuxer-ogg.js | 23 + .../EmbedPlayer/binPlayers/ogv.js/ogv-support.js | 201 + .../EmbedPlayer/binPlayers/ogv.js/ogv-version.js | 1 + .../binPlayers/ogv.js/ogv-worker-audio.js | 317 ++ .../binPlayers/ogv.js/ogv-worker-video.js | 317 ++ .../EmbedPlayer/binPlayers/ogv.js/ogv.js | 3966 ++++++++++++++++++++ .../EmbedPlayer/binPlayers/ogv.js/readme.md | 237 ++ .../MwEmbedModules/EmbedPlayer/i18n/af.json | 23 + .../MwEmbedModules/EmbedPlayer/i18n/aln.json | 34 + .../MwEmbedModules/EmbedPlayer/i18n/ar.json | 59 + .../MwEmbedModules/EmbedPlayer/i18n/arc.json | 8 + .../MwEmbedModules/EmbedPlayer/i18n/ast.json | 89 + .../MwEmbedModules/EmbedPlayer/i18n/az.json | 10 + .../MwEmbedModules/EmbedPlayer/i18n/ba.json | 16 + .../MwEmbedModules/EmbedPlayer/i18n/be-tarask.json | 86 + .../MwEmbedModules/EmbedPlayer/i18n/bg.json | 12 + .../MwEmbedModules/EmbedPlayer/i18n/bho.json | 13 + .../MwEmbedModules/EmbedPlayer/i18n/bn.json | 74 + .../MwEmbedModules/EmbedPlayer/i18n/br.json | 76 + .../MwEmbedModules/EmbedPlayer/i18n/bs.json | 12 + .../MwEmbedModules/EmbedPlayer/i18n/ca.json | 46 + .../MwEmbedModules/EmbedPlayer/i18n/ce.json | 15 + .../MwEmbedModules/EmbedPlayer/i18n/cs.json | 68 + .../MwEmbedModules/EmbedPlayer/i18n/cu.json | 9 + .../MwEmbedModules/EmbedPlayer/i18n/de.json | 98 + .../MwEmbedModules/EmbedPlayer/i18n/diq.json | 70 + .../MwEmbedModules/EmbedPlayer/i18n/dsb.json | 83 + .../MwEmbedModules/EmbedPlayer/i18n/el.json | 77 + .../MwEmbedModules/EmbedPlayer/i18n/en-gb.json | 8 + .../MwEmbedModules/EmbedPlayer/i18n/en.json | 95 + .../MwEmbedModules/EmbedPlayer/i18n/eo.json | 27 + .../MwEmbedModules/EmbedPlayer/i18n/es.json | 99 + .../MwEmbedModules/EmbedPlayer/i18n/et.json | 48 + .../MwEmbedModules/EmbedPlayer/i18n/eu.json | 17 + .../MwEmbedModules/EmbedPlayer/i18n/fa.json | 87 + .../MwEmbedModules/EmbedPlayer/i18n/fi.json | 41 + .../MwEmbedModules/EmbedPlayer/i18n/fr.json | 97 + .../MwEmbedModules/EmbedPlayer/i18n/frp.json | 76 + .../MwEmbedModules/EmbedPlayer/i18n/fy.json | 9 + .../MwEmbedModules/EmbedPlayer/i18n/ga.json | 8 + .../MwEmbedModules/EmbedPlayer/i18n/gl.json | 96 + .../MwEmbedModules/EmbedPlayer/i18n/grc.json | 8 + .../MwEmbedModules/EmbedPlayer/i18n/gsw.json | 80 + .../MwEmbedModules/EmbedPlayer/i18n/he.json | 83 + .../MwEmbedModules/EmbedPlayer/i18n/hsb.json | 83 + .../MwEmbedModules/EmbedPlayer/i18n/hu.json | 75 + .../MwEmbedModules/EmbedPlayer/i18n/ia.json | 79 + .../MwEmbedModules/EmbedPlayer/i18n/id.json | 76 + .../MwEmbedModules/EmbedPlayer/i18n/ilo.json | 16 + .../MwEmbedModules/EmbedPlayer/i18n/is.json | 64 + .../MwEmbedModules/EmbedPlayer/i18n/it.json | 92 + .../MwEmbedModules/EmbedPlayer/i18n/ja.json | 84 + .../MwEmbedModules/EmbedPlayer/i18n/ka.json | 73 + .../MwEmbedModules/EmbedPlayer/i18n/ko.json | 83 + .../MwEmbedModules/EmbedPlayer/i18n/ksh.json | 84 + .../MwEmbedModules/EmbedPlayer/i18n/ku-latn.json | 10 + .../MwEmbedModules/EmbedPlayer/i18n/ky.json | 9 + .../MwEmbedModules/EmbedPlayer/i18n/lb.json | 55 + .../MwEmbedModules/EmbedPlayer/i18n/lt.json | 90 + .../MwEmbedModules/EmbedPlayer/i18n/lv.json | 60 + .../MwEmbedModules/EmbedPlayer/i18n/mk.json | 95 + .../MwEmbedModules/EmbedPlayer/i18n/ml.json | 96 + .../MwEmbedModules/EmbedPlayer/i18n/ms.json | 82 + .../MwEmbedModules/EmbedPlayer/i18n/mt.json | 23 + .../MwEmbedModules/EmbedPlayer/i18n/nb.json | 92 + .../MwEmbedModules/EmbedPlayer/i18n/nds-nl.json | 8 + .../MwEmbedModules/EmbedPlayer/i18n/nl.json | 85 + .../MwEmbedModules/EmbedPlayer/i18n/nn.json | 84 + .../MwEmbedModules/EmbedPlayer/i18n/oc.json | 50 + .../MwEmbedModules/EmbedPlayer/i18n/pam.json | 8 + .../MwEmbedModules/EmbedPlayer/i18n/pdc.json | 9 + .../MwEmbedModules/EmbedPlayer/i18n/pl.json | 81 + .../MwEmbedModules/EmbedPlayer/i18n/pms.json | 80 + .../MwEmbedModules/EmbedPlayer/i18n/ps.json | 21 + .../MwEmbedModules/EmbedPlayer/i18n/pt-br.json | 86 + .../MwEmbedModules/EmbedPlayer/i18n/pt.json | 101 + .../MwEmbedModules/EmbedPlayer/i18n/qqq.json | 50 + .../MwEmbedModules/EmbedPlayer/i18n/qu.json | 8 + .../MwEmbedModules/EmbedPlayer/i18n/ro.json | 83 + .../MwEmbedModules/EmbedPlayer/i18n/roa-tara.json | 33 + .../MwEmbedModules/EmbedPlayer/i18n/ru.json | 100 + .../MwEmbedModules/EmbedPlayer/i18n/rue.json | 9 + .../MwEmbedModules/EmbedPlayer/i18n/si.json | 78 + .../MwEmbedModules/EmbedPlayer/i18n/sk.json | 6 + .../MwEmbedModules/EmbedPlayer/i18n/sr-ec.json | 78 + .../MwEmbedModules/EmbedPlayer/i18n/sr-el.json | 77 + .../MwEmbedModules/EmbedPlayer/i18n/sv.json | 96 + .../MwEmbedModules/EmbedPlayer/i18n/ta.json | 40 + .../MwEmbedModules/EmbedPlayer/i18n/te.json | 17 + .../MwEmbedModules/EmbedPlayer/i18n/tet.json | 8 + .../MwEmbedModules/EmbedPlayer/i18n/tl.json | 83 + .../MwEmbedModules/EmbedPlayer/i18n/tr.json | 85 + .../MwEmbedModules/EmbedPlayer/i18n/tzm.json | 11 + .../MwEmbedModules/EmbedPlayer/i18n/uk.json | 92 + .../MwEmbedModules/EmbedPlayer/i18n/ur.json | 12 + .../MwEmbedModules/EmbedPlayer/i18n/vec.json | 38 + .../MwEmbedModules/EmbedPlayer/i18n/vep.json | 12 + .../MwEmbedModules/EmbedPlayer/i18n/vi.json | 77 + .../MwEmbedModules/EmbedPlayer/i18n/yi.json | 10 + .../MwEmbedModules/EmbedPlayer/i18n/zh-hans.json | 102 + .../MwEmbedModules/EmbedPlayer/i18n/zh-hant.json | 86 + .../EmbedPlayer/resources/blackvideo.mp4 | Bin 0 -> 4575 bytes .../EmbedPlayer/resources/mw.EmbedPlayer.js | 2760 ++++++++++++++ .../EmbedPlayer/resources/mw.EmbedPlayerGeneric.js | 36 + .../resources/mw.EmbedPlayerIEWebMPrompt.css | 18 + .../resources/mw.EmbedPlayerIEWebMPrompt.js | 46 + .../resources/mw.EmbedPlayerImageOverlay.js | 307 ++ .../EmbedPlayer/resources/mw.EmbedPlayerKplayer.js | 485 +++ .../EmbedPlayer/resources/mw.EmbedPlayerNative.js | 1088 ++++++ .../EmbedPlayer/resources/mw.EmbedPlayerOgvJs.js | 222 ++ .../EmbedPlayer/resources/mw.EmbedPlayerVLCApp.js | 101 + .../EmbedPlayer/resources/mw.EmbedPlayerVlc.js | 358 ++ .../EmbedPlayer/resources/mw.EmbedTypes.js | 360 ++ .../EmbedPlayer/resources/mw.MediaElement.js | 491 +++ .../EmbedPlayer/resources/mw.MediaPlayer.js | 85 + .../EmbedPlayer/resources/mw.MediaPlayers.js | 195 + .../EmbedPlayer/resources/mw.MediaSource.js | 490 +++ .../resources/mw.processEmbedPlayers.js | 353 ++ .../EmbedPlayer/resources/skins/EmbedPlayer.css | 166 + .../resources/skins/kskin/PlayerSkinKskin.css | 484 +++ .../images/kaltura_open_source_video_platform.gif | Bin 0 -> 3368 bytes .../images/kaltura_open_source_video_platform.png | Bin 0 -> 302 bytes .../resources/skins/kskin/images/ksprite.png | Bin 0 -> 13520 bytes .../resources/skins/kskin/mw.PlayerSkinKskin.js | 394 ++ .../resources/skins/mvpcf/PlayerSkinMvpcf.css | 194 + .../skins/mvpcf/images/player_big_play_button.png | Bin 0 -> 2935 bytes .../resources/skins/mvpcf/mw.PlayerSkinMvpcf.js | 7 + .../resources/skins/mw.PlayerControlBuilder.js | 2721 ++++++++++++++ .../EmbedPlayer/tests/Player_Audio.html | 16 + .../EmbedPlayer/tests/Player_DynamicEmbed.html | 24 + .../EmbedPlayer/tests/Player_IncludeJQuery.html | 27 + .../EmbedPlayer/tests/Player_Native_Bindings.html | 129 + .../EmbedPlayer/tests/Player_Seek.html | 35 + .../EmbedPlayer/tests/Player_ServerSeek.html | 35 + .../EmbedPlayer/tests/Player_ServerSideSeek.html | 14 + .../EmbedPlayer/tests/Player_Sources.html | 24 + .../EmbedPlayer/tests/Player_Themable.html | 71 + .../MwEmbedModules/TimedText/TimedText.config.php | 31 + .../MwEmbedModules/TimedText/TimedText.loader.js | 52 + .../MwEmbedModules/TimedText/TimedText.php | 21 + .../MwEmbedModules/TimedText/i18n/af.json | 10 + .../MwEmbedModules/TimedText/i18n/ar.json | 14 + .../MwEmbedModules/TimedText/i18n/ast.json | 26 + .../MwEmbedModules/TimedText/i18n/be-tarask.json | 28 + .../MwEmbedModules/TimedText/i18n/bg.json | 8 + .../MwEmbedModules/TimedText/i18n/bn.json | 15 + .../MwEmbedModules/TimedText/i18n/br.json | 26 + .../MwEmbedModules/TimedText/i18n/bs.json | 10 + .../MwEmbedModules/TimedText/i18n/ca.json | 9 + .../MwEmbedModules/TimedText/i18n/ce.json | 8 + .../MwEmbedModules/TimedText/i18n/cs.json | 16 + .../MwEmbedModules/TimedText/i18n/cy.json | 26 + .../MwEmbedModules/TimedText/i18n/de-formal.json | 7 + .../MwEmbedModules/TimedText/i18n/de.json | 28 + .../MwEmbedModules/TimedText/i18n/diq.json | 13 + .../MwEmbedModules/TimedText/i18n/dsb.json | 27 + .../MwEmbedModules/TimedText/i18n/el.json | 24 + .../MwEmbedModules/TimedText/i18n/en.json | 25 + .../MwEmbedModules/TimedText/i18n/eo.json | 7 + .../MwEmbedModules/TimedText/i18n/es.json | 30 + .../MwEmbedModules/TimedText/i18n/et.json | 18 + .../MwEmbedModules/TimedText/i18n/eu.json | 10 + .../MwEmbedModules/TimedText/i18n/fa.json | 30 + .../MwEmbedModules/TimedText/i18n/fi.json | 16 + .../MwEmbedModules/TimedText/i18n/fr.json | 29 + .../MwEmbedModules/TimedText/i18n/frp.json | 26 + .../MwEmbedModules/TimedText/i18n/fy.json | 8 + .../MwEmbedModules/TimedText/i18n/gl.json | 26 + .../MwEmbedModules/TimedText/i18n/gsw.json | 27 + .../MwEmbedModules/TimedText/i18n/he.json | 28 + .../MwEmbedModules/TimedText/i18n/hsb.json | 26 + .../MwEmbedModules/TimedText/i18n/hu.json | 23 + .../MwEmbedModules/TimedText/i18n/ia.json | 26 + .../MwEmbedModules/TimedText/i18n/id.json | 27 + .../MwEmbedModules/TimedText/i18n/it.json | 29 + .../MwEmbedModules/TimedText/i18n/ja.json | 29 + .../MwEmbedModules/TimedText/i18n/ka.json | 24 + .../MwEmbedModules/TimedText/i18n/ko.json | 26 + .../MwEmbedModules/TimedText/i18n/ksh.json | 8 + .../MwEmbedModules/TimedText/i18n/lb.json | 23 + .../MwEmbedModules/TimedText/i18n/lt.json | 9 + .../MwEmbedModules/TimedText/i18n/lv.json | 9 + .../MwEmbedModules/TimedText/i18n/mk.json | 26 + .../MwEmbedModules/TimedText/i18n/ml.json | 26 + .../MwEmbedModules/TimedText/i18n/ms.json | 26 + .../MwEmbedModules/TimedText/i18n/nb.json | 25 + .../MwEmbedModules/TimedText/i18n/nl.json | 26 + .../MwEmbedModules/TimedText/i18n/nn.json | 14 + .../MwEmbedModules/TimedText/i18n/pdc.json | 8 + .../MwEmbedModules/TimedText/i18n/pfl.json | 7 + .../MwEmbedModules/TimedText/i18n/pl.json | 29 + .../MwEmbedModules/TimedText/i18n/pms.json | 27 + .../MwEmbedModules/TimedText/i18n/ps.json | 8 + .../MwEmbedModules/TimedText/i18n/pt-br.json | 30 + .../MwEmbedModules/TimedText/i18n/pt.json | 30 + .../MwEmbedModules/TimedText/i18n/qqq.json | 19 + .../MwEmbedModules/TimedText/i18n/ro.json | 27 + .../MwEmbedModules/TimedText/i18n/roa-tara.json | 14 + .../MwEmbedModules/TimedText/i18n/ru.json | 29 + .../MwEmbedModules/TimedText/i18n/rue.json | 8 + .../MwEmbedModules/TimedText/i18n/si.json | 25 + .../MwEmbedModules/TimedText/i18n/sl.json | 8 + .../MwEmbedModules/TimedText/i18n/so.json | 7 + .../MwEmbedModules/TimedText/i18n/sr-ec.json | 15 + .../MwEmbedModules/TimedText/i18n/sr-el.json | 14 + .../MwEmbedModules/TimedText/i18n/sv.json | 27 + .../MwEmbedModules/TimedText/i18n/ta.json | 16 + .../MwEmbedModules/TimedText/i18n/te.json | 9 + .../MwEmbedModules/TimedText/i18n/tl.json | 27 + .../MwEmbedModules/TimedText/i18n/tr.json | 11 + .../MwEmbedModules/TimedText/i18n/uk.json | 27 + .../MwEmbedModules/TimedText/i18n/ur.json | 9 + .../MwEmbedModules/TimedText/i18n/vi.json | 18 + .../MwEmbedModules/TimedText/i18n/wa.json | 15 + .../MwEmbedModules/TimedText/i18n/yi.json | 9 + .../MwEmbedModules/TimedText/i18n/zh-hans.json | 30 + .../MwEmbedModules/TimedText/i18n/zh-hant.json | 29 + .../TimedText/resources/mw.TextSource.js | 504 +++ .../TimedText/resources/mw.TimedText.js | 1313 +++++++ .../TimedText/resources/mw.style.TimedText.css | 18 + extensions/TimedMediaHandler/README | 127 + .../TimedMediaHandler/SpecialOrphanedTimedText.php | 258 ++ .../TimedMediaHandler/SpecialTimedMediaHandler.php | 235 ++ .../TimedMediaHandler/TimedMediaHandler.hooks.php | 485 +++ .../TimedMediaHandler.i18n.alias.php | 76 + .../TimedMediaHandler.i18n.magic.php | 86 + extensions/TimedMediaHandler/TimedMediaHandler.php | 306 ++ extensions/TimedMediaHandler/TimedMediaHandler.sql | 19 + .../TimedMediaHandler/TimedMediaHandler_body.php | 483 +++ .../TimedMediaHandler/TimedMediaIframeOutput.php | 152 + .../TimedMediaHandler/TimedMediaThumbnail.php | 240 ++ .../TimedMediaTransformOutput.php | 486 +++ extensions/TimedMediaHandler/TimedTextPage.php | 187 + .../TimedMediaHandler/TranscodeStatusTable.php | 236 ++ .../WebVideoTranscode/WebVideoTranscode.php | 1192 ++++++ .../WebVideoTranscode/WebVideoTranscodeJob.php | 965 +++++ .../archives/transcode_name_key.sql | 3 + extensions/TimedMediaHandler/composer.json | 10 + extensions/TimedMediaHandler/gitinfo.json | 1 + .../handlers/FLACHandler/FLACHandler.php | 74 + .../handlers/ID3Handler/ID3Handler.php | 107 + .../handlers/Mp4Handler/Mp4Handler.php | 139 + .../handlers/OggHandler/File_Ogg/File/Ogg.php | 631 ++++ .../OggHandler/File_Ogg/File/Ogg/Bitstream.php | 125 + .../handlers/OggHandler/File_Ogg/File/Ogg/Flac.php | 133 + .../OggHandler/File_Ogg/File/Ogg/Media.php | 262 ++ .../handlers/OggHandler/File_Ogg/File/Ogg/Opus.php | 126 + .../OggHandler/File_Ogg/File/Ogg/Speex.php | 122 + .../OggHandler/File_Ogg/File/Ogg/Theora.php | 240 ++ .../OggHandler/File_Ogg/File/Ogg/Vorbis.php | 790 ++++ .../handlers/OggHandler/File_Ogg/File/README | 2 + .../handlers/OggHandler/OggException.php | 4 + .../handlers/OggHandler/OggHandler.php | 379 ++ .../handlers/TextHandler/TextHandler.php | 305 ++ .../handlers/WAVHandler/WAVHandler.php | 87 + .../handlers/WebMHandler/WebMHandler.php | 175 + extensions/TimedMediaHandler/i18n/ady-cyrl.json | 11 + extensions/TimedMediaHandler/i18n/aeb-latn.json | 9 + extensions/TimedMediaHandler/i18n/af.json | 25 + extensions/TimedMediaHandler/i18n/akz.json | 8 + extensions/TimedMediaHandler/i18n/aln.json | 23 + extensions/TimedMediaHandler/i18n/an.json | 20 + extensions/TimedMediaHandler/i18n/ang.json | 9 + extensions/TimedMediaHandler/i18n/ar.json | 67 + extensions/TimedMediaHandler/i18n/arc.json | 8 + extensions/TimedMediaHandler/i18n/arq.json | 10 + extensions/TimedMediaHandler/i18n/arz.json | 24 + extensions/TimedMediaHandler/i18n/as.json | 10 + extensions/TimedMediaHandler/i18n/ast.json | 122 + extensions/TimedMediaHandler/i18n/av.json | 9 + extensions/TimedMediaHandler/i18n/avk.json | 9 + extensions/TimedMediaHandler/i18n/awa.json | 10 + extensions/TimedMediaHandler/i18n/az.json | 13 + extensions/TimedMediaHandler/i18n/azb.json | 11 + extensions/TimedMediaHandler/i18n/ba.json | 14 + extensions/TimedMediaHandler/i18n/bcc.json | 24 + extensions/TimedMediaHandler/i18n/bcl.json | 90 + extensions/TimedMediaHandler/i18n/be-tarask.json | 71 + extensions/TimedMediaHandler/i18n/be.json | 10 + extensions/TimedMediaHandler/i18n/bg.json | 24 + extensions/TimedMediaHandler/i18n/bgn.json | 11 + extensions/TimedMediaHandler/i18n/bho.json | 8 + extensions/TimedMediaHandler/i18n/bn.json | 48 + extensions/TimedMediaHandler/i18n/br.json | 51 + extensions/TimedMediaHandler/i18n/bs.json | 31 + extensions/TimedMediaHandler/i18n/ca.json | 44 + extensions/TimedMediaHandler/i18n/ce.json | 85 + extensions/TimedMediaHandler/i18n/ckb.json | 14 + extensions/TimedMediaHandler/i18n/cs.json | 75 + extensions/TimedMediaHandler/i18n/cu.json | 10 + extensions/TimedMediaHandler/i18n/cv.json | 8 + extensions/TimedMediaHandler/i18n/cy.json | 50 + extensions/TimedMediaHandler/i18n/da.json | 100 + extensions/TimedMediaHandler/i18n/de-formal.json | 8 + extensions/TimedMediaHandler/i18n/de.json | 130 + extensions/TimedMediaHandler/i18n/din.json | 8 + extensions/TimedMediaHandler/i18n/diq.json | 41 + extensions/TimedMediaHandler/i18n/dsb.json | 30 + extensions/TimedMediaHandler/i18n/dty.json | 12 + extensions/TimedMediaHandler/i18n/ee.json | 9 + extensions/TimedMediaHandler/i18n/el.json | 28 + extensions/TimedMediaHandler/i18n/en-gb.json | 12 + extensions/TimedMediaHandler/i18n/en.json | 154 + extensions/TimedMediaHandler/i18n/eo.json | 26 + extensions/TimedMediaHandler/i18n/es.json | 117 + extensions/TimedMediaHandler/i18n/et.json | 122 + extensions/TimedMediaHandler/i18n/eu.json | 29 + extensions/TimedMediaHandler/i18n/fa.json | 123 + extensions/TimedMediaHandler/i18n/fi.json | 78 + extensions/TimedMediaHandler/i18n/fo.json | 12 + extensions/TimedMediaHandler/i18n/fr.json | 150 + extensions/TimedMediaHandler/i18n/frp.json | 38 + extensions/TimedMediaHandler/i18n/frr.json | 11 + extensions/TimedMediaHandler/i18n/fur.json | 23 + extensions/TimedMediaHandler/i18n/fy.json | 16 + extensions/TimedMediaHandler/i18n/ga.json | 13 + extensions/TimedMediaHandler/i18n/gl.json | 125 + extensions/TimedMediaHandler/i18n/gom-deva.json | 12 + extensions/TimedMediaHandler/i18n/gom-latn.json | 11 + extensions/TimedMediaHandler/i18n/grc.json | 13 + extensions/TimedMediaHandler/i18n/gsw.json | 62 + extensions/TimedMediaHandler/i18n/gu.json | 23 + extensions/TimedMediaHandler/i18n/gv.json | 8 + extensions/TimedMediaHandler/i18n/he.json | 125 + extensions/TimedMediaHandler/i18n/hi.json | 39 + extensions/TimedMediaHandler/i18n/hr.json | 31 + extensions/TimedMediaHandler/i18n/hrx.json | 8 + extensions/TimedMediaHandler/i18n/hsb.json | 83 + extensions/TimedMediaHandler/i18n/ht.json | 9 + extensions/TimedMediaHandler/i18n/hu.json | 37 + extensions/TimedMediaHandler/i18n/hy.json | 11 + extensions/TimedMediaHandler/i18n/ia.json | 92 + extensions/TimedMediaHandler/i18n/id.json | 97 + extensions/TimedMediaHandler/i18n/ilo.json | 89 + extensions/TimedMediaHandler/i18n/io.json | 11 + extensions/TimedMediaHandler/i18n/is.json | 60 + extensions/TimedMediaHandler/i18n/it.json | 108 + extensions/TimedMediaHandler/i18n/ja.json | 91 + extensions/TimedMediaHandler/i18n/jut.json | 21 + extensions/TimedMediaHandler/i18n/jv.json | 65 + extensions/TimedMediaHandler/i18n/ka.json | 89 + extensions/TimedMediaHandler/i18n/khw.json | 10 + extensions/TimedMediaHandler/i18n/kk-arab.json | 15 + extensions/TimedMediaHandler/i18n/kk-cyrl.json | 56 + extensions/TimedMediaHandler/i18n/kk-latn.json | 15 + extensions/TimedMediaHandler/i18n/km.json | 24 + extensions/TimedMediaHandler/i18n/kn.json | 17 + extensions/TimedMediaHandler/i18n/ko.json | 104 + extensions/TimedMediaHandler/i18n/krc.json | 11 + extensions/TimedMediaHandler/i18n/krj.json | 8 + extensions/TimedMediaHandler/i18n/ksh.json | 81 + extensions/TimedMediaHandler/i18n/ku-latn.json | 14 + extensions/TimedMediaHandler/i18n/ky.json | 8 + extensions/TimedMediaHandler/i18n/la.json | 9 + extensions/TimedMediaHandler/i18n/lad.json | 10 + extensions/TimedMediaHandler/i18n/lb.json | 73 + extensions/TimedMediaHandler/i18n/lfn.json | 8 + extensions/TimedMediaHandler/i18n/li.json | 21 + extensions/TimedMediaHandler/i18n/lrc.json | 44 + extensions/TimedMediaHandler/i18n/lt.json | 123 + extensions/TimedMediaHandler/i18n/luz.json | 10 + extensions/TimedMediaHandler/i18n/lv.json | 22 + extensions/TimedMediaHandler/i18n/mai.json | 10 + extensions/TimedMediaHandler/i18n/mg.json | 10 + extensions/TimedMediaHandler/i18n/min.json | 30 + extensions/TimedMediaHandler/i18n/mk.json | 144 + extensions/TimedMediaHandler/i18n/ml.json | 124 + extensions/TimedMediaHandler/i18n/mr.json | 28 + extensions/TimedMediaHandler/i18n/ms.json | 97 + extensions/TimedMediaHandler/i18n/mt.json | 11 + extensions/TimedMediaHandler/i18n/myv.json | 13 + extensions/TimedMediaHandler/i18n/mzn.json | 10 + extensions/TimedMediaHandler/i18n/nah.json | 12 + extensions/TimedMediaHandler/i18n/nap.json | 122 + extensions/TimedMediaHandler/i18n/nb.json | 105 + extensions/TimedMediaHandler/i18n/nds-nl.json | 24 + extensions/TimedMediaHandler/i18n/nds.json | 20 + extensions/TimedMediaHandler/i18n/ne.json | 25 + extensions/TimedMediaHandler/i18n/nl.json | 108 + extensions/TimedMediaHandler/i18n/nn.json | 41 + extensions/TimedMediaHandler/i18n/oc.json | 31 + extensions/TimedMediaHandler/i18n/olo.json | 18 + extensions/TimedMediaHandler/i18n/or.json | 85 + extensions/TimedMediaHandler/i18n/os.json | 9 + extensions/TimedMediaHandler/i18n/pa.json | 13 + extensions/TimedMediaHandler/i18n/pdc.json | 9 + extensions/TimedMediaHandler/i18n/pfl.json | 10 + extensions/TimedMediaHandler/i18n/pl.json | 101 + extensions/TimedMediaHandler/i18n/pms.json | 96 + extensions/TimedMediaHandler/i18n/pnb.json | 10 + extensions/TimedMediaHandler/i18n/ps.json | 29 + extensions/TimedMediaHandler/i18n/pt-br.json | 40 + extensions/TimedMediaHandler/i18n/pt.json | 102 + extensions/TimedMediaHandler/i18n/qqq.json | 172 + extensions/TimedMediaHandler/i18n/qu.json | 14 + extensions/TimedMediaHandler/i18n/ro.json | 93 + extensions/TimedMediaHandler/i18n/roa-tara.json | 91 + extensions/TimedMediaHandler/i18n/ru.json | 111 + extensions/TimedMediaHandler/i18n/rue.json | 10 + extensions/TimedMediaHandler/i18n/sa.json | 19 + extensions/TimedMediaHandler/i18n/sah.json | 23 + extensions/TimedMediaHandler/i18n/scn.json | 11 + extensions/TimedMediaHandler/i18n/sco.json | 16 + extensions/TimedMediaHandler/i18n/sdh.json | 8 + extensions/TimedMediaHandler/i18n/sgs.json | 10 + extensions/TimedMediaHandler/i18n/shn.json | 9 + extensions/TimedMediaHandler/i18n/shy-latn.json | 8 + extensions/TimedMediaHandler/i18n/si.json | 85 + extensions/TimedMediaHandler/i18n/sk.json | 25 + extensions/TimedMediaHandler/i18n/sl.json | 96 + extensions/TimedMediaHandler/i18n/sq.json | 20 + extensions/TimedMediaHandler/i18n/sr-ec.json | 55 + extensions/TimedMediaHandler/i18n/sr-el.json | 53 + extensions/TimedMediaHandler/i18n/stq.json | 20 + extensions/TimedMediaHandler/i18n/su.json | 19 + extensions/TimedMediaHandler/i18n/sv.json | 125 + extensions/TimedMediaHandler/i18n/sw.json | 14 + extensions/TimedMediaHandler/i18n/szl.json | 9 + extensions/TimedMediaHandler/i18n/ta.json | 32 + extensions/TimedMediaHandler/i18n/tcy.json | 9 + extensions/TimedMediaHandler/i18n/te.json | 37 + extensions/TimedMediaHandler/i18n/tg-cyrl.json | 20 + extensions/TimedMediaHandler/i18n/tg-latn.json | 20 + extensions/TimedMediaHandler/i18n/tk.json | 20 + extensions/TimedMediaHandler/i18n/tl.json | 84 + extensions/TimedMediaHandler/i18n/tly.json | 8 + extensions/TimedMediaHandler/i18n/tr.json | 80 + extensions/TimedMediaHandler/i18n/ts.json | 9 + extensions/TimedMediaHandler/i18n/tt-cyrl.json | 17 + extensions/TimedMediaHandler/i18n/tzm.json | 8 + extensions/TimedMediaHandler/i18n/ug-arab.json | 13 + extensions/TimedMediaHandler/i18n/uk.json | 134 + extensions/TimedMediaHandler/i18n/ur.json | 21 + extensions/TimedMediaHandler/i18n/vec.json | 85 + extensions/TimedMediaHandler/i18n/vep.json | 11 + extensions/TimedMediaHandler/i18n/vi.json | 130 + extensions/TimedMediaHandler/i18n/vo.json | 14 + extensions/TimedMediaHandler/i18n/vro.json | 10 + extensions/TimedMediaHandler/i18n/wa.json | 4 + extensions/TimedMediaHandler/i18n/war.json | 9 + extensions/TimedMediaHandler/i18n/wuu.json | 10 + extensions/TimedMediaHandler/i18n/xmf.json | 10 + extensions/TimedMediaHandler/i18n/yi.json | 15 + extensions/TimedMediaHandler/i18n/yue.json | 24 + extensions/TimedMediaHandler/i18n/zh-hans.json | 137 + extensions/TimedMediaHandler/i18n/zh-hant.json | 119 + .../TimedMediaHandler/libs/getid3/getid3.lib.php | 1346 +++++++ .../TimedMediaHandler/libs/getid3/getid3.php | 1776 +++++++++ .../TimedMediaHandler/libs/getid3/license.txt | 340 ++ .../libs/getid3/module.audio-video.matroska.php | 1771 +++++++++ .../libs/getid3/module.audio-video.mpeg.php | 296 ++ .../libs/getid3/module.audio-video.quicktime.php | 2192 +++++++++++ .../libs/getid3/module.audio-video.riff.php | 2435 ++++++++++++ .../libs/getid3/module.audio.ac3.php | 473 +++ .../libs/getid3/module.audio.dts.php | 291 ++ .../libs/getid3/module.audio.flac.php | 442 +++ .../libs/getid3/module.audio.mp3.php | 2009 ++++++++++ .../libs/getid3/module.audio.ogg.php | 671 ++++ .../libs/getid3/module.tag.id3v1.php | 359 ++ .../libs/getid3/module.tag.id3v2.php | 3414 +++++++++++++++++ .../TimedMediaHandler/libs/getid3/readme.txt | 591 +++ .../maintenance/cleanupTranscodes.php | 46 + .../maintenance/moveTranscoded.php | 56 + .../maintenance/resetTranscodes.php | 41 + .../maintenance/retryTranscodes.php | 55 + extensions/TimedMediaHandler/mwEmbedLoader.php | 24 + extensions/TimedMediaHandler/package.json | 14 + .../resources/PopUpThumbVideo.css | 29 + .../resources/download_sprite.png | Bin 0 -> 806 bytes .../resources/embedPlayerIframe.css | 7 + .../resources/ext.tmh.OgvJsSupport.js | 67 + .../resources/ext.tmh.TimedTextSelector.js | 8 + .../resources/ext.tmh.transcodetable.js | 90 + .../resources/mw.MediaWikiPlayer.loader.js | 8 + .../resources/mw.MediaWikiPlayerSupport.js | 380 ++ .../resources/mw.PopUpThumbVideo.js | 44 + .../resources/mw.TMHGalleryHook.js | 46 + .../resources/player_big_play_button.png | Bin 0 -> 987 bytes .../resources/player_big_play_button_hover.png | Bin 0 -> 1022 bytes .../TimedMediaHandler/resources/transcodeTable.css | 14 + .../tests/phpunit/ApiTestCaseVideoUpload.php | 130 + extensions/TimedMediaHandler/tests/phpunit/README | 11 + .../tests/phpunit/TestApiUploadVideo.php | 34 + .../tests/phpunit/TestOggHandler.php | 67 + .../tests/phpunit/TestTimeParsing.php | 42 + .../tests/phpunit/TestTimedMediaHandler.php | 47 + .../phpunit/TestTimedMediaTransformOutput.php | 142 + .../tests/phpunit/TestVideoThumbnail.php | 80 + .../tests/phpunit/TestVideoTranscode.php | 121 + .../tests/phpunit/TestWebMHandler.php | 52 + .../TimedMediaHandler/tests/phpunit/media/README | 38 + .../tests/phpunit/media/VP9-tractor.webm | Bin 0 -> 179277 bytes .../tests/phpunit/media/bear-vp9-opus.webm | Bin 0 -> 101414 bytes .../tests/phpunit/media/broken-file.ogg | Bin 0 -> 11424 bytes .../tests/phpunit/media/doubleTag.oga | Bin 0 -> 11423 bytes .../phpunit/media/shuttle10seconds.1080x608.webm | Bin 0 -> 699018 bytes .../media/test5seconds.electricsheep.300x400.ogv | Bin 0 -> 299761 bytes extensions/TimedMediaHandler/version | 5 + 527 files changed, 64411 insertions(+) create mode 100644 extensions/TimedMediaHandler/.gitignore create mode 100644 extensions/TimedMediaHandler/.gitreview create mode 100644 extensions/TimedMediaHandler/.jscsrc create mode 100644 extensions/TimedMediaHandler/.jshintignore create mode 100644 extensions/TimedMediaHandler/.jshintrc create mode 100644 extensions/TimedMediaHandler/ApiQueryVideoInfo.php create mode 100644 extensions/TimedMediaHandler/ApiTranscodeReset.php create mode 100644 extensions/TimedMediaHandler/ApiTranscodeStatus.php create mode 100644 extensions/TimedMediaHandler/COPYING create mode 100644 extensions/TimedMediaHandler/Gruntfile.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/EmbedPlayer.config.php create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/EmbedPlayer.loader.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/EmbedPlayer.php create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/kaltura-player/LightDoodleskin.swf create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/kaltura-player/README create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/kaltura-player/config.xml create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/kaltura-player/expressInstall.swf create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/kaltura-player/kdp3.3.5.27.swf create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/kaltura-player/kdp3.swf create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/kaltura-player/skin.swf create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/COPYING create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/COPYING-ogg.txt create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/COPYING-opus.txt create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/COPYING-theora.txt create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/COPYING-vorbis.txt create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/dynamicaudio.swf create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/ogv-decoder-audio-opus.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/ogv-decoder-audio-vorbis.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/ogv-decoder-video-theora.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/ogv-demuxer-ogg.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/ogv-support.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/ogv-version.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/ogv-worker-audio.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/ogv-worker-video.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/ogv.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/binPlayers/ogv.js/readme.md create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/af.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/aln.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ar.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/arc.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ast.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/az.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ba.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/be-tarask.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/bg.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/bho.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/bn.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/br.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/bs.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ca.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ce.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/cs.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/cu.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/de.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/diq.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/dsb.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/el.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/en-gb.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/en.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/eo.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/es.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/et.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/eu.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/fa.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/fi.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/fr.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/frp.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/fy.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ga.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/gl.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/grc.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/gsw.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/he.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/hsb.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/hu.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ia.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/id.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ilo.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/is.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/it.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ja.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ka.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ko.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ksh.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ku-latn.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ky.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/lb.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/lt.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/lv.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/mk.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ml.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ms.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/mt.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/nb.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/nds-nl.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/nl.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/nn.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/oc.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/pam.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/pdc.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/pl.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/pms.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ps.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/pt-br.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/pt.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/qqq.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/qu.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ro.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/roa-tara.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ru.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/rue.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/si.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/sk.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/sr-ec.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/sr-el.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/sv.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ta.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/te.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/tet.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/tl.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/tr.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/tzm.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/uk.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/ur.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/vec.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/vep.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/vi.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/yi.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/zh-hans.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/i18n/zh-hant.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/blackvideo.mp4 create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayer.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerGeneric.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerIEWebMPrompt.css create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerIEWebMPrompt.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerImageOverlay.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerKplayer.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerNative.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerOgvJs.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerVLCApp.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedPlayerVlc.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.EmbedTypes.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.MediaElement.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.MediaPlayer.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.MediaPlayers.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.MediaSource.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/mw.processEmbedPlayers.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/EmbedPlayer.css create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/PlayerSkinKskin.css create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/images/kaltura_open_source_video_platform.gif create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/images/kaltura_open_source_video_platform.png create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/images/ksprite.png create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/kskin/mw.PlayerSkinKskin.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mvpcf/PlayerSkinMvpcf.css create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mvpcf/images/player_big_play_button.png create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mvpcf/mw.PlayerSkinMvpcf.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/resources/skins/mw.PlayerControlBuilder.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/tests/Player_Audio.html create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/tests/Player_DynamicEmbed.html create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/tests/Player_IncludeJQuery.html create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/tests/Player_Native_Bindings.html create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/tests/Player_Seek.html create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/tests/Player_ServerSeek.html create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/tests/Player_ServerSideSeek.html create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/tests/Player_Sources.html create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/tests/Player_Themable.html create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/TimedText.config.php create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/TimedText.loader.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/TimedText.php create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/af.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ar.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ast.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/be-tarask.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/bg.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/bn.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/br.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/bs.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ca.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ce.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/cs.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/cy.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/de-formal.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/de.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/diq.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/dsb.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/el.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/en.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/eo.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/es.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/et.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/eu.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/fa.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/fi.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/fr.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/frp.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/fy.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/gl.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/gsw.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/he.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/hsb.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/hu.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ia.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/id.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/it.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ja.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ka.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ko.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ksh.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/lb.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/lt.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/lv.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/mk.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ml.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ms.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/nb.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/nl.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/nn.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/pdc.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/pfl.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/pl.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/pms.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ps.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/pt-br.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/pt.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/qqq.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ro.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/roa-tara.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ru.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/rue.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/si.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/sl.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/so.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/sr-ec.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/sr-el.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/sv.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ta.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/te.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/tl.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/tr.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/uk.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/ur.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/vi.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/wa.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/yi.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/zh-hans.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/i18n/zh-hant.json create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/resources/mw.TextSource.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/resources/mw.TimedText.js create mode 100644 extensions/TimedMediaHandler/MwEmbedModules/TimedText/resources/mw.style.TimedText.css create mode 100644 extensions/TimedMediaHandler/README create mode 100644 extensions/TimedMediaHandler/SpecialOrphanedTimedText.php create mode 100644 extensions/TimedMediaHandler/SpecialTimedMediaHandler.php create mode 100644 extensions/TimedMediaHandler/TimedMediaHandler.hooks.php create mode 100644 extensions/TimedMediaHandler/TimedMediaHandler.i18n.alias.php create mode 100644 extensions/TimedMediaHandler/TimedMediaHandler.i18n.magic.php create mode 100644 extensions/TimedMediaHandler/TimedMediaHandler.php create mode 100644 extensions/TimedMediaHandler/TimedMediaHandler.sql create mode 100644 extensions/TimedMediaHandler/TimedMediaHandler_body.php create mode 100644 extensions/TimedMediaHandler/TimedMediaIframeOutput.php create mode 100644 extensions/TimedMediaHandler/TimedMediaThumbnail.php create mode 100644 extensions/TimedMediaHandler/TimedMediaTransformOutput.php create mode 100644 extensions/TimedMediaHandler/TimedTextPage.php create mode 100644 extensions/TimedMediaHandler/TranscodeStatusTable.php create mode 100644 extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscode.php create mode 100644 extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscodeJob.php create mode 100644 extensions/TimedMediaHandler/archives/transcode_name_key.sql create mode 100644 extensions/TimedMediaHandler/composer.json create mode 100644 extensions/TimedMediaHandler/gitinfo.json create mode 100644 extensions/TimedMediaHandler/handlers/FLACHandler/FLACHandler.php create mode 100644 extensions/TimedMediaHandler/handlers/ID3Handler/ID3Handler.php create mode 100644 extensions/TimedMediaHandler/handlers/Mp4Handler/Mp4Handler.php create mode 100644 extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg.php create mode 100644 extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Bitstream.php create mode 100644 extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Flac.php create mode 100644 extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Media.php create mode 100644 extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Opus.php create mode 100644 extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Speex.php create mode 100644 extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Theora.php create mode 100644 extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Vorbis.php create mode 100644 extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/README create mode 100644 extensions/TimedMediaHandler/handlers/OggHandler/OggException.php create mode 100644 extensions/TimedMediaHandler/handlers/OggHandler/OggHandler.php create mode 100644 extensions/TimedMediaHandler/handlers/TextHandler/TextHandler.php create mode 100644 extensions/TimedMediaHandler/handlers/WAVHandler/WAVHandler.php create mode 100644 extensions/TimedMediaHandler/handlers/WebMHandler/WebMHandler.php create mode 100644 extensions/TimedMediaHandler/i18n/ady-cyrl.json create mode 100644 extensions/TimedMediaHandler/i18n/aeb-latn.json create mode 100644 extensions/TimedMediaHandler/i18n/af.json create mode 100644 extensions/TimedMediaHandler/i18n/akz.json create mode 100644 extensions/TimedMediaHandler/i18n/aln.json create mode 100644 extensions/TimedMediaHandler/i18n/an.json create mode 100644 extensions/TimedMediaHandler/i18n/ang.json create mode 100644 extensions/TimedMediaHandler/i18n/ar.json create mode 100644 extensions/TimedMediaHandler/i18n/arc.json create mode 100644 extensions/TimedMediaHandler/i18n/arq.json create mode 100644 extensions/TimedMediaHandler/i18n/arz.json create mode 100644 extensions/TimedMediaHandler/i18n/as.json create mode 100644 extensions/TimedMediaHandler/i18n/ast.json create mode 100644 extensions/TimedMediaHandler/i18n/av.json create mode 100644 extensions/TimedMediaHandler/i18n/avk.json create mode 100644 extensions/TimedMediaHandler/i18n/awa.json create mode 100644 extensions/TimedMediaHandler/i18n/az.json create mode 100644 extensions/TimedMediaHandler/i18n/azb.json create mode 100644 extensions/TimedMediaHandler/i18n/ba.json create mode 100644 extensions/TimedMediaHandler/i18n/bcc.json create mode 100644 extensions/TimedMediaHandler/i18n/bcl.json create mode 100644 extensions/TimedMediaHandler/i18n/be-tarask.json create mode 100644 extensions/TimedMediaHandler/i18n/be.json create mode 100644 extensions/TimedMediaHandler/i18n/bg.json create mode 100644 extensions/TimedMediaHandler/i18n/bgn.json create mode 100644 extensions/TimedMediaHandler/i18n/bho.json create mode 100644 extensions/TimedMediaHandler/i18n/bn.json create mode 100644 extensions/TimedMediaHandler/i18n/br.json create mode 100644 extensions/TimedMediaHandler/i18n/bs.json create mode 100644 extensions/TimedMediaHandler/i18n/ca.json create mode 100644 extensions/TimedMediaHandler/i18n/ce.json create mode 100644 extensions/TimedMediaHandler/i18n/ckb.json create mode 100644 extensions/TimedMediaHandler/i18n/cs.json create mode 100644 extensions/TimedMediaHandler/i18n/cu.json create mode 100644 extensions/TimedMediaHandler/i18n/cv.json create mode 100644 extensions/TimedMediaHandler/i18n/cy.json create mode 100644 extensions/TimedMediaHandler/i18n/da.json create mode 100644 extensions/TimedMediaHandler/i18n/de-formal.json create mode 100644 extensions/TimedMediaHandler/i18n/de.json create mode 100644 extensions/TimedMediaHandler/i18n/din.json create mode 100644 extensions/TimedMediaHandler/i18n/diq.json create mode 100644 extensions/TimedMediaHandler/i18n/dsb.json create mode 100644 extensions/TimedMediaHandler/i18n/dty.json create mode 100644 extensions/TimedMediaHandler/i18n/ee.json create mode 100644 extensions/TimedMediaHandler/i18n/el.json create mode 100644 extensions/TimedMediaHandler/i18n/en-gb.json create mode 100644 extensions/TimedMediaHandler/i18n/en.json create mode 100644 extensions/TimedMediaHandler/i18n/eo.json create mode 100644 extensions/TimedMediaHandler/i18n/es.json create mode 100644 extensions/TimedMediaHandler/i18n/et.json create mode 100644 extensions/TimedMediaHandler/i18n/eu.json create mode 100644 extensions/TimedMediaHandler/i18n/fa.json create mode 100644 extensions/TimedMediaHandler/i18n/fi.json create mode 100644 extensions/TimedMediaHandler/i18n/fo.json create mode 100644 extensions/TimedMediaHandler/i18n/fr.json create mode 100644 extensions/TimedMediaHandler/i18n/frp.json create mode 100644 extensions/TimedMediaHandler/i18n/frr.json create mode 100644 extensions/TimedMediaHandler/i18n/fur.json create mode 100644 extensions/TimedMediaHandler/i18n/fy.json create mode 100644 extensions/TimedMediaHandler/i18n/ga.json create mode 100644 extensions/TimedMediaHandler/i18n/gl.json create mode 100644 extensions/TimedMediaHandler/i18n/gom-deva.json create mode 100644 extensions/TimedMediaHandler/i18n/gom-latn.json create mode 100644 extensions/TimedMediaHandler/i18n/grc.json create mode 100644 extensions/TimedMediaHandler/i18n/gsw.json create mode 100644 extensions/TimedMediaHandler/i18n/gu.json create mode 100644 extensions/TimedMediaHandler/i18n/gv.json create mode 100644 extensions/TimedMediaHandler/i18n/he.json create mode 100644 extensions/TimedMediaHandler/i18n/hi.json create mode 100644 extensions/TimedMediaHandler/i18n/hr.json create mode 100644 extensions/TimedMediaHandler/i18n/hrx.json create mode 100644 extensions/TimedMediaHandler/i18n/hsb.json create mode 100644 extensions/TimedMediaHandler/i18n/ht.json create mode 100644 extensions/TimedMediaHandler/i18n/hu.json create mode 100644 extensions/TimedMediaHandler/i18n/hy.json create mode 100644 extensions/TimedMediaHandler/i18n/ia.json create mode 100644 extensions/TimedMediaHandler/i18n/id.json create mode 100644 extensions/TimedMediaHandler/i18n/ilo.json create mode 100644 extensions/TimedMediaHandler/i18n/io.json create mode 100644 extensions/TimedMediaHandler/i18n/is.json create mode 100644 extensions/TimedMediaHandler/i18n/it.json create mode 100644 extensions/TimedMediaHandler/i18n/ja.json create mode 100644 extensions/TimedMediaHandler/i18n/jut.json create mode 100644 extensions/TimedMediaHandler/i18n/jv.json create mode 100644 extensions/TimedMediaHandler/i18n/ka.json create mode 100644 extensions/TimedMediaHandler/i18n/khw.json create mode 100644 extensions/TimedMediaHandler/i18n/kk-arab.json create mode 100644 extensions/TimedMediaHandler/i18n/kk-cyrl.json create mode 100644 extensions/TimedMediaHandler/i18n/kk-latn.json create mode 100644 extensions/TimedMediaHandler/i18n/km.json create mode 100644 extensions/TimedMediaHandler/i18n/kn.json create mode 100644 extensions/TimedMediaHandler/i18n/ko.json create mode 100644 extensions/TimedMediaHandler/i18n/krc.json create mode 100644 extensions/TimedMediaHandler/i18n/krj.json create mode 100644 extensions/TimedMediaHandler/i18n/ksh.json create mode 100644 extensions/TimedMediaHandler/i18n/ku-latn.json create mode 100644 extensions/TimedMediaHandler/i18n/ky.json create mode 100644 extensions/TimedMediaHandler/i18n/la.json create mode 100644 extensions/TimedMediaHandler/i18n/lad.json create mode 100644 extensions/TimedMediaHandler/i18n/lb.json create mode 100644 extensions/TimedMediaHandler/i18n/lfn.json create mode 100644 extensions/TimedMediaHandler/i18n/li.json create mode 100644 extensions/TimedMediaHandler/i18n/lrc.json create mode 100644 extensions/TimedMediaHandler/i18n/lt.json create mode 100644 extensions/TimedMediaHandler/i18n/luz.json create mode 100644 extensions/TimedMediaHandler/i18n/lv.json create mode 100644 extensions/TimedMediaHandler/i18n/mai.json create mode 100644 extensions/TimedMediaHandler/i18n/mg.json create mode 100644 extensions/TimedMediaHandler/i18n/min.json create mode 100644 extensions/TimedMediaHandler/i18n/mk.json create mode 100644 extensions/TimedMediaHandler/i18n/ml.json create mode 100644 extensions/TimedMediaHandler/i18n/mr.json create mode 100644 extensions/TimedMediaHandler/i18n/ms.json create mode 100644 extensions/TimedMediaHandler/i18n/mt.json create mode 100644 extensions/TimedMediaHandler/i18n/myv.json create mode 100644 extensions/TimedMediaHandler/i18n/mzn.json create mode 100644 extensions/TimedMediaHandler/i18n/nah.json create mode 100644 extensions/TimedMediaHandler/i18n/nap.json create mode 100644 extensions/TimedMediaHandler/i18n/nb.json create mode 100644 extensions/TimedMediaHandler/i18n/nds-nl.json create mode 100644 extensions/TimedMediaHandler/i18n/nds.json create mode 100644 extensions/TimedMediaHandler/i18n/ne.json create mode 100644 extensions/TimedMediaHandler/i18n/nl.json create mode 100644 extensions/TimedMediaHandler/i18n/nn.json create mode 100644 extensions/TimedMediaHandler/i18n/oc.json create mode 100644 extensions/TimedMediaHandler/i18n/olo.json create mode 100644 extensions/TimedMediaHandler/i18n/or.json create mode 100644 extensions/TimedMediaHandler/i18n/os.json create mode 100644 extensions/TimedMediaHandler/i18n/pa.json create mode 100644 extensions/TimedMediaHandler/i18n/pdc.json create mode 100644 extensions/TimedMediaHandler/i18n/pfl.json create mode 100644 extensions/TimedMediaHandler/i18n/pl.json create mode 100644 extensions/TimedMediaHandler/i18n/pms.json create mode 100644 extensions/TimedMediaHandler/i18n/pnb.json create mode 100644 extensions/TimedMediaHandler/i18n/ps.json create mode 100644 extensions/TimedMediaHandler/i18n/pt-br.json create mode 100644 extensions/TimedMediaHandler/i18n/pt.json create mode 100644 extensions/TimedMediaHandler/i18n/qqq.json create mode 100644 extensions/TimedMediaHandler/i18n/qu.json create mode 100644 extensions/TimedMediaHandler/i18n/ro.json create mode 100644 extensions/TimedMediaHandler/i18n/roa-tara.json create mode 100644 extensions/TimedMediaHandler/i18n/ru.json create mode 100644 extensions/TimedMediaHandler/i18n/rue.json create mode 100644 extensions/TimedMediaHandler/i18n/sa.json create mode 100644 extensions/TimedMediaHandler/i18n/sah.json create mode 100644 extensions/TimedMediaHandler/i18n/scn.json create mode 100644 extensions/TimedMediaHandler/i18n/sco.json create mode 100644 extensions/TimedMediaHandler/i18n/sdh.json create mode 100644 extensions/TimedMediaHandler/i18n/sgs.json create mode 100644 extensions/TimedMediaHandler/i18n/shn.json create mode 100644 extensions/TimedMediaHandler/i18n/shy-latn.json create mode 100644 extensions/TimedMediaHandler/i18n/si.json create mode 100644 extensions/TimedMediaHandler/i18n/sk.json create mode 100644 extensions/TimedMediaHandler/i18n/sl.json create mode 100644 extensions/TimedMediaHandler/i18n/sq.json create mode 100644 extensions/TimedMediaHandler/i18n/sr-ec.json create mode 100644 extensions/TimedMediaHandler/i18n/sr-el.json create mode 100644 extensions/TimedMediaHandler/i18n/stq.json create mode 100644 extensions/TimedMediaHandler/i18n/su.json create mode 100644 extensions/TimedMediaHandler/i18n/sv.json create mode 100644 extensions/TimedMediaHandler/i18n/sw.json create mode 100644 extensions/TimedMediaHandler/i18n/szl.json create mode 100644 extensions/TimedMediaHandler/i18n/ta.json create mode 100644 extensions/TimedMediaHandler/i18n/tcy.json create mode 100644 extensions/TimedMediaHandler/i18n/te.json create mode 100644 extensions/TimedMediaHandler/i18n/tg-cyrl.json create mode 100644 extensions/TimedMediaHandler/i18n/tg-latn.json create mode 100644 extensions/TimedMediaHandler/i18n/tk.json create mode 100644 extensions/TimedMediaHandler/i18n/tl.json create mode 100644 extensions/TimedMediaHandler/i18n/tly.json create mode 100644 extensions/TimedMediaHandler/i18n/tr.json create mode 100644 extensions/TimedMediaHandler/i18n/ts.json create mode 100644 extensions/TimedMediaHandler/i18n/tt-cyrl.json create mode 100644 extensions/TimedMediaHandler/i18n/tzm.json create mode 100644 extensions/TimedMediaHandler/i18n/ug-arab.json create mode 100644 extensions/TimedMediaHandler/i18n/uk.json create mode 100644 extensions/TimedMediaHandler/i18n/ur.json create mode 100644 extensions/TimedMediaHandler/i18n/vec.json create mode 100644 extensions/TimedMediaHandler/i18n/vep.json create mode 100644 extensions/TimedMediaHandler/i18n/vi.json create mode 100644 extensions/TimedMediaHandler/i18n/vo.json create mode 100644 extensions/TimedMediaHandler/i18n/vro.json create mode 100644 extensions/TimedMediaHandler/i18n/wa.json create mode 100644 extensions/TimedMediaHandler/i18n/war.json create mode 100644 extensions/TimedMediaHandler/i18n/wuu.json create mode 100644 extensions/TimedMediaHandler/i18n/xmf.json create mode 100644 extensions/TimedMediaHandler/i18n/yi.json create mode 100644 extensions/TimedMediaHandler/i18n/yue.json create mode 100644 extensions/TimedMediaHandler/i18n/zh-hans.json create mode 100644 extensions/TimedMediaHandler/i18n/zh-hant.json create mode 100644 extensions/TimedMediaHandler/libs/getid3/getid3.lib.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/getid3.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/license.txt create mode 100644 extensions/TimedMediaHandler/libs/getid3/module.audio-video.matroska.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/module.audio-video.mpeg.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/module.audio-video.quicktime.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/module.audio-video.riff.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/module.audio.ac3.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/module.audio.dts.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/module.audio.flac.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/module.audio.mp3.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/module.audio.ogg.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/module.tag.id3v1.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/module.tag.id3v2.php create mode 100644 extensions/TimedMediaHandler/libs/getid3/readme.txt create mode 100644 extensions/TimedMediaHandler/maintenance/cleanupTranscodes.php create mode 100644 extensions/TimedMediaHandler/maintenance/moveTranscoded.php create mode 100644 extensions/TimedMediaHandler/maintenance/resetTranscodes.php create mode 100644 extensions/TimedMediaHandler/maintenance/retryTranscodes.php create mode 100644 extensions/TimedMediaHandler/mwEmbedLoader.php create mode 100644 extensions/TimedMediaHandler/package.json create mode 100644 extensions/TimedMediaHandler/resources/PopUpThumbVideo.css create mode 100644 extensions/TimedMediaHandler/resources/download_sprite.png create mode 100644 extensions/TimedMediaHandler/resources/embedPlayerIframe.css create mode 100644 extensions/TimedMediaHandler/resources/ext.tmh.OgvJsSupport.js create mode 100644 extensions/TimedMediaHandler/resources/ext.tmh.TimedTextSelector.js create mode 100644 extensions/TimedMediaHandler/resources/ext.tmh.transcodetable.js create mode 100644 extensions/TimedMediaHandler/resources/mw.MediaWikiPlayer.loader.js create mode 100644 extensions/TimedMediaHandler/resources/mw.MediaWikiPlayerSupport.js create mode 100644 extensions/TimedMediaHandler/resources/mw.PopUpThumbVideo.js create mode 100644 extensions/TimedMediaHandler/resources/mw.TMHGalleryHook.js create mode 100644 extensions/TimedMediaHandler/resources/player_big_play_button.png create mode 100644 extensions/TimedMediaHandler/resources/player_big_play_button_hover.png create mode 100644 extensions/TimedMediaHandler/resources/transcodeTable.css create mode 100644 extensions/TimedMediaHandler/tests/phpunit/ApiTestCaseVideoUpload.php create mode 100644 extensions/TimedMediaHandler/tests/phpunit/README create mode 100644 extensions/TimedMediaHandler/tests/phpunit/TestApiUploadVideo.php create mode 100644 extensions/TimedMediaHandler/tests/phpunit/TestOggHandler.php create mode 100644 extensions/TimedMediaHandler/tests/phpunit/TestTimeParsing.php create mode 100644 extensions/TimedMediaHandler/tests/phpunit/TestTimedMediaHandler.php create mode 100644 extensions/TimedMediaHandler/tests/phpunit/TestTimedMediaTransformOutput.php create mode 100644 extensions/TimedMediaHandler/tests/phpunit/TestVideoThumbnail.php create mode 100644 extensions/TimedMediaHandler/tests/phpunit/TestVideoTranscode.php create mode 100644 extensions/TimedMediaHandler/tests/phpunit/TestWebMHandler.php create mode 100644 extensions/TimedMediaHandler/tests/phpunit/media/README create mode 100644 extensions/TimedMediaHandler/tests/phpunit/media/VP9-tractor.webm create mode 100644 extensions/TimedMediaHandler/tests/phpunit/media/bear-vp9-opus.webm create mode 100644 extensions/TimedMediaHandler/tests/phpunit/media/broken-file.ogg create mode 100644 extensions/TimedMediaHandler/tests/phpunit/media/doubleTag.oga create mode 100644 extensions/TimedMediaHandler/tests/phpunit/media/shuttle10seconds.1080x608.webm create mode 100644 extensions/TimedMediaHandler/tests/phpunit/media/test5seconds.electricsheep.300x400.ogv create mode 100644 extensions/TimedMediaHandler/version diff --git a/extensions/TimedMediaHandler/.gitignore b/extensions/TimedMediaHandler/.gitignore new file mode 100644 index 00000000..0172e092 --- /dev/null +++ b/extensions/TimedMediaHandler/.gitignore @@ -0,0 +1,6 @@ +.svn +*~ +*.kate-swp +.*.swp +.project +node_modules/ diff --git a/extensions/TimedMediaHandler/.gitreview b/extensions/TimedMediaHandler/.gitreview new file mode 100644 index 00000000..39e4aad6 --- /dev/null +++ b/extensions/TimedMediaHandler/.gitreview @@ -0,0 +1,5 @@ +[gerrit] +host=gerrit.wikimedia.org +port=29418 +project=mediawiki/extensions/TimedMediaHandler.git +defaultbranch=REL1_26 diff --git a/extensions/TimedMediaHandler/.jscsrc b/extensions/TimedMediaHandler/.jscsrc new file mode 100644 index 00000000..9d22e3f2 --- /dev/null +++ b/extensions/TimedMediaHandler/.jscsrc @@ -0,0 +1,3 @@ +{ + "preset": "wikimedia" +} diff --git a/extensions/TimedMediaHandler/.jshintignore b/extensions/TimedMediaHandler/.jshintignore new file mode 100644 index 00000000..0f77f29d --- /dev/null +++ b/extensions/TimedMediaHandler/.jshintignore @@ -0,0 +1 @@ +MwEmbedModules diff --git a/extensions/TimedMediaHandler/.jshintrc b/extensions/TimedMediaHandler/.jshintrc new file mode 100644 index 00000000..ef363f81 --- /dev/null +++ b/extensions/TimedMediaHandler/.jshintrc @@ -0,0 +1,44 @@ +{ + /* Common */ + + // Enforcing + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "latedef": true, + "newcap": true, + "noarg": true, + "noempty": true, + "nonew": true, + "quotmark": "single", + "trailing": true, + "undef": true, + "unused": true, + // Legacy + "onevar": true, + + /* Local */ + + // Enforcing + "bitwise": true, + "es3": true, + // Relaxing + "laxbreak": true, + "smarttabs": true, + "multistr": true, + // Environment + "browser": true, + // Legacy + "nomen": true, + + "predef": [ + "mediaWiki", + "mw", + "jQuery", + "OGVVersion", + "OGVSupport", + "OGVPlayer", + "OGVLoader" + ] +} diff --git a/extensions/TimedMediaHandler/ApiQueryVideoInfo.php b/extensions/TimedMediaHandler/ApiQueryVideoInfo.php new file mode 100644 index 00000000..65239166 --- /dev/null +++ b/extensions/TimedMediaHandler/ApiQueryVideoInfo.php @@ -0,0 +1,268 @@ +getHandler() && $file->getHandler() instanceof TimedMediaHandler ) { + $vals['derivatives'] = WebVideoTranscode::getSources( $file, array( 'fullurl') ); + $result->setIndexedTagName( $vals['derivatives'], "derivative" ); + } else { + // Non-TMH file, no derivatives. + $vals['derivatives'] = array(); + } + } + return $vals; + } + + public static function getPropertyNames( $filter = array() ) { + $prop = parent::getPropertyNames(); + $prop[] = 'derivatives'; + return $prop; + } + + public static function getPropertyDescriptions( $filter = array(), $modulePrefix = '' ) { + $s = parent::getPropertyDescriptions( $filter, $modulePrefix ); + $s[] = ' derivatives - Adds an array of the different format and quality versions of an audio or video file that are available.'; + return $s; + } + + /** + * @deprecated since MediaWiki core 1.25 + */ + public function getExamples() { + return array( + 'api.php?action=query&titles=File:Folgers.ogv&prop=videoinfo', + ); + } + + /** + * @see ApiBase::getExamplesMessages() + */ + protected function getExamplesMessages() { + return array( + 'action=query&titles=File:Folgers.ogv&prop=videoinfo' + => 'apihelp-query+videoinfo-example-1', + ); + } + + /** + * Execute and getAllowedprops have to be copied verbatim because of static self:: references + * + * With late static binding this would be avoidable: + * http://php.net/manual/en/language.oop5.late-static-bindings.php + */ + public function execute() { + $params = $this->extractRequestParams(); + + $prop = array_flip( $params['prop'] ); + + $scale = $this->getScale( $params ); + + $pageIds = $this->getPageSet()->getAllTitlesByNamespace(); + if ( !empty( $pageIds[NS_FILE] ) ) { + $titles = array_keys( $pageIds[NS_FILE] ); + asort( $titles ); // Ensure the order is always the same + + $skip = false; + if ( !is_null( $params['continue'] ) ) { + $skip = true; + $cont = explode( '|', $params['continue'] ); + if ( count( $cont ) != 2 ) { + $this->dieUsage( 'Invalid continue param. You should pass the original ' . + 'value returned by the previous query', '_badcontinue' ); + } + $fromTitle = strval( $cont[0] ); + $fromTimestamp = $cont[1]; + // Filter out any titles before $fromTitle + foreach ( $titles as $key => $title ) { + if ( $title < $fromTitle ) { + unset( $titles[$key] ); + } else { + break; + } + } + } + + $result = $this->getResult(); + $images = RepoGroup::singleton()->findFiles( $titles ); + foreach ( $images as $img ) { + // Skip redirects + if ( $img->getOriginalTitle()->isRedirect() ) { + continue; + } + + $start = $skip ? $fromTimestamp : $params['start']; + $pageId = $pageIds[NS_IMAGE][ $img->getOriginalTitle()->getDBkey() ]; + + $fit = $result->addValue( + array( 'query', 'pages', intval( $pageId ) ), + 'imagerepository', $img->getRepoName() + ); + if ( !$fit ) { + if ( count( $pageIds[NS_IMAGE] ) == 1 ) { + // The user is screwed. imageinfo can't be solely + // responsible for exceeding the limit in this case, + // so set a query-continue that just returns the same + // thing again. When the violating queries have been + // out-continued, the result will get through + $this->setContinueEnumParameter( 'start', + wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) ); + } else { + $this->setContinueEnumParameter( 'continue', + $this->getContinueStr( $img ) ); + } + break; + } + + // Check if we can make the requested thumbnail, and get transform parameters. + $finalThumbParams = $this->mergeThumbParams( $img, $scale, $params['urlparam'] ); + + // Get information about the current version first + // Check that the current version is within the start-end boundaries + $gotOne = false; + if ( + ( is_null( $start ) || $img->getTimestamp() <= $start ) && + ( is_null( $params['end'] ) || $img->getTimestamp() >= $params['end'] ) + ) { + $gotOne = true; + + $fit = $this->addPageSubItem( $pageId, + self::getInfo( $img, $prop, $result, $finalThumbParams ) ); + if ( !$fit ) { + if ( count( $pageIds[NS_IMAGE] ) == 1 ) { + // See the 'the user is screwed' comment above + $this->setContinueEnumParameter( 'start', + wfTimestamp( TS_ISO_8601, $img->getTimestamp() ) ); + } else { + $this->setContinueEnumParameter( 'continue', + $this->getContinueStr( $img ) ); + } + break; + } + } + + // Now get the old revisions + // Get one more to facilitate query-continue functionality + $count = ( $gotOne ? 1 : 0 ); + $oldies = $img->getHistory( $params['limit'] - $count + 1, $start, $params['end'] ); + foreach ( $oldies as $oldie ) { + if ( ++$count > $params['limit'] ) { + // We've reached the extra one which shows that there are additional pages to be had. Stop here... + // Only set a query-continue if there was only one title + if ( count( $pageIds[NS_FILE] ) == 1 ) { + $this->setContinueEnumParameter( 'start', + wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) ); + } + break; + } + $fit = $this->addPageSubItem( $pageId, + self::getInfo( $oldie, $prop, $result, $finalThumbParams ) ); + if ( !$fit ) { + if ( count( $pageIds[NS_IMAGE] ) == 1 ) { + $this->setContinueEnumParameter( 'start', + wfTimestamp( TS_ISO_8601, $oldie->getTimestamp() ) ); + } else { + $this->setContinueEnumParameter( 'continue', + $this->getContinueStr( $oldie ) ); + } + break; + } + } + if ( !$fit ) { + break; + } + $skip = false; + } + + if ( defined( 'ApiResult::META_CONTENT' ) ) { + $pages = (array)$this->getResult()->getResultData( array( 'query', 'pages' ), array( 'Strip' => 'base' ) ); + } else { + $data = $this->getResultData(); + $pages = $data['query']['pages']; + } + foreach ( $pages as $pageid => $arr ) { + if ( !isset( $arr['imagerepository'] ) ) { + $result->addValue( + array( 'query', 'pages', intval( $pageid ) ), + 'imagerepository', '' + ); + } + // The above can't fail because it doesn't increase the result size + } + } + } + + public function getAllowedParams() { + // Get imageinfo params + $params = array_intersect_key( + parent::getAllowedParams(), + array_flip( array( + 'limit', 'start', 'end', 'urlwidth', 'urlheight', 'urlparam', 'continue' + ) ) + ); + if ( defined( 'ApiBase::PARAM_HELP_MSG' ) ) { + foreach ( $params as $k => $v ) { + if ( !isset( $params[$k][ApiBase::PARAM_HELP_MSG] ) ) { + $params[$k][ApiBase::PARAM_HELP_MSG] = "apihelp-query+imageinfo-param-$k"; + } + } + } + + // Add our param + $params = array( + 'prop' => array( + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_DFLT => 'timestamp|user', + ApiBase::PARAM_TYPE => self::getPropertyNames(), + ), + ) + $params; + + return $params; + } + + /** + * Get API self-documentation. + * + * Needed since core calls self::getPropertyDescriptions(), + * (and not static::getPropertyDescriptions() ) which binds + * to the static method in that class instead of the static + * method of the same name in this class. + * @deprecated since MediaWiki core 1.25 + */ + public function getParamDescription() { + $params = parent::getParamDescription(); + $p = $this->getModulePrefix(); + $params['prop'] = self::getPropertyDescriptions( array(), $p ); + return $params; + } +} diff --git a/extensions/TimedMediaHandler/ApiTranscodeReset.php b/extensions/TimedMediaHandler/ApiTranscodeReset.php new file mode 100644 index 00000000..597d17e1 --- /dev/null +++ b/extensions/TimedMediaHandler/ApiTranscodeReset.php @@ -0,0 +1,184 @@ +dieUsage( 'Transcode is disabled on this wiki', 'disabledtranscode' ); + } + + // Confirm the user has the transcode-reset right + if( !$wgUser->isAllowed( 'transcode-reset' ) ){ + $this->dieUsage( 'You don\'t have permission to reset transcodes', 'missingpermission' ); + } + $params = $this->extractRequestParams(); + + // Make sure we have a valid Title + $titleObj = Title::newFromText( $params['title'] ); + if ( !$titleObj || $titleObj->isExternal() ) { + $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) ); + } + // Make sure the title can be transcoded + if( !TimedMediaHandlerHooks::isTranscodableTitle( $titleObj ) ){ + $this->dieUsageMsg( array( 'invalidtranscodetitle', $params['title'] ) ); + } + $transcodeKey = false; + // Make sure its a enabled transcode key we are trying to remove: + // ( if you update your transcode keys the api is not how you purge the database of expired keys ) + if( isset( $params['transcodekey'] ) ){ + global $wgEnabledTranscodeSet, $wgEnabledAudioTranscodeSet; + $transcodeSet = array_merge($wgEnabledTranscodeSet, $wgEnabledAudioTranscodeSet); + if( !in_array( $params['transcodekey'], $transcodeSet ) ){ + $this->dieUsage( 'Invalid or disabled transcode key: ' . htmlspecialchars( $params['transcodekey'] ) , 'badtranscodekey' ); + } else { + $transcodeKey = $params['transcodekey']; + } + } + + // Don't reset if less than 1 hour has passed and we have no error ) + $file = wfFindFile( $titleObj ); + $timeSinceLastReset = self::checkTimeSinceLastRest( $file, $transcodeKey ); + if( $timeSinceLastReset < $wgWaitTimeForTranscodeReset){ + $this->dieUsage( 'Not enough time has passed since the last reset of this transcode. ' . + TimedMediaHandler::getTimePassedMsg( $wgWaitTimeForTranscodeReset - $timeSinceLastReset ) . + ' until this transcode can be reset', 'notenoughtimereset'); + } + + // All good do the transcode removal: + WebVideoTranscode::removeTranscodes( $file, $transcodeKey ); + + // Oh and we wanted to reset it, right? Trigger again. + WebVideoTranscode::updateJobQueue( $file, $transcodeKey ); + + $this->getResult()->addValue(null, 'success', 'removed transcode'); + } + + /** + * @param $file + * @param $transcodeKey + * @return int|string + */ + static public function checkTimeSinceLastRest( $file, $transcodeKey ){ + global $wgWaitTimeForTranscodeReset; + $transcodeStates = WebVideoTranscode::getTranscodeState( $file ); + if( $transcodeKey ){ + if( ! $transcodeStates[$transcodeKey] ){ + // transcode key not found + return $wgWaitTimeForTranscodeReset + 1; + } + return self::getStateResetTime( $transcodeStates[$transcodeKey] ); + } + // least wait is set to reset time: + $leastWait = $wgWaitTimeForTranscodeReset + 1; + // else check for lowest reset time + foreach($transcodeStates as $state ){ + $ctime = self::getStateResetTime( $state ); + if( $ctime < $leastWait){ + $leastWait = $ctime; + } + } + return $leastWait; + } + + /** + * @param $state + * @return int|string + */ + static public function getStateResetTime( $state ){ + global $wgWaitTimeForTranscodeReset; + $db = wfGetDB( DB_SLAVE ); + // if an error return waitTime +1 + if( !is_null( $state['time_error']) ){ + return $wgWaitTimeForTranscodeReset + 1; + } + // return wait time from most recent event + foreach( array( 'time_success', 'time_startwork', 'time_addjob' ) as $timeField ){ + if( !is_null( $state[ $timeField ] )){ + return $db->timestamp() - $db->timestamp( $state[ $timeField ] ); + } + } + // No time info, return resetWaitTime + return $wgWaitTimeForTranscodeReset + 1; + } + + public function mustBePosted() { + return true; + } + + public function isWriteMode() { + return true; + } + + /** + * @deprecated since MediaWiki core 1.25 + */ + protected function getDescription() { + return 'Users with the \'transcode-reset\' right can reset and re-run a transcode job'; + } + + protected function getAllowedParams() { + return array( + 'title' => array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => true + ), + 'transcodekey' => null, + 'token' => null, + ); + } + + /** + * @deprecated since MediaWiki core 1.25 + */ + protected function getParamDescription() { + return array( + 'title' => 'The media file title', + 'transcodekey' => 'The transcode key you wish to reset', + 'token' => 'An edit token obtained via action=tokens', + ); + } + + public function needsToken() { + return 'csrf'; + } + + public function getTokenSalt() { + return ''; + } + + /** + * @deprecated since MediaWiki core 1.25 + */ + protected function getExamples() { + return array( + 'Reset all transcodes for Clip.webm :', + ' api.php?action=transcodereset&title=File:Clip.webm&token=%2B\\', + 'Reset the \'360_560kbs.webm\' transcode key for clip.webm. Get a list of transcode keys via a \'transcodestatus\' query', + ' api.php?action=transcodereset&title=File:Clip.webm&transcodekey=360_560kbs.webm&token=%2B\\', + ); + } + + /** + * @see ApiBase::getExamplesMessages() + */ + protected function getExamplesMessages() { + return array( + 'action=transcodereset&title=File:Clip.webm&token=123ABC' + => 'apihelp-transcodereset-example-1', + 'action=transcodereset&title=File:Clip.webm&transcodekey=360_560kbs.webm&token=123ABC' + => 'apihelp-transcodereset-example-2', + ); + } +} diff --git a/extensions/TimedMediaHandler/ApiTranscodeStatus.php b/extensions/TimedMediaHandler/ApiTranscodeStatus.php new file mode 100644 index 00000000..38dcccc6 --- /dev/null +++ b/extensions/TimedMediaHandler/ApiTranscodeStatus.php @@ -0,0 +1,79 @@ +getPageSet()->getAllTitlesByNamespace(); + // Make sure we have files in the title set: + if ( !empty( $pageIds[NS_FILE] ) ) { + $titles = array_keys( $pageIds[NS_FILE] ); + asort( $titles ); // Ensure the order is always the same + + $result = $this->getResult(); + $images = RepoGroup::singleton()->findFiles( $titles ); + /** + * @var $img File + */ + foreach ( $images as $img ) { + // if its a "transcode" add the transcode status table output + if( TimedMediaHandlerHooks::isTranscodableTitle( $img->getTitle() ) ){ + $transcodeStatus = WebVideoTranscode::getTranscodeState( $img ); + // remove useless properties + foreach($transcodeStatus as $key=>&$val ){ + unset( $val['id'] ); + unset( $val['image_name']); + unset( $val['key'] ); + } + $result->addValue( array( 'query', 'pages', $img->getTitle()->getArticleID() ), 'transcodestatus', $transcodeStatus ); + } + } + } + } + + public function getCacheMode( $params ) { + return 'public'; + } + + public function getAllowedParams() { + return array(); + } + + /** + * @deprecated since MediaWiki core 1.25 + */ + public function getDescription() { + return array( + 'Get transcode status for a given file page' + ); + } + + /** + * @deprecated since MediaWiki core 1.25 + */ + protected function getExamples() { + return array ( + 'api.php?action=query&prop=transcodestatus&titles=File:Clip.webm', + ); + } + + /** + * @see ApiBase::getExamplesMessages() + */ + protected function getExamplesMessages() { + return array( + 'action=query&prop=transcodestatus&titles=File:Clip.webm' + => 'apihelp-query+transcodestatus-example-1', + ); + } +} diff --git a/extensions/TimedMediaHandler/COPYING b/extensions/TimedMediaHandler/COPYING new file mode 100644 index 00000000..019694a9 --- /dev/null +++ b/extensions/TimedMediaHandler/COPYING @@ -0,0 +1,342 @@ +== GNU GENERAL PUBLIC LICENSE == + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +=== Preamble === + +The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + +We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + +Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and +modification follow. + +== TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION == + +'''0.''' This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + +'''1.''' You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + +'''2.''' You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + '''a)''' You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + '''b)''' You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + '''c)''' If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +'''3.''' You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + '''a)''' Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + '''b)''' Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + '''c)''' Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +'''4.''' You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + +'''5.''' You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +'''6.''' Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +'''7.''' If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +'''8.''' If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + +'''9.''' The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + +'''10.''' If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + +=== NO WARRANTY === + +'''11.''' BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + +'''12.''' IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + '''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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + + Copyright (C) + + 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 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/extensions/TimedMediaHandler/Gruntfile.js b/extensions/TimedMediaHandler/Gruntfile.js new file mode 100644 index 00000000..45380710 --- /dev/null +++ b/extensions/TimedMediaHandler/Gruntfile.js @@ -0,0 +1,34 @@ +/*jshint node:true */ +module.exports = function ( grunt ) { + grunt.loadNpmTasks( 'grunt-contrib-jshint' ); + grunt.loadNpmTasks( 'grunt-jsonlint' ); + grunt.loadNpmTasks( 'grunt-banana-checker' ); + grunt.loadNpmTasks( 'grunt-jscs' ); + + grunt.initConfig( { + jshint: { + options: { + jshintrc: true + }, + all: [ + '*.js' + ] + }, + jscs: { + src: '<%= jshint.all %>' + }, + banana: { + all: 'i18n/' + }, + jsonlint: { + all: [ + '*.json', + '**/*.json', + '!node_modules/**' + ] + } + } ); + + grunt.registerTask( 'test', [ 'jshint', 'jscs', 'jsonlint', 'banana' ] ); + grunt.registerTask( 'default', 'test' ); +}; diff --git a/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/EmbedPlayer.config.php b/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/EmbedPlayer.config.php new file mode 100644 index 00000000..af32b4d7 --- /dev/null +++ b/extensions/TimedMediaHandler/MwEmbedModules/EmbedPlayer/EmbedPlayer.config.php @@ -0,0 +1,194 @@ + true, + + // The preferred media codec preference + // Note user selected format order + 'EmbedPlayer.CodecPreference' => array( 'vp9', 'webm', 'h264', 'ogg' ), + + // If video tag support should be disabled all-together, used to test + // fallback decoding methods and direct file links + 'EmbedPlayer.DisableVideoTagSupport' => false, + + // If detected browser flash support should be ignored, and flash support, + // set to false. This will eliminate support for flash based playback. + 'EmbedPlayer.DisableHTML5FlashFallback'=> false, + + // Can be used to set player sources via configuration, useful in cases, + // where metadata is loaded from an api, but actual sources need to be pointed + // to another location at runtime via config + 'EmbedPlayer.ReplaceSources' => null, + + // If the flavor selector menu option should be displayed: + // This will be enabled by default in some future release of the library + 'EmbedPlayer.EnableFlavorSelector' => false, + + // If the iPad should use html controls + // With html controls you can't access native fullscreen + // With html controls you can support html themed controls, overlays, ads etc. ) + 'EmbedPlayer.EnableIpadHTMLControls' => true, + + // If the webkit-playsinline attribute should be added to the video tag. Will cause the player + // to play inline on iPhone + 'EmbedPlayer.WebKitPlaysInline'=> false, + + // If we should use the native device fullscreen call, + // this gives you hybrid player, where controls are in HTML for in browser playback, + // and native controls when the fullscreen button is pressed. + // its not the default since often users want playback control if using html controls, + // ( i.e no add skip etc ) + 'EmbedPlayer.EnableIpadNativeFullscreen' => false, + + // By default we display an html play screen, image thumb and play button. + // even though this is still a 'native' player. + // If you are not using ad plugins you may want to set this to false + // and display the native play button: + 'EmbedPlayer.iPhoneShowHTMLPlayScreen' => true, + + // If the large play button should be disabled on replay: + 'EmbedPlayer.ForceLargeReplayButton' => false, + + // The attribution library page + 'EmbedPlayer.LibraryPage' => 'http://www.kaltura.org/project/HTML5_Video_Media_JavaScript_Library', + + // What tags will be re-written to video player by default + // Set to empty string or null to avoid automatic video tag rewrites to embedPlayer + "EmbedPlayer.RewriteSelector" => "video,audio,playlist", + + // Default video size ( if no size provided ) + "EmbedPlayer.DefaultSize" => "400x300", + + // Default player controls size: + 'EmbedPlayer.ControlsHeight' => 31, + + // Default time display size: + 'EmbedPlayer.TimeDisplayWidth' => 85, + + // If the video player should attribute kaltura + "EmbedPlayer.KalturaAttribution" => true, + + // The attribution button + 'EmbedPlayer.AttributionButton' => array( + 'title' => 'Kaltura html5 video library', + 'href' => 'http://www.kaltura.com', + // Style icon to be applied + 'class' => 'kaltura-icon', + // Style to be applied to the outer attribution button container div + 'style' => array(), + // An icon image url 16x16 image url or data url ) + 'iconurl' => false + ), + + // If the options control bar menu item should be enabled: + 'EmbedPlayer.EnableOptionsMenu' => false, + + // If users can right click on the player + 'EmbedPlayer.EnableRightClick' => true, + + // Default supported menu items is merged with skin menu items + 'EmbedPlayer.EnabledOptionsMenuItems' => array( + // Player Select + 'playerSelect', + + // Download the file menu + 'download', + + // Share the video menu + 'share', + + // Player library link + 'aboutPlayerLibrary' + ), + + // If the player should wait for metadata like video size and duration, before trying to draw + // the player interface. + 'EmbedPlayer.WaitForMeta' => true, + + // Set the browser player warning flag displays warning for non optimal playback + "EmbedPlayer.ShowNativeWarning" => true, + + // If player errors / alerts should be displayed: + "EmbedPlayer.ShowPlayerAlerts" => true, + + // If fullscreen is global enabled. + "EmbedPlayer.EnableFullscreen" => true, + + // If the embed player time display should be shown + 'EmbedPlayer.EnableTimeDisplay' => true, + + // If the embed player volume control should be displayed + 'EmbedPlayer.EnableVolumeControl' => true, + + // If fullscreen should pop-open a new window + //( instead of trying to expand the video player to browser fullscreen ) + "EmbedPlayer.NewWindowFullscreen" => false, + + // If a fullscreen tip to press f11 should be displayed when entering fullscreen + "EmbedPlayer.FullscreenTip" => true, + + // if the browser should display a warning for direct file links: + "EmbedPlayer.DirectFileLinkWarning" => false, + + "EmbedPlayer.FirefoxLink" => 'http://www.mozilla.com/en-US/firefox/upgrade.html?from=mwEmbed', + + // If mwEmbed should use the Native player controls + // this will prevent video tag rewriting and skinning + // useful for devices such as iPad / iPod that + // don't fully support DOM overlays or don't expose full-screen + // functionality to javascript + "EmbedPlayer.NativeControls" => false, + + // If mwEmbed should use native controls on mobile safari + "EmbedPlayer.NativeControlsMobileSafari" => true, + + // The z-index given to the player interface during full screen ( high z-index ) + "EmbedPlayer.FullScreenZIndex" => 999998, + + // The default share embed mode ( can be "iframe" or "xssVideo" ) + // + // "iframe" will provide a + * + */ + +class TimedMediaIframeOutput { + /** + * The iframe hook check file pages embedplayer=yes + * @param $title Title + * @param $article Article + * @param bool $doOutput + * @return bool + */ + static function iframeHook( &$title, &$article, $doOutput = true ) { + global $wgRequest, $wgOut, $wgEnableIframeEmbed; + if( !$wgEnableIframeEmbed ) + return true; //continue normal output iframes are "off" (maybe throw a warning in the future) + + // Make sure we are in the right namespace and iframe=true was called: + if( is_object( $title ) && $title->getNamespace() == NS_FILE && + $wgRequest->getVal('embedplayer') == 'yes' && + $wgEnableIframeEmbed && + $doOutput ){ + + if ( self::outputIframe( $title ) ) { + // Turn off output of anything other than the iframe + $wgOut->disable(); + } + } + + return true; + } + + /** + * Output an iframe + * @param $title Title + * @throws Exception + */ + static function outputIframe( $title ) { + global $wgEnableIframeEmbed, $wgOut, $wgUser, $wgBreakFrames; + + if( !$wgEnableIframeEmbed ){ + return false; + } + + // Setup the render parm + $file = wfFindFile( $title ); + if ( !$file ) { + // file was removed, show wiki page with warning + return false; + } + $params = array( + 'fillwindow' => true + ); + $videoTransform = $file->transform( $params ); + + // Definitely do not want to break frames + $wgBreakFrames = false; + $wgOut->allowClickjacking(); + + $wgOut->addModules( array( 'embedPlayerIframeStyle', 'mw.EmbedPlayer' ) ); + $wgOut->sendCacheControl(); + ?> + + + + +<?php echo $title->getText() ?> + 'ResourceLoaderDynamicStyles', 'content' => '' ) ); + ?> + getHeadLinksArray() ); + echo implode( "\n", $wgOut->getHeadLinksArray() ); + ?> + + getHeadScripts(); ?> + + + + + + getBottomScripts(); ?> + + + + getWidth() && + $options['height'] != $options['file']->getHeight() + ){ + return self::resizeThumb( $options ); + } + // try OggThumb, and fallback to ffmpeg + $result = self::tryOggThumb( $options ); + if ( $result === false ) { + return self::tryFfmpegThumb( $options ); + } + return $result; + } + + /** + * Run oggThumb to generate a still image from a video file, using a frame + * close to the given number of seconds from the start. + * + * @param $options array + * @return bool|MediaTransformError + * + */ + static function tryOggThumb( $options ) { + global $wgOggThumbLocation; + + // Check that the file is 'ogg' format + if( $options['file']->getHandler()->getMetadataType( $options['file'] ) != 'ogg' ){ + return false; + } + + // Check for $wgOggThumbLocation + if( !$wgOggThumbLocation || !is_file( $wgOggThumbLocation ) ){ + return false; + } + + $time = self::getThumbTime( $options ); + $dstPath = $options['dstPath']; + $videoPath = $options['file']->getLocalRefPath(); + + $cmd = wfEscapeShellArg( $wgOggThumbLocation ) + . ' -t ' . floatval( $time ); + // Set the output size if set in options: + if( isset( $options['width'] ) && isset( $options['height'] ) ){ + $cmd.= ' -s '. intval( $options['width'] ) . 'x' . intval( $options['height'] ); + } + $cmd .= ' -n ' . wfEscapeShellArg( $dstPath ) . + ' ' . wfEscapeShellArg( $videoPath ) . ' 2>&1'; + $retval = 0; + $returnText = wfShellExec( $cmd, $retval ); + + if ( $options['file']->getHandler()->removeBadFile( $dstPath, $retval ) || $retval ) { + // oggThumb spams both stderr and stdout with useless progress + // messages, and then often forgets to output anything when + // something actually does go wrong. So interpreting its output is + // a challenge. + $lines = explode( "\n", str_replace( "\r\n", "\n", $returnText ) ); + if ( count( $lines ) > 0 + && preg_match( '/invalid option -- \'n\'$/', $lines[0] ) ) + { + $returnText = wfMessage( 'timedmedia-oggThumb-version', '0.9' )->inContentLanguage()->text(); + } else { + $returnText = wfMessage( 'timedmedia-oggThumb-failed' )->inContentLanguage()->text(); + } + return new MediaTransformError( 'thumbnail_error', + $options['width'], $options['height'], $returnText ); + } + return true; + } + + /** + * @param $options array + * @return bool|MediaTransformError + */ + static function tryFfmpegThumb( $options ){ + global $wgFFmpegLocation, $wgMaxShellMemory; + + if( !$wgFFmpegLocation || !is_file( $wgFFmpegLocation ) ){ + return false; + } + + $cmd = wfEscapeShellArg( $wgFFmpegLocation ) . ' -threads 1 '; + + $offset = intval( self::getThumbTime( $options ) ); + /* + This is a workaround until ffmpegs ogg demuxer properly seeks to keyframes. + Seek N seconds before offset and seek in decoded stream after that. + -ss before input seeks without decode + -ss after input seeks in decoded stream + + N depends on framerate of input, keyframe interval defaults + to 64 for most encoders, seeking a bit before that + */ + + $framerate = $options['file']->getHandler()->getFramerate( $options['file'] ); + if ( $framerate > 0 ) { + $seekoffset = 1 + intval( 64 / $framerate ); + } else { + $seekoffset = 3; + } + + if($offset > $seekoffset) { + $cmd .= ' -ss ' . floatval($offset - $seekoffset); + $offset = $seekoffset; + } + + //try to get temorary local url to file + $backend = $options['file']->getRepo()->getBackend(); + // getFileHttpUrl was only added in mw 1.21, dont fail if it does not exist + if ( method_exists( $backend, 'getFileHttpUrl' ) ) { + $src = $backend->getFileHttpUrl( array( + 'src' => $options['file']->getPath() + ) ); + } else { + $src = null; + } + if ( $src == null ) { + $src = $options['file']->getLocalRefPath(); + } + + $cmd .= ' -y -i ' . wfEscapeShellArg( $src ); + $cmd .= ' -ss ' . $offset . ' '; + + // Set the output size if set in options: + if( isset( $options['width'] ) && isset( $options['height'] ) ){ + $cmd.= ' -s '. intval( $options['width'] ) . 'x' . intval( $options['height'] ); + } + + # MJPEG, that's the same as JPEG except it's supported by the windows build of ffmpeg + # No audio, one frame + $cmd .= ' -f mjpeg -an -vframes 1 ' . + wfEscapeShellArg( $options['dstPath'] ) . ' 2>&1'; + + $retval = 0; + $returnText = wfShellExec( $cmd, $retval ); + // Check if it was successful + if ( !$options['file']->getHandler()->removeBadFile( $options['dstPath'], $retval ) ) { + return true; + } + $returnText = $cmd . "\nwgMaxShellMemory: $wgMaxShellMemory\n" . $returnText; + // Return error box + return new MediaTransformError( 'thumbnail_error', $options['width'], $options['height'], $returnText ); + } + + /** + * @param $options array + * @return bool|MediaTransformError + */ + static function resizeThumb( $options ) { + $file = $options['file']; + $params = array(); + foreach( array( 'start', 'thumbtime' ) as $key ) { + if( isset( $options[ $key ] ) ) { + $params[ $key ] = $options[ $key ]; + } + } + $params["width"] = $file->getWidth(); + $params["height"] = $file->getHeight(); + + $poolKey = $file->getRepo()->getSharedCacheKey( 'file', md5( $file->getName() ) ); + $posOptions = array_flip( array( 'start', 'thumbtime' ) ); + $poolKey = wfAppendQuery( $poolKey, array_intersect_key( $options, $posOptions ) ); + + if ( class_exists( 'PoolCounterWorkViaCallback' ) ) { + $work = new PoolCounterWorkViaCallback( 'TMHTransformFrame', + '_tmh:frame:' . $poolKey, + array( 'doWork' => function() use ($file, $params) { + return $file->transform( $params, File::RENDER_NOW ); + } ) ); + $thumb = $work->execute(); + } else { + $thumb = $file->transform( $params, File::RENDER_NOW ); + } + + if ( !$thumb || $thumb->isError() ) { + return $thumb; + } + $src = $thumb->getStoragePath(); + if ( !$src ) { + return false; + } + $thumbFile = new UnregisteredLocalFile( $file->getTitle(), + RepoGroup::singleton()->getLocalRepo(), $src, false ); + $thumbParams = array( + "width" => $options['width'], + "height" => $options['height'] + ); + $handler = $thumbFile->getHandler(); + if ( !$handler ) { + return false; + } + $scaledThumb = $handler->doTransform( + $thumbFile, + $options['dstPath'], + $options['dstPath'], + $thumbParams + ); + + if ( !$scaledThumb || $scaledThumb->isError() ) { + return $scaledThumb; + } + return true; + } + + /** + * @param $options array + * @return bool|float|int + */ + static function getThumbTime( $options ){ + $length = $options['file']->getLength(); + + // If start time param isset use that for the thumb: + if( isset( $options['start'] ) ) { + $thumbtime = TimedMediaHandler::parseTimeString( $options['start'], $length ); + if( $thumbtime !== false ) + return $thumbtime; + } + // else use thumbtime + if ( isset( $options['thumbtime'] ) ) { + $thumbtime = TimedMediaHandler::parseTimeString( $options['thumbtime'], $length ); + if( $thumbtime !== false ) + return $thumbtime; + } + // Seek to midpoint by default, it tends to be more interesting than the start + return $length / 2; + } +} diff --git a/extensions/TimedMediaHandler/TimedMediaTransformOutput.php b/extensions/TimedMediaHandler/TimedMediaTransformOutput.php new file mode 100644 index 00000000..36532093 --- /dev/null +++ b/extensions/TimedMediaHandler/TimedMediaTransformOutput.php @@ -0,0 +1,486 @@ +$key = $conf[$key]; + } else { + $this->$key = false; + } + } + } + + /** + * @return TextHandler + */ + function getTextHandler(){ + if( !$this->textHandler ){ + // Init an associated textHandler + $this->textHandler = new TextHandler( $this->file ); + } + return $this->textHandler; + } + + /** + * Get the media transform thumbnail + * @return string + */ + function getUrl( $sizeOverride = false ){ + global $wgVersion, $wgResourceBasePath, $wgStylePath; + // Needs to be 1.24c because version_compare() works in confusing ways + if ( version_compare( $wgVersion, '1.24c', '>=' ) ) { + $url = "$wgResourceBasePath/resources/assets/file-type-icons/fileicon-ogg.png"; + } else { + $url = "$wgStylePath/common/images/icons/fileicon-ogg.png"; + } + + if ( $this->isVideo ) { + if ( $this->thumbUrl ) { + $url = $this->thumbUrl; + } + + // Update the $posterUrl to $sizeOverride ( if not an old file ) + if( !$this->file->isOld() && $sizeOverride && + $sizeOverride[0] && intval( $sizeOverride[0] ) != intval( $this->width ) ){ + $apiUrl = $this->getPoster( $sizeOverride[0] ); + if( $apiUrl ){ + $url = $apiUrl; + } + } + } + return $url; + } + + /** + * TODO get the local path + * @return mixed + */ + function getPath(){ + return $this->dstPath; + } + + /** + * @return int + */ + function getPlayerHeight(){ + // Check if "video" tag output: + if ( $this->isVideo ) { + return intval( $this->height ); + } else { + // Give sound files a height of 23px + return 23; + } + } + + /** + * @return int + */ + function getPlayerWidth(){ + // Check if "video" tag output: + if ( $this->isVideo ) { + return intval( $this->width ); + } else { + // Give sound files a width of 300px ( if unsized ) + if( $this->width == 0 ){ + return 300; + } + // else give the target size down to 35 px wide + return ( $this->width < 35 ) ? 35 : intval( $this->width ) ; + } + } + + /** + * @return string + */ + function getTagName(){ + return ( $this->isVideo ) ? 'video' : 'audio'; + } + + /** + * @param $options array + * @return string + * @throws Exception + */ + function toHtml( $options = array() ) { + if ( count( func_get_args() ) == 2 ) { + throw new Exception( __METHOD__ .' called in the old style' ); + } + + $oldHeight = $this->height; + $oldWidth = $this->width; + if ( isset( $options['override-height'] ) ) { + $this->height = $options['override-height']; + } + if ( isset( $options['override-width'] ) ) { + $this->width = $options['override-width']; + } + + if ( $this->useImagePopUp() ) { + $res = $this->getImagePopUp(); + } else { + $res = $this->getHtmlMediaTagOutput(); + } + $this->width = $oldWidth; + $this->height = $oldHeight; + return $res; + } + + /** + * Helper to determine if to use pop up dialog for videos + * + * @return boolean + */ + private function useImagePopUp() { + global $wgMinimumVideoPlayerSize; + // Check if the video is too small to play inline ( instead do a pop-up dialog ) + // If we're filling the window (e.g. during an iframe embed) one probably doesn't want the pop up. + // Also the pop up is broken in that case. + return $this->isVideo + && !$this->fillwindow + && $this->getPlayerWidth() < $wgMinimumVideoPlayerSize + // Do not do pop-up if its going to be the same size as inline player anyways + && $this->getPlayerWidth() < $this->getPopupPlayerWidth(); + } + + /** + * XXX migrate this to the mediawiki Html class as 'tagSet' helper function + * @param $tagName + * @param $tagSet + * @return string + */ + static function htmlTagSet( $tagName, $tagSet ){ + if( empty( $tagSet ) ){ + return ''; + } + $s = ''; + foreach( $tagSet as $attr ){ + $s .= Html::element( $tagName, $attr); + } + return $s; + } + + /** + * @return string + */ + function getImagePopUp(){ + // pop up videos set the autoplay attribute to true: + $autoPlay = true; + return Xml::tags( 'div' , array( + 'id' => self::PLAYER_ID_PREFIX . TimedMediaTransformOutput::$serial++, + 'class' => 'PopUpMediaTransform', + 'style' => "width:" . $this->getPlayerWidth() . "px;", + 'videopayload' => $this->getHtmlMediaTagOutput( $this->getPopupPlayerSize(), $autoPlay ), + ), + Xml::tags( 'img', array( + 'alt' => $this->file->getTitle(), + 'style' => "width:" . $this->getPlayerWidth() . "px;height:" . + $this->getPlayerHeight() . "px", + 'src' => $this->getUrl(), + ),'') + . + // For javascript disabled browsers provide a link to the asset: + Xml::tags( 'a', array( + 'href'=> $this->file->getUrl(), + 'title' => wfMessage( 'timedmedia-play-media' )->escaped(), + 'target' => 'new' + ), + Xml::tags( 'span', array( + 'class' => 'play-btn-large' + ), + // Have some sort of text for lynx & screen readers. + Html::element( + 'span', + array( 'class' => 'mw-tmh-playtext' ), + wfMessage( 'timedmedia-play-media' )->text() + ) + ) + ) + ); + } + + /** + * Get target popup player size + */ + function getPopupPlayerSize(){ + // Get the max width from the enabled transcode settings: + $maxImageSize = WebVideoTranscode::getMaxSizeWebStream(); + return WebVideoTranscode::getMaxSizeTransform( $this->file, $maxImageSize); + } + + /** + * Helper function to get pop up width + * + * Silly function because array index operations aren't allowed + * on function calls before php 5.4 + */ + private function getPopupPlayerWidth() { + list( $popUpWidth ) = $this->getPopupPlayerSize(); + return $popUpWidth; + } + + /** + * Sort media by bandwidth, but with things not wide enough at end + * + * The list should be in preferred source order, so we want the file + * with the lowest bitrate (to save bandwidth) first, but we also want + * appropriate resolution files before the 160p transcodes. + */ + private function sortMediaByBandwidth( $a, $b ) { + $width = $this->getPlayerWidth(); + $maxWidth = $this->getPopupPlayerWidth(); + if ( $this->useImagePopUp() || $width > $maxWidth ) { + // If its a pop-up player than we should use the pop up player size + // if its a normal player, but has a bigger width than the pop-up + // player, then we use the pop-up players width as the target width + // as that is equivalent to the max transcode size. Otherwise this + // will suggest the original file as the best source, which seems like + // a potentially bad idea, as it could be anything size wise. + $width = $maxWidth; + } + + if ( $a['width'] < $width && $b['width'] >= $width ) { + // $a is not wide enough but $b is + // so we consider $a > $b as we want $b before $a + return 1; + } + if ( $a['width'] >= $width && $b['width'] < $width ) { + // $b not wide enough, so $a must be preferred. + return -1; + } + if ( $a['width'] < $width && $b['width'] < $width && $a['width'] != $b['width'] ) { + // both are too small. Go with the one closer to the target width + return ( $a['width'] < $b['width'] ) ? -1 : 1; + } + // Both are big enough, or both equally too small. Go with the one + // that has a lower bit-rate (as it will be faster to download). + if ( isset( $a['bandwidth'] ) && isset( $b['bandwidth'] ) ) { + return ( $a['bandwidth'] < $b['bandwidth'] ) ? -1 : 1; + } + + // We have no firm basis for a comparison, so consider them equal. + return 0; + } + + /** + * Call mediaWiki xml helper class to build media tag output from + * supplied arrays + * @param $sizeOverride array + * @param $autoPlay boolean sets the autoplay attribute + * @return string + */ + function getHtmlMediaTagOutput( $sizeOverride = array(), $autoPlay = false ){ + // Try to get the first source src attribute ( usually this should be the source file ) + $mediaSources = $this->getMediaSources(); + reset( $mediaSources ); // do not rely on auto-resetting of arrays under HHVM + $firstSource = current( $mediaSources ); + + if( !$firstSource['src'] ){ + // XXX media handlers don't seem to work with exceptions.. + return 'Error missing media source'; + }; + + // Sort sources by bandwidth least to greatest ( so default selection on resource constrained + // browsers ( without js? ) go with minimal source. + usort( $mediaSources, array( $this, 'sortMediaByBandwidth' ) ); + + // We prefix some source attributes with data- to pass along to the javascript player + $prefixedSourceAttr = Array( 'width', 'height', 'title', 'shorttitle', 'bandwidth', 'framerate', 'disablecontrols' ); + foreach( $mediaSources as &$source ){ + foreach( $source as $attr => $val ){ + if( in_array( $attr, $prefixedSourceAttr ) ){ + $source[ 'data-' . $attr ] = $val; + unset( $source[ $attr ] ); + } + } + } + + $width = $sizeOverride ? $sizeOverride[0] : $this->getPlayerWidth(); + if( $this->fillwindow ){ + $width = '100%'; + } else { + $width .= 'px'; + } + // Build the video tag output: + $s = Xml::tags( 'div' , array( + 'class' => 'mediaContainer', + 'style' => 'position:relative;display:block;width:'. $width + ), + Html::rawElement( $this->getTagName(), $this->getMediaAttr( $sizeOverride, $autoPlay ), + // The set of media sources: + self::htmlTagSet( 'source', $mediaSources ) . + + // Timed text: + self::htmlTagSet( 'track', + $this->file ? $this->getTextHandler()->getTracks() : null ) . + + // Fallback text displayed for browsers without js and without video tag support: + /// XXX note we may want to replace this with an image and download link play button + wfMessage( 'timedmedia-no-player-js', $firstSource['src'] )->text() + ) + ); + return $s; + } + + /** + * Get poster. + * @param $width Integer width of poster. Should not equal $this->width. + * @throws Exception If $width is same as $this->width. + * @return String|bool url for poster or false + */ + function getPoster ( $width ) { + if ( intval( $width ) === intval( $this->width ) ) { + // Prevent potential loop + throw new Exception( "Asked for poster in current size. Potential loop." ); + } + $params = array( "width" => intval( $width ) ); + $mto = $this->file->transform( $params ); + if ( $mto ) { + return $mto->getUrl(); + } else { + return false; + } + } + + /** + * Get the media attributes + * @param $sizeOverride Array|bool of width and height + * @return array + */ + function getMediaAttr( $sizeOverride = false, $autoPlay = false ){ + global $wgVideoPlayerSkin ; + // Normalize values + $length = floatval( $this->length ); + $offset = floatval( $this->offset ); + + $width = $sizeOverride ? $sizeOverride[0] : $this->getPlayerWidth(); + $height = $sizeOverride ? $sizeOverride[1]: $this->getPlayerHeight(); + + // The poster url: + $posterUrl = $this->getUrl( $sizeOverride ); + + if( $this->fillwindow ){ + $width = '100%'; + $height = '100%'; + } else{ + $width .= 'px'; + $height .= 'px'; + } + + $mediaAttr = array( + 'id' => self::PLAYER_ID_PREFIX . TimedMediaTransformOutput::$serial++, + 'style' => "width:{$width}", + // Get the correct size: + 'poster' => $posterUrl, + + // Note we set controls to true ( for no-js players ) when mwEmbed rewrites the interface + // it updates the controls attribute of the embed video + 'controls'=> 'true', + // Since we will reload the item with javascript, + // tell browser to not load the video before + 'preload'=>'none', + ); + + if ( $this->isVideo ) { + $mediaAttr['style'] .= ";height:{$height}"; + } + + if( $autoPlay === true ){ + $mediaAttr['autoplay'] = 'true'; + } + + // MediaWiki uses the kSkin class + $mediaAttr['class'] = 'kskin'; + + if ( $this->file ) { + // Custom data-attributes + $mediaAttr += array( + 'data-durationhint' => $length, + 'data-startoffset' => $offset, + 'data-mwtitle' => $this->file->getTitle()->getDBkey() + ); + + // Add api provider: + if( $this->file->isLocal() ){ + $apiProviderName = 'local'; + } else { + // Set the api provider name to "wikimediacommons" for shared ( instant commons convention ) + // (provider names should have identified the provider instead of the provider type "shared") + $apiProviderName = $this->file->getRepoName(); + if( $apiProviderName == 'shared' ) { + $apiProviderName = 'wikimediacommons'; + } + } + // XXX Note: will probably migrate mwprovider to an escaped api url. + $mediaAttr[ 'data-mwprovider' ] = $apiProviderName; + } else { + if ( $length ) { + $mediaAttr[ 'data-durationhint' ] = $length; + } + if ( $offset ) { + $mediaAttr[ 'data-startoffset' ] = $offset; + } + } + if ( $this->disablecontrols ) { + $mediaAttr[ 'data-disablecontrols' ] = $this->disablecontrols; + } + return $mediaAttr; + } + + /** + * @return null + */ + function getMediaSources(){ + if( !$this->sources ){ + // Generate transcode jobs ( and get sources that are already transcoded) + // At a minimum this should return the source video file. + $this->sources = WebVideoTranscode::getSources( $this->file ); + // Check if we have "start or end" times and append the temporal url fragment hash + foreach( $this->sources as &$source ){ + $source['src'].= $this->getTemporalUrlHash(); + } + } + return $this->sources; + } + + function getTemporalUrlHash(){ + if( $this->hashTime ){ + return $this->hashTime; + } + $hash =''; + if( $this->start ){ + $startSec = TimedMediaHandler::parseTimeString( $this->start ); + if( $startSec !== false ){ + $hash.= '#t=' . TimedMediaHandler::seconds2npt( $startSec ); + } + } + if( $this->end ){ + if( $hash == '' ){ + $hash .= '#t=0'; + } + $endSec = TimedMediaHandler::parseTimeString( $this->end ); + if( $endSec !== false ){ + $hash.= ',' . TimedMediaHandler::seconds2npt( $endSec ); + } + } + $this->hashTime = $hash; + return $this->hashTime; + } +} diff --git a/extensions/TimedMediaHandler/TimedTextPage.php b/extensions/TimedMediaHandler/TimedTextPage.php new file mode 100644 index 00000000..361c2eb9 --- /dev/null +++ b/extensions/TimedMediaHandler/TimedTextPage.php @@ -0,0 +1,187 @@ +getContext()->getRequest(); + $out = $this->getContext()->getOutput(); + $user = $this->getContext()->getUser(); + + $diff = $request->getVal( 'diff' ); + + if ( $this->getTitle()->getNamespace() != NS_TIMEDTEXT || isset( $diff ) ) { + parent::view(); + return; + } + $this->renderOutput( $out ); + } + + /** + * Render TimedText to given output + * @param $out OutputPage + */ + public function renderOutput( $out ){ + // parse page title: + $titleParts = explode( '.', $this->getTitle()->getDBkey() ); + $timedTextExtension = array_pop( $titleParts ); + $languageKey = array_pop( $titleParts ); + + $oldid = $this->getOldID(); + # Are we looking at an old revision + if ( $oldid && $this->mRevision ) { + $this->fetchContentObject(); + $out->setRevisionId( $this->getRevIdFetched() ); + $this->setOldSubtitle( $oldid ); + + if ( !$this->showDeletedRevisionHeader() ) { + wfDebug( __METHOD__ . ": cannot view deleted revision\n" ); + return; + } + } + + // Check for File name without text extension: + // i.e TimedText:myfile.ogg + $fileTitle = Title::newFromText( $this->getTitle()->getDBkey(), NS_FILE ); + $file = wfFindFile( $fileTitle ); + // Check for a valid srt page, present redirect form for the full title match: + if( !in_array( $timedTextExtension, self::$knownTimedTextExtensions ) && $file && $file->exists() ){ + if( $file->isLocal() ){ + $this->doRedirectToPageForm( $fileTitle ); + } else { + $this->doLinkToRemote( $file ); + } + return; + } + + // Check for File name with text extension ( from remaning parts of title ) + // i.e TimedText:myfile.ogg.en.srt + + $videoTitle = Title::newFromText( implode('.', $titleParts ), NS_FILE ); + + // Check for remote file + $basefile = wfFindFile( $videoTitle ); + if ( !$basefile ) { + $out->addHTML( wfMessage( 'timedmedia-subtitle-no-video' )->escaped() ); + return; + } + + if( !$basefile->isLocal() ){ + $this->doLinkToRemote( $basefile ); + return; + } + + // Look up the language name: + $language = $out->getLanguage()->getCode(); + $languages = Language::fetchLanguageNames( $language, 'all' ); + if( isset( $languages[ $languageKey ] ) ) { + $languageName = $languages[ $languageKey ]; + } else { + $languageName = $languageKey; + } + + // Set title + $message = $this->exists() ? + 'mwe-timedtext-language-subtitles-for-clip' : + 'mwe-timedtext-language-no-subtitles-for-clip'; + $out->setPageTitle( wfMessage($message, $languageName, $videoTitle ) ); + + // Get the video with with a max of 600 pixel page + $out->addHTML( + xml::tags( 'table', array( 'style'=> 'border:none' ), + xml::tags( 'tr', null, + xml::tags( 'td', array( 'valign' => 'top', 'width' => self::$videoWidth ), + $this->getVideoHTML( $videoTitle ) + ) . + xml::tags( 'td', array( 'valign' => 'top' ) , $this->getTimedTextHTML( $languageName ) ) + ) + ) + ); + } + + /** + * Timed text or file is hosted on remote repo, Add a short description and link to foring repo + * @param $file File the base file + */ + function doLinkToRemote( $file ){ + $output = $this->getContext()->getOutput(); + $output->setPageTitle( wfMessage( 'timedmedia-subtitle-remote', + $file->getRepo()->getDisplayName() ) ); + $output->addHTML( wfMessage( 'timedmedia-subtitle-remote-link', + $file->getDescriptionUrl(), $file->getRepo()->getDisplayName() ) ); + } + + function doRedirectToPageForm(){ + $lang = $this->getContext()->getLanguage(); + $out = $this->getContext()->getOutput(); + + // Set the page title: + $out->setPageTitle( wfMessage( 'timedmedia-subtitle-new' ) ); + + // Look up the language name: + $language = $out->getLanguage()->getCode(); + $attrs = array( 'id' => 'timedmedia-tt-input' ); + $langSelect = Xml::languageSelector( $language, false, null, $attrs, null ); + + $out->addHTML( + Xml::tags('div', array( 'style' => 'text-align:center' ), + Xml::tags( 'div', null, + wfMessage( 'timedmedia-subtitle-new-desc', $lang->getCode() )->parse() + ) . + $langSelect[1] . + Xml::tags( 'button', + array( 'id' => 'timedmedia-tt-go' ), + wfMessage( 'timedmedia-subtitle-new-go' )->escaped() + ) + ) + ); + $out->addModules( 'ext.tmh.TimedTextSelector' ); + } + + /** + * Gets the video HTML ( with the current language set as default ) + * @param $videoTitle string + * @return String + */ + private function getVideoHTML( $videoTitle ){ + // Get the video embed: + $file = wfFindFile( $videoTitle ); + if( !$file ){ + return wfMessage( 'timedmedia-subtitle-no-video' )->escaped(); + } else { + $videoTransform= $file->transform( + array( + 'width' => self::$videoWidth + ) + ); + return $videoTransform->toHTML(); + } + } + + /** + * Gets an HTML representation of the Timed Text + * + * @param $languageName string + * @return Message|string + */ + private function getTimedTextHTML( $languageName ){ + if( !$this->exists() ){ + return wfMessage( 'timedmedia-subtitle-no-subtitles', $languageName ); + } + return Xml::element( + 'pre', + array( 'style' => 'margin-top: 0px;' ), + $this->getContent(), + false + ); + } +} diff --git a/extensions/TimedMediaHandler/TranscodeStatusTable.php b/extensions/TimedMediaHandler/TranscodeStatusTable.php new file mode 100644 index 00000000..2220fc72 --- /dev/null +++ b/extensions/TimedMediaHandler/TranscodeStatusTable.php @@ -0,0 +1,236 @@ +addModules( array( 'ext.tmh.transcodetable' ) ); + + $o = '

' . wfMessage( 'timedmedia-status-header' )->escaped() . '

'; + // Give the user a purge page link + $o.= Linker::link( + $file->getTitle(), + wfMessage('timedmedia-update-status')->escaped(), + array(), + array( 'action'=> 'purge' ) + ); + + $o.= Xml::openElement( 'table', array( 'class' => 'wikitable mw-filepage-transcodestatus' ) ) . "\n" + . '' + . '' . wfMessage( 'timedmedia-transcodeinfo' )->escaped() . '' + . '' . wfMessage( 'timedmedia-transcodebitrate' )->escaped() . '' + . '' . wfMessage( 'timedmedia-direct-link' )->escaped() . ''; + + if( $wgUser->isAllowed( 'transcode-reset' ) ) { + $o.= '' . wfMessage( 'timedmedia-actions' )->escaped() . ''; + } + + $o.= '' . wfMessage( 'timedmedia-status' )->escaped() . ''; + $o.= '' . wfMessage( 'timedmedia-transcodeduration' )->escaped() . ''; + $o.= "\n"; + + $o.= self::getTranscodeRows( $file ); + + $o.= Xml::closeElement( 'table' ); + return $o; + } + + /** + * Get the video or audio codec for the defined transcode, + * for grouping/sorting purposes. + */ + public static function codecFromTranscodeKey( $key ) { + if ( isset( WebVideoTranscode::$derivativeSettings[$key] ) ) { + $settings = WebVideoTranscode::$derivativeSettings[$key]; + if ( isset( $settings['videoCodec'] ) ) { + return $settings['videoCodec']; + } else if ( isset( $settings['audioCodec'] ) ) { + return $settings['audioCodec']; + } else { + // this this shouldn't happen... + // fall through + } + } else { + // derivative type no longer defined or invalid def? + // fall through + } + return $key; + } + + /** + * @param $file File + * @return string + */ + public static function getTranscodeRows( $file ){ + global $wgUser, $wgLang; + $o=''; + + $transcodeRows = WebVideoTranscode::getTranscodeState( $file ); + + uksort( $transcodeRows, function( $a, $b ) { + $formatOrder = array( 'vp9', 'vp8', 'h264', 'theora', 'opus', 'vorbis', 'aac' ); + + $aFormat = self::codecFromTranscodeKey( $a ); + $bFormat = self::codecFromTranscodeKey( $b ); + $aIndex = array_search( $aFormat, $formatOrder ); + $bIndex = array_search( $bFormat, $formatOrder ); + + if ( $aIndex === false && $bIndex === false ) { + return -strnatcmp( $a, $b ); + } else if ( $aIndex === false ) { + return 1; + } else if ( $bIndex === false ) { + return -1; + } else if ( $aIndex === $bIndex ) { + return -strnatcmp( $a, $b ); + } else { + return ( $aIndex - $bIndex ); + } + } ); + + foreach( $transcodeRows as $transcodeKey => $state ){ + $o.=''; + // Encode info: + $o.='' . wfMessage('timedmedia-derivative-' . $transcodeKey )->escaped() . ''; + $o.='' . self::getTranscodeBitrate( $file, $state ) . ''; + + // Download file + $o.=''; + $o.= ( !is_null( $state['time_success'] ) ) ? + '
' . + wfMessage('timedmedia-download' )->escaped(). '
' : + wfMessage('timedmedia-not-ready' )->escaped(); + $o.=''; + + // Check if we should include actions: + if( $wgUser->isAllowed( 'transcode-reset' ) ){ + // include reset transcode action buttons + $o.='' . wfMessage('timedmedia-reset')->escaped() . + ''; + } + + // Status: + $o.='' . self::getStatusMsg( $file, $state ) . ''; + $o.='' . self::getTranscodeDuration( $file, $state ) . ''; + + $o.=''; + } + return $o; + } + + /** + * @param $file File + * @param $transcodeKey string + * @return string + */ + public static function getSourceUrl( $file, $transcodeKey ){ + return WebVideoTranscode::getTranscodedUrlForFile( $file, $transcodeKey ); + } + + /** + * @param $file File + * @param $state + * @return string + */ + public static function getTranscodeDuration( $file, $state ) { + global $wgLang; + if( !is_null( $state['time_success'] ) ) { + $startTime = wfTimestamp( TS_UNIX, $state['time_startwork'] ); + $endTime = wfTimestamp( TS_UNIX, $state['time_success'] ); + $delta = $endTime - $startTime; + $duration = $wgLang->formatTimePeriod( $delta ); + return $duration; + } else { + return ''; + } + } + + /** + * @param $file File + * @param $state + * @return string + */ + public static function getTranscodeBitrate( $file, $state ) { + global $wgLang; + if( !is_null( $state['time_success'] ) ) { + return $wgLang->formatBitrate( $state['final_bitrate'] ); + } else { + return ''; + } + } + + /** + * @param $file File + * @param $state + * @return string + */ + public static function getStatusMsg( $file, $state ){ + global $wgContLang; + // Check for success: + if( !is_null( $state['time_success'] ) ) { + return wfMessage( 'timedmedia-completed-on', + $wgContLang->timeAndDate( $state[ 'time_success' ] ) )->escaped(); + } + // Check for error: + if( !is_null( $state['time_error'] ) ){ + $attribs = array(); + if( !is_null( $state['error'] ) ){ + $attribs = array( + 'class' => 'mw-tmh-pseudo-error-link', + 'data-error' => $state['error'], + ); + } + + return Html::rawElement( 'span', $attribs, + wfMessage( 'timedmedia-error-on', + $wgContLang->timeAndDate( $state['time_error'] ) )->escaped() + ); + } + + //$db = wfGetDB( DB_SLAVE ); + // Check for started encoding + if( !is_null( $state['time_startwork'] ) ){ + $timePassed = time() - wfTimestamp ( TS_UNIX, $state['time_startwork'] ); + // Get the rough estimate of time done: ( this is not very costly considering everything else + // that happens in an action=purge video page request ) + /*$filePath = WebVideoTranscode::getTargetEncodePath( $file, $state['key'] ); + if( is_file( $filePath ) ){ + $targetSize = WebVideoTranscode::getProjectedFileSize( $file, $state['key'] ); + if( $targetSize === false ){ + $doneMsg = wfMessage( 'timedmedia-unknown-target-size', $wgLang->formatSize( filesize( $filePath ) ) )->escaped(); + } else { + $doneMsg = wfMessage('timedmedia-percent-done', round( filesize( $filePath ) / $targetSize, 2 ) )->escaped(); + } + } */ + // Predicting percent done is not working well right now ( disabled for now ) + $doneMsg = ''; + return wfMessage( + 'timedmedia-started-transcode', + TimedMediaHandler::getTimePassedMsg( $timePassed ), $doneMsg + )->escaped(); + } + // Check for job added ( but not started encoding ) + if( !is_null( $state['time_addjob'] ) ){ + $timePassed = time() - wfTimestamp ( TS_UNIX, $state['time_addjob'] ); + return wfMessage( + 'timedmedia-in-job-queue', + TimedMediaHandler::getTimePassedMsg( $timePassed ) + )->escaped(); + } + // Return unknown status error: + return wfMessage('timedmedia-status-unknown')->escaped(); + } +} diff --git a/extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscode.php b/extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscode.php new file mode 100644 index 00000000..e4d02556 --- /dev/null +++ b/extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscode.php @@ -0,0 +1,1192 @@ + 'Ogg 200'; + */ + + // Ogg Profiles + const ENC_OGV_160P = '160p.ogv'; + const ENC_OGV_240P = '240p.ogv'; + const ENC_OGV_360P = '360p.ogv'; + const ENC_OGV_480P = '480p.ogv'; + const ENC_OGV_720P = '720p.ogv'; + const ENC_OGV_1080P = '1080p.ogv'; + + // WebM VP8/Vorbis profiles: + const ENC_WEBM_160P = '160p.webm'; + const ENC_WEBM_360P = '360p.webm'; + const ENC_WEBM_480P = '480p.webm'; + const ENC_WEBM_720P = '720p.webm'; + const ENC_WEBM_1080P = '1080p.webm'; + const ENC_WEBM_2160P = '2160p.webm'; + + // WebM VP9/Opus profiles: + const ENC_VP9_360P = '360p.vp9.webm'; + const ENC_VP9_480P = '480p.vp9.webm'; + const ENC_VP9_720P = '720p.vp9.webm'; + const ENC_VP9_1080P = '1080p.vp9.webm'; + const ENC_VP9_2160P = '2160p.vp9.webm'; + + // mp4 profiles: + const ENC_H264_320P = '320p.mp4'; + const ENC_H264_480P = '480p.mp4'; + const ENC_H264_720P = '720p.mp4'; + const ENC_H264_1080P = '1080p.mp4'; + const ENC_H264_2160P = '2160p.mp4'; + + const ENC_OGG_VORBIS = 'ogg'; + const ENC_OGG_OPUS = 'opus'; + const ENC_MP3 = 'mp3'; + const ENC_AAC = 'm4a'; + + // Static cache of transcode state per instantiation + public static $transcodeState = array() ; + + /** + * Encoding parameters are set via firefogg encode api + * + * For clarity and compatibility with passing down + * client side encode settings at point of upload + * + * http://firefogg.org/dev/index.html + */ + public static $derivativeSettings = array( + WebVideoTranscode::ENC_OGV_160P => + array( + 'maxSize' => '288x160', + 'videoBitrate' => '160', + 'framerate' => '15', + 'audioQuality' => '-1', + 'samplerate' => '44100', + 'channels' => '2', + 'noUpscaling' => 'true', + //'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora + 'optimize' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'theora', + 'type' => 'video/ogg; codecs="theora, vorbis"', + ), + WebVideoTranscode::ENC_OGV_240P => + array( + 'maxSize' => '426x240', + 'videoBitrate' => '512', + 'audioQuality' => '0', + 'samplerate' => '44100', + 'channels' => '2', + 'noUpscaling' => 'true', + //'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora + 'optimize' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'theora', + 'type' => 'video/ogg; codecs="theora, vorbis"', + ), + WebVideoTranscode::ENC_OGV_360P => + array( + 'maxSize' => '640x360', + 'videoBitrate' => '1024', + 'audioQuality' => '1', + 'samplerate' => '44100', + 'channels' => '2', + 'noUpscaling' => 'true', + //'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora + 'optimize' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'theora', + 'type' => 'video/ogg; codecs="theora, vorbis"', + ), + WebVideoTranscode::ENC_OGV_480P => + array( + 'maxSize' => '854x480', + 'videoBitrate' => '2048', + 'audioQuality' => '2', + 'samplerate' => '44100', + 'channels' => '2', + 'noUpscaling' => 'true', + //'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora + 'optimize' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'theora', + 'type' => 'video/ogg; codecs="theora, vorbis"', + ), + + WebVideoTranscode::ENC_OGV_720P => + array( + 'maxSize' => '1280x720', + 'videoQuality' => 6, + 'audioQuality' => 3, + 'noUpscaling' => 'true', + //'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora + 'optimize' => 'true', + 'keyframeInterval' => '128', + 'videoCodec' => 'theora', + 'type' => 'video/ogg; codecs="theora, vorbis"', + ), + + WebVideoTranscode::ENC_OGV_1080P => + array( + 'maxSize' => '1920x1080', + 'videoQuality' => 6, + 'audioQuality' => 3, + 'noUpscaling' => 'true', + //'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora + 'optimize' => 'true', + 'keyframeInterval' => '128', + 'videoCodec' => 'theora', + 'type' => 'video/ogg; codecs="theora, vorbis"', + ), + + // WebM transcode: + WebVideoTranscode::ENC_WEBM_160P => + array( + 'maxSize' => '288x160', + 'videoBitrate' => '256', + 'audioQuality' => '-1', + 'samplerate' => '44100', + 'channels' => '2', + 'noUpscaling' => 'true', + 'twopass' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'vp8', + 'type' => 'video/webm; codecs="vp8, vorbis"', + ), + WebVideoTranscode::ENC_WEBM_360P => + array( + 'maxSize' => '640x360', + 'videoBitrate' => '512', + 'audioQuality' => '1', + 'samplerate' => '44100', + 'noUpscaling' => 'true', + 'twopass' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'vp8', + 'type' => 'video/webm; codecs="vp8, vorbis"', + ), + WebVideoTranscode::ENC_WEBM_480P => + array( + 'maxSize' => '854x480', + 'videoBitrate' => '1024', + 'audioQuality' => '2', + 'samplerate' => '44100', + 'noUpscaling' => 'true', + 'twopass' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'vp8', + 'type' => 'video/webm; codecs="vp8, vorbis"', + ), + WebVideoTranscode::ENC_WEBM_720P => + array( + 'maxSize' => '1280x720', + 'videoQuality' => 7, + 'audioQuality' => 3, + 'noUpscaling' => 'true', + 'videoCodec' => 'vp8', + 'type' => 'video/webm; codecs="vp8, vorbis"', + ), + WebVideoTranscode::ENC_WEBM_1080P => + array( + 'maxSize' => '1920x1080', + 'videoQuality' => 7, + 'audioQuality' => 3, + 'noUpscaling' => 'true', + 'videoCodec' => 'vp8', + 'type' => 'video/webm; codecs="vp8, vorbis"', + ), + WebVideoTranscode::ENC_WEBM_2160P => + array( + 'maxSize' => '4096x2160', + 'videoQuality' => 7, + 'audioQuality' => 3, + 'noUpscaling' => 'true', + 'videoCodec' => 'vp8', + 'type' => 'video/webm; codecs="vp8, vorbis"', + ), + + // WebM VP9 transcode: + WebVideoTranscode::ENC_VP9_360P => + array( + 'maxSize' => '640x360', + 'videoBitrate' => '256', + 'samplerate' => '48000', + 'noUpscaling' => 'true', + 'twopass' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'vp9', + 'audioCodec' => 'opus', + 'type' => 'video/webm; codecs="vp9, opus"', + ), + WebVideoTranscode::ENC_VP9_480P => + array( + 'maxSize' => '854x480', + 'videoBitrate' => '512', + 'samplerate' => '48000', + 'noUpscaling' => 'true', + 'twopass' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'vp9', + 'audioCodec' => 'opus', + 'type' => 'video/webm; codecs="vp9, opus"', + ), + WebVideoTranscode::ENC_VP9_720P => + array( + 'maxSize' => '1280x720', + 'videoBitrate' => '1024', + 'samplerate' => '48000', + 'noUpscaling' => 'true', + 'twopass' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'vp9', + 'audioCodec' => 'opus', + 'tileColumns' => '2', + 'type' => 'video/webm; codecs="vp9, opus"', + ), + WebVideoTranscode::ENC_VP9_1080P => + array( + 'maxSize' => '1920x1080', + 'videoBitrate' => '2048', + 'samplerate' => '48000', + 'noUpscaling' => 'true', + 'twopass' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'vp9', + 'audioCodec' => 'opus', + 'tileColumns' => '4', + 'type' => 'video/webm; codecs="vp9, opus"', + ), + WebVideoTranscode::ENC_VP9_2160P => + array( + 'maxSize' => '4096x2160', + 'videoBitrate' => '8192', + 'samplerate' => '48000', + 'noUpscaling' => 'true', + 'twopass' => 'true', + 'keyframeInterval' => '128', + 'bufDelay' => '256', + 'videoCodec' => 'vp9', + 'audioCodec' => 'opus', + 'tileColumns' => '4', + 'type' => 'video/webm; codecs="vp9, opus"', + ), + + // Losly defined per PCF guide to mp4 profiles: + // https://develop.participatoryculture.org/index.php/ConversionMatrix + // and apple HLS profile guide: + // https://developer.apple.com/library/ios/#documentation/networkinginternet/conceptual/streamingmediaguide/UsingHTTPLiveStreaming/UsingHTTPLiveStreaming.html#//apple_ref/doc/uid/TP40008332-CH102-DontLinkElementID_24 + + WebVideoTranscode::ENC_H264_320P => + array( + 'maxSize' => '480x320', + 'videoCodec' => 'h264', + 'preset' => 'ipod320', + 'videoBitrate' => '400k', + 'audioCodec' => 'aac', + 'channels' => '2', + 'audioBitrate' => '40k', + 'type' => 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"', + ), + + WebVideoTranscode::ENC_H264_480P => + array( + 'maxSize' => '640x480', + 'videoCodec' => 'h264', + 'preset' => 'ipod640', + 'videoBitrate' => '1200k', + 'audioCodec' => 'aac', + 'channels' => '2', + 'audioBitrate' => '64k', + 'type' => 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"', + ), + + WebVideoTranscode::ENC_H264_720P => + array( + 'maxSize' => '1280x720', + 'videoCodec' => 'h264', + 'preset' => '720p', + 'videoBitrate' => '2500k', + 'audioCodec' => 'aac', + 'channels' => '2', + 'audioBitrate' => '128k', + 'type' => 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"', + ), + + WebVideoTranscode::ENC_H264_1080P => + array( + 'maxSize' => '1920x1080', + 'videoCodec' => 'h264', + 'videoBitrate' => '5000k', + 'audioCodec' => 'aac', + 'channels' => '2', + 'audioBitrate' => '128k', + 'type' => 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"', + ), + WebVideoTranscode::ENC_H264_2160P => + array( + 'maxSize' => '4096x2160', + 'videoCodec' => 'h264', + 'videoBitrate' => '16384k', + 'audioCodec' => 'aac', + 'channels' => '2', + 'audioBitrate' => '128k', + 'type' => 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"', + ), + + //Audio profiles + WebVideoTranscode::ENC_OGG_VORBIS => + array( + 'audioCodec' => 'vorbis', + 'audioQuality' => '1', + 'samplerate' => '44100', + 'channels' => '2', + 'noUpscaling' => 'true', + 'novideo' => 'true', + 'type' => 'audio/ogg; codecs="vorbis"', + ), + WebVideoTranscode::ENC_OGG_OPUS => + array( + 'audioCodec' => 'opus', + 'audioQuality' => '1', + 'samplerate' => '44100', + 'channels' => '2', + 'noUpscaling' => 'true', + 'novideo' => 'true', + 'type' => 'audio/ogg; codecs="opus"', + ), + WebVideoTranscode::ENC_MP3 => + array( + 'audioCodec' => 'mp3', + 'audioQuality' => '1', + 'samplerate' => '44100', + 'channels' => '2', + 'noUpscaling' => 'true', + 'novideo' => 'true', + 'type' => 'audio/mpeg', + ), + WebVideoTranscode::ENC_AAC => + array( + 'audioCodec' => 'aac', + 'audioQuality' => '1', + 'samplerate' => '44100', + 'channels' => '2', + 'noUpscaling' => 'true', + 'novideo' => 'true', + 'type' => 'audio/mp4; codecs="mp4a.40.5"', + ), + ); + + /** + * @param $file File + * @param $transcodeKey string + * @return string + */ + static public function getDerivativeFilePath( $file, $transcodeKey ) { + return $file->getTranscodedPath( self::getTranscodeFileBaseName( $file, $transcodeKey ) ); + } + + /** + * Get the name to use as the base name for the transcode. + * + * Swift has problems where the url-encoded version of + * the path (ie 'filename.ogv/filename.ogv.720p.webm' ) + * is greater that > 1024 bytes, so shorten in that case. + * + * Future versions might respect FileRepo::$abbrvThreshold. + * + * @param File $file + * @param String $suffix Optional suffix (e.g. transcode key). + * @return String File name, or the string transcode. + */ + static public function getTranscodeFileBaseName( $file, $suffix = '' ) { + $name = $file->getName(); + if ( strlen( urlencode( $name ) ) * 2 + 12 > 1024 ) { + return 'transcode' . '.' . $suffix; + } else { + return $name . '.' . $suffix; + } + } + + /** + * Get url for a transcode. + * + * @param $file File + * @param $suffix string Transcode key + * @return string + */ + static public function getTranscodedUrlForFile( $file, $suffix = '' ) { + return $file->getTranscodedUrl( self::getTranscodeFileBaseName( $file, $suffix ) ); + } + + /** + * Get temp file at target path for video encode + * + * @param $file File + * @param $transcodeKey String + * + * @return TempFSFile at target encode path + */ + static public function getTargetEncodeFile( &$file, $transcodeKey ){ + $filePath = self::getDerivativeFilePath( $file, $transcodeKey ); + $ext = strtolower( pathinfo( "$filePath", PATHINFO_EXTENSION ) ); + + // Create a temp FS file with the same extension + $tmpFile = TempFSFile::factory( 'transcode_' . $transcodeKey, $ext); + if ( !$tmpFile ) { + return False; + } + return $tmpFile; + } + + /** + * Get the max size of the web stream ( constant bitrate ) + * @return int + */ + static public function getMaxSizeWebStream(){ + global $wgEnabledTranscodeSet; + $maxSize = 0; + foreach( $wgEnabledTranscodeSet as $transcodeKey ){ + if( isset( self::$derivativeSettings[$transcodeKey]['videoBitrate'] ) ){ + $currentSize = self::$derivativeSettings[$transcodeKey]['maxSize']; + if( $currentSize > $maxSize ){ + $maxSize = $currentSize; + } + } + } + return $maxSize; + } + + /** + * Give a rough estimate on file size + * Note this is not always accurate.. especially with variable bitrate codecs ;) + * @param $file File + * @param $transcodeKey string + * @return number + */ + static public function getProjectedFileSize( $file, $transcodeKey ){ + $settings = self::$derivativeSettings[$transcodeKey]; + if( $settings[ 'videoBitrate' ] && $settings['audioBitrate'] ){ + return $file->getLength() * 8 * ( + self::$derivativeSettings[$transcodeKey]['videoBitrate'] + + + self::$derivativeSettings[$transcodeKey]['audioBitrate'] + ); + } + // Else just return the size of the source video ( we have no idea how large the actual derivative size will be ) + return $file->getLength() * $file->getHandler()->getBitrate( $file ) * 8; + } + + /** + * Static function to get the set of video assets + * Checks if the file is local or remote and grabs respective sources + * @param $file File + * @param $options array + * @return array|mixed + */ + static public function getSources( &$file , $options = array() ){ + if( $file->isLocal() || $file->repo instanceof ForeignDBViaLBRepo ){ + return self::getLocalSources( $file , $options ); + } else { + return self::getRemoteSources( $file , $options ); + } + } + + /** + * Grabs sources from the remote repo via ApiQueryVideoInfo.php entry point. + * + * TODO: This method could use some rethinking. See comments on PS1 of + * + * + * Because this works with commons regardless of whether TimedMediaHandler is installed or not + * @param $file File + * @param $options array + * @return array|mixed + */ + static public function getRemoteSources(&$file , $options = array() ){ + global $wgMemc; + // Setup source attribute options + $dataPrefix = in_array( 'nodata', $options )? '': 'data-'; + + // Use descriptionCacheExpiry as our expire for timed text tracks info + if ( $file->repo->descriptionCacheExpiry > 0 ) { + wfDebug("Attempting to get sources from cache..."); + $key = $file->repo->getLocalCacheKey( 'WebVideoSources', 'url', $file->getName() ); + $sources = $wgMemc->get($key); + if ( $sources ) { + wfDebug("Success found sources in local cache\n"); + return $sources; + } + wfDebug("source cache miss\n"); + } + + wfDebug("Get Video sources from remote api for " . $file->getName() . "\n"); + $query = array( + 'action' => 'query', + 'prop' => 'videoinfo', + 'viprop' => 'derivatives', + 'titles' => MWNamespace::getCanonicalName( NS_FILE ) .':'. $file->getTitle()->mTextform + ); + + $data = $file->repo->fetchImageQuery( $query ); + + if( isset( $data['warnings'] ) && isset( $data['warnings']['query'] ) + && $data['warnings']['query']['*'] == "Unrecognized value for parameter 'prop': videoinfo" ) + { + // Commons does not yet have TimedMediaHandler. + // Use the normal file repo system single source: + return array( self::getPrimarySourceAttributes( $file, array( $dataPrefix ) ) ); + } + $sources = array(); + // Generate the source list from the data response: + if( isset( $data['query'] ) && $data['query']['pages'] ){ + $vidResult = array_shift( $data['query']['pages'] ); + if( isset( $vidResult['videoinfo'] ) ) { + $derResult = array_shift( $vidResult['videoinfo'] ); + $derivatives = $derResult['derivatives']; + foreach( $derivatives as $derivativeSource ){ + $sources[] = $derivativeSource; + } + } + } + + // Update the cache: + if ( $sources && $file->repo->descriptionCacheExpiry > 0 ) { + $wgMemc->set( $key, $sources, $file->repo->descriptionCacheExpiry ); + } + + return $sources; + + } + + /** + * Based on the $wgEnabledTranscodeSet set of enabled derivatives we + * return sources that are ready. + * + * This will not automatically update or queue anything! + * + * @param $file File object + * @param $options array Options, a set of options: + * 'nodata' Strips the data- attribute, useful when your output is not html + * @return array an associative array of sources suitable for tag output + */ + static public function getLocalSources( &$file , $options=array() ){ + global $wgEnabledTranscodeSet, $wgEnabledAudioTranscodeSet, $wgEnableTranscode; + $sources = array(); + + // Add the original file: + $sources[] = self::getPrimarySourceAttributes( $file, $options ); + + // If $wgEnableTranscode is false don't look for or add other local sources: + if( $wgEnableTranscode === false && + !($file->repo instanceof ForeignDBViaLBRepo) ){ + return $sources; + } + + // If an "oldFile" don't look for other sources: + if( $file->isOld() ){ + return $sources; + } + + // Now Check for derivatives + if( $file->getHandler()->isAudio( $file ) ){ + $transcodeSet = $wgEnabledAudioTranscodeSet; + } else { + $transcodeSet = $wgEnabledTranscodeSet; + } + foreach( $transcodeSet as $transcodeKey ){ + if ( self::isTranscodeEnabled( $file, $transcodeKey ) ) { + // Try and add the source + self::addSourceIfReady( $file, $sources, $transcodeKey, $options ); + } + } + + return $sources; + } + + /** + * Get the transcode state for a given filename and transcodeKey + * + * @param $fileName string + * @param $transcodeKey string + * @return bool + */ + public static function isTranscodeReady( $file, $transcodeKey ){ + + // Check if we need to populate the transcodeState cache: + $transcodeState = self::getTranscodeState( $file ); + + // If no state is found the cache for this file is false: + if( !isset( $transcodeState[ $transcodeKey ] ) ) { + return false; + } + // Else return boolean ready state ( if not null, then ready ): + return !is_null( $transcodeState[ $transcodeKey ]['time_success'] ); + } + + /** + * Clear the transcode state cache: + * @param String $fileName Optional fileName to clear transcode cache for + */ + public static function clearTranscodeCache( $fileName = null){ + if( $fileName ){ + unset( self::$transcodeState[ $fileName ] ); + } else { + self::$transcodeState = array(); + } + } + + /** + * Populates the transcode table with the current DB state of transcodes + * if transcodes are not found in the database their state is set to "false" + * + * @param {Object} File object + */ + public static function getTranscodeState( $file, $db = false ){ + global $wgTranscodeBackgroundTimeLimit; + $fileName = $file->getName(); + if( ! isset( self::$transcodeState[$fileName] ) ){ + if ( $db === false ) { + $db = $file->repo->getSlaveDB(); + } + // initialize the transcode state array + self::$transcodeState[ $fileName ] = array(); + $res = $db->select( 'transcode', + '*', + array( 'transcode_image_name' => $fileName ), + __METHOD__, + array( 'LIMIT' => 100 ) + ); + $overTimeout = array(); + $over = $db->timestamp(time() - (2 * $wgTranscodeBackgroundTimeLimit)); + // Populate the per transcode state cache + foreach ( $res as $row ) { + // strip the out the "transcode_" from keys + $trascodeState = array(); + foreach( $row as $k => $v ){ + $trascodeState[ str_replace( 'transcode_', '', $k ) ] = $v; + } + self::$transcodeState[ $fileName ][ $row->transcode_key ] = $trascodeState; + if ( $row->transcode_time_startwork != NULL + && $row->transcode_time_startwork < $over + && $row->transcode_time_success == NULL + && $row->transcode_time_error == NULL ) { + $overTimeout[] = $row->transcode_key; + } + } + if ( $overTimeout ) { + $dbw = wfGetDB( DB_MASTER ); + $dbw->update( + 'transcode', + array( + 'transcode_time_error' => $dbw->timestamp(), + 'transcode_error' => 'timeout' + ), + array( + 'transcode_image_name' => $fileName, + 'transcode_key' => $overTimeout + ), + __METHOD__, + array( 'LIMIT' => count( $overTimeout ) ) + ); + } + } + $sorted = self::$transcodeState[ $fileName ]; + uksort( $sorted, 'strnatcmp' ); + return $sorted; + } + + /** + * Remove any transcode files and db states associated with a given $file + * Note that if you want to see them again, you must re-queue them by calling + * startJobQueue() or updateJobQueue(). + * + * also remove the transcode files: + * @param $file File Object + * @param $transcodeKey String Optional transcode key to remove only this key + */ + public static function removeTranscodes( &$file, $transcodeKey = false ){ + + // if transcode key is non-false, non-null: + if( $transcodeKey ){ + // only remove the requested $transcodeKey + $removeKeys = array( $transcodeKey ); + } else { + // Remove any existing files ( regardless of their state ) + $res = $file->repo->getMasterDB()->select( 'transcode', + array( 'transcode_key' ), + array( 'transcode_image_name' => $file->getName() ) + ); + $removeKeys = array(); + foreach( $res as $transcodeRow ){ + $removeKeys[] = $transcodeRow->transcode_key; + } + } + + // Remove files by key: + $urlsToPurge = array(); + foreach ( $removeKeys as $tKey ) { + $urlsToPurge[] = self::getTranscodedUrlForFile( $file, $tKey ); + $filePath = self::getDerivativeFilePath( $file, $tKey ); + if( $file->repo->fileExists( $filePath ) ){ + wfSuppressWarnings(); + $res = $file->repo->quickPurge( $filePath ); + wfRestoreWarnings(); + if( !$res ){ + wfDebug( "Could not delete file $filePath\n" ); + } + } + } + + SquidUpdate::purge( $urlsToPurge ); + + // Build the sql query: + $dbw = wfGetDB( DB_MASTER ); + $deleteWhere = array( 'transcode_image_name' => $file->getName() ); + // Check if we are removing a specific transcode key + if( $transcodeKey !== false ){ + $deleteWhere['transcode_key'] = $transcodeKey; + } + // Remove the db entries + $dbw->delete( 'transcode', $deleteWhere, __METHOD__ ); + + // Purge the cache for pages that include this video: + $titleObj = $file->getTitle(); + self::invalidatePagesWithFile( $titleObj ); + + // Remove from local WebVideoTranscode cache: + self::clearTranscodeCache( $titleObj->getDBkey() ); + } + + /** + * @param $titleObj Title + */ + public static function invalidatePagesWithFile( &$titleObj ){ + wfDebug("WebVideoTranscode:: Invalidate pages that include: " . $titleObj->getDBkey() . "\n" ); + // Purge the main image page: + $titleObj->invalidateCache(); + + // TODO if the video is used in over 500 pages add to 'job queue' + // TODO interwiki invalidation ? + $limit = 500; + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( + array( 'imagelinks', 'page' ), + array( 'page_namespace', 'page_title' ), + array( 'il_to' => $titleObj->getDBkey(), 'il_from = page_id' ), + __METHOD__, + array( 'LIMIT' => $limit + 1 ) + ); + foreach ( $res as $page ) { + $title = Title::makeTitle( $page->page_namespace, $page->page_title ); + $title->invalidateCache(); + } + } + + /** + * Add a source to the sources list if the transcode job is ready + * + * If the source is not found, it will not be used yet... + * Missing transcodes should be added by write tasks, not read tasks! + */ + public static function addSourceIfReady( &$file, &$sources, $transcodeKey, $dataPrefix = '' ){ + // Check if the transcode is ready: + if( self::isTranscodeReady( $file, $transcodeKey ) ){ + $sources[] = self::getDerivativeSourceAttributes( $file, $transcodeKey, $dataPrefix ); + } + } + + /** + * Get the primary "source" asset used for other derivatives + * @param $file File + * @param $options array + * @return array + */ + static public function getPrimarySourceAttributes( $file, $options = array() ){ + global $wgLang; + $src = in_array( 'fullurl', $options)? wfExpandUrl( $file->getUrl() ) : $file->getUrl(); + + $bitrate = $file->getHandler()->getBitrate( $file ); + $metadataType = $file->getHandler()->getMetadataType( $file ); + + // Give grep a chance to find the usages: timedmedia-ogg, timedmedia-webm, + // timedmedia-mp4, timedmedia-flac, timedmedia-wav + if( $file->getHandler()->isAudio( $file ) ){ + $title = wfMessage( 'timedmedia-source-audio-file-desc', + wfMessage( 'timedmedia-' . $metadataType )->text() ) + ->params( $wgLang->formatBitrate( $bitrate ) )->text(); + } else { + $title = wfMessage( 'timedmedia-source-file-desc', + wfMessage( 'timedmedia-' . $metadataType )->text() ) + ->numParams( $file->getWidth(), $file->getHeight() ) + ->params( $wgLang->formatBitrate( $bitrate ) )->text(); + } + + // Give grep a chance to find the usages: timedmedia-ogg, timedmedia-webm, + // timedmedia-mp4, timedmedia-flac, timedmedia-wav + $source = array( + 'src' => $src, + 'type' => $file->getHandler()->getWebType( $file ), + 'title' => $title, + "shorttitle" => wfMessage( + 'timedmedia-source-file', + wfMessage( 'timedmedia-' . $metadataType )->text() + )->text(), + "width" => intval( $file->getWidth() ), + "height" => intval( $file->getHeight() ), + ); + + if( $bitrate ){ + $source["bandwidth"] = round ( $bitrate ); + } + + // For video include framerate: + if( !$file->getHandler()->isAudio( $file ) ){ + $framerate = $file->getHandler()->getFramerate( $file ); + if( $framerate ){ + $source[ "framerate" ] = floatval( $framerate ); + } + } + return $source; + } + + /** + * Get derivative "source" attributes + * @param $file File + * @param $transcodeKey string + * @param $options array + * @return array + */ + static public function getDerivativeSourceAttributes($file, $transcodeKey, $options = array() ){ + $fileName = $file->getTitle()->getDbKey(); + + $src = self::getTranscodedUrlForFile( $file, $transcodeKey ); + + if( $file->getHandler()->isAudio( $file ) ){ + $width = $height = 0; + } else { + list( $width, $height ) = WebVideoTranscode::getMaxSizeTransform( + $file, + self::$derivativeSettings[$transcodeKey]['maxSize'] + ); + } + + $framerate = ( isset( self::$derivativeSettings[$transcodeKey]['framerate'] ) )? + self::$derivativeSettings[$transcodeKey]['framerate'] : + $file->getHandler()->getFramerate( $file ); + // Setup the url src: + $src = in_array( 'fullurl', $options) ? wfExpandUrl( $src ) : $src; + $fields = array( + 'src' => $src, + 'title' => wfMessage( 'timedmedia-derivative-desc-' . $transcodeKey )->text(), + 'type' => self::$derivativeSettings[ $transcodeKey ][ 'type' ], + "shorttitle" => wfMessage( 'timedmedia-derivative-' . $transcodeKey )->text(), + "transcodekey" => $transcodeKey, + + // Add data attributes per emerging DASH / webTV adaptive streaming attributes + // eventually we will define a manifest xml entry point. + "width" => intval( $width ), + "height" => intval( $height ), + ); + + // a "ready" transcode should have a bitrate: + if ( isset( self::$transcodeState[$fileName] ) ) { + $fields["bandwidth"] = intval( + self::$transcodeState[$fileName][ $transcodeKey ]['final_bitrate'] + ); + } + + if ( !$file->getHandler()->isAudio( $file ) ) { + $fields += array( "framerate" => floatval( $framerate ) ); + } + return $fields; + } + + /** + * Queue up all enabled transcodes if missing. + * @param $file File object + */ + public static function startJobQueue( File $file ) { + global $wgEnabledTranscodeSet, $wgEnabledAudioTranscodeSet; + $keys = array_merge( $wgEnabledTranscodeSet, $wgEnabledAudioTranscodeSet ); + + // 'Natural sort' puts the transcodes in ascending order by resolution, + // which roughly gives us fastest-to-slowest order. + natsort($keys); + + foreach ( $keys as $tKey ) { + // Note the job queue will de-duplicate and handle various errors, so we + // can just blast out the full list here. + self::updateJobQueue( $file, $tKey ); + } + } + + /** + * Make sure all relevant transcodes for the given file are tracked in the + * transcodes table; add entries for any missing ones. + * + * @param $file File object + */ + public static function cleanupTranscodes( File $file ) { + global $wgEnabledTranscodeSet, $wgEnabledAudioTranscodeSet; + + $fileName = $file->getTitle()->getDbKey(); + $db = $file->repo->getMasterDB(); + + $transcodeState = self::getTranscodeState( $file, $db ); + + $keys = array_merge( $wgEnabledTranscodeSet, $wgEnabledAudioTranscodeSet ); + foreach ( $keys as $transcodeKey ) { + if ( !self::isTranscodeEnabled( $file, $transcodeKey ) ) { + // This transcode is no longer enabled or erroneously included... + // Leave it in place, allowing it to be removed manually; + // it won't be used in playback and should be doing no harm. + continue; + } + if ( !isset( $transcodeState[ $transcodeKey ] ) ) { + $db->insert( + 'transcode', + array( + 'transcode_image_name' => $fileName, + 'transcode_key' => $transcodeKey, + 'transcode_time_addjob' => null, + 'transcode_error' => "", + 'transcode_final_bitrate' => 0 + ), + __METHOD__, + array( 'IGNORE' ) + ); + } + } + } + + /** + * Check if the given transcode key is appropriate for the file. + * + * @param $file File object + * @param $transcodeKey String transcode key + * @return boolean + */ + public static function isTranscodeEnabled( File $file, $transcodeKey ) { + global $wgEnabledTranscodeSet, $wgEnabledAudioTranscodeSet; + + $audio = $file->getHandler()->isAudio( $file ); + if ( $audio ) { + $keys = $wgEnabledAudioTranscodeSet; + } else { + $keys = $wgEnabledTranscodeSet; + } + + if ( in_array( $transcodeKey, $keys ) ) { + $settings = self::$derivativeSettings[$transcodeKey]; + if ( $audio ) { + $sourceCodecs = $file->getHandler()->getStreamTypes( $file ); + $sourceCodec = $sourceCodecs ? strtolower( $sourceCodecs[0] ) : ''; + return ( $sourceCodec !== $settings['audioCodec'] ); + } else if ( self::isTargetLargerThanFile( $file, $settings['maxSize'] ) ) { + // Are we the smallest enabled transcode for this type? + // Then go ahead and make a wee little transcode for compat. + return self::isSmallestTranscodeForCodec( $transcodeKey ); + } else { + return true; + } + } else { + // Transcode key is invalid or has been disabled. + return false; + } + } + + /** + * Update the job queue if the file is not already in the job queue: + * @param $file File object + * @param $transcodeKey String transcode key + */ + public static function updateJobQueue( &$file, $transcodeKey ){ + $fileName = $file->getTitle()->getDbKey(); + $db = $file->repo->getMasterDB(); + + $transcodeState = self::getTranscodeState( $file, $db ); + + if ( !self::isTranscodeEnabled( $file, $transcodeKey ) ) { + return; + } + + // If the job hasn't been added yet, attempt to do so + if ( !isset( $transcodeState[ $transcodeKey ] ) ) { + $db->insert( + 'transcode', + array( + 'transcode_image_name' => $fileName, + 'transcode_key' => $transcodeKey, + 'transcode_time_addjob' => $db->timestamp(), + 'transcode_error' => "", + 'transcode_final_bitrate' => 0 + ), + __METHOD__, + array( 'IGNORE' ) + ); + + if ( !$db->affectedRows() ) { + // There is already a row for that job added by another request, no need to continue + return; + } + + $job = new WebVideoTranscodeJob( $file->getTitle(), array( + 'transcodeMode' => 'derivative', + 'transcodeKey' => $transcodeKey, + ) ); + + if ( $job->insert() ) { + // Clear the state cache ( now that we have updated the page ) + self::clearTranscodeCache( $fileName ); + } else { + // Adding job failed, update transcode row + $db->update( + 'transcode', + array( + 'transcode_time_error' => $db->timestamp(), + 'transcode_error' => "Failed to insert Job." + ), + array( + 'transcode_image_name' => $fileName, + 'transcode_key' => $transcodeKey, + ), + __METHOD__, + array( 'LIMIT' => 1 ) + ); + } + } + } + + /** + * Transforms the size per a given "maxSize" + * if maxSize is > file, file size is used + * @param $file File + * @param $targetMaxSize int + * @return array + */ + public static function getMaxSizeTransform( &$file, $targetMaxSize ){ + $maxSize = self::getMaxSize( $targetMaxSize ); + $sourceWidth = intval( $file->getWidth() ); + $sourceHeight = intval( $file->getHeight() ); + if ( $sourceHeight === 0 ) { + // Audio file + return array( 0, 0 ); + } + $sourceAspect = $sourceWidth / $sourceHeight; + $targetWidth = $sourceWidth; + $targetHeight = $sourceHeight; + if ( $sourceAspect <= $maxSize['aspect'] ) { + if ( $sourceHeight > $maxSize['height'] ) { + $targetHeight = $maxSize['height']; + $targetWidth = intval( $targetHeight * $sourceAspect ); + } + } else { + if ( $sourceWidth > $maxSize['width'] ) { + $targetWidth = $maxSize['width']; + $targetHeight = intval( $targetWidth / $sourceAspect ); + //some players do not like uneven frame sizes + } + } + //some players do not like uneven frame sizes + $targetWidth += $targetWidth%2; + $targetHeight += $targetHeight%2; + return array( $targetWidth, $targetHeight ); + } + + /** + * Test if a given transcode target is larger than the source file + * + * @param $file File object + * @param $targetMaxSize string + * @return bool + */ + public static function isTargetLargerThanFile( &$file, $targetMaxSize ){ + $maxSize = self::getMaxSize( $targetMaxSize ); + $sourceWidth = $file->getWidth(); + $sourceHeight = $file->getHeight(); + $sourceAspect = intval( $sourceWidth ) / intval( $sourceHeight ); + if ( $sourceAspect <= $maxSize['aspect'] ) { + return ( $maxSize['height'] > $sourceHeight ); + } else { + return ( $maxSize['width'] > $sourceWidth ); + } + } + + /** + * Is the given transcode key the smallest configured transcode for + * its video codec? + */ + public static function isSmallestTranscodeForCodec( $transcodeKey ) { + global $wgEnabledTranscodeSet; + + $settings = self::$derivativeSettings[$transcodeKey]; + $vcodec = $settings['videoCodec']; + $maxSize = self::getMaxSize( $settings['maxSize'] ); + + foreach ( $wgEnabledTranscodeSet as $tKey ) { + $tsettings = self::$derivativeSettings[$tKey]; + if ( $tsettings['videoCodec'] === $vcodec ) { + $tmaxSize = self::getMaxSize( $tsettings['maxSize'] ); + if ( $tmaxSize['width'] < $maxSize['width'] ) { + return false; + } + if ( $tmaxSize['height'] < $maxSize['height'] ) { + return false; + } + } + } + + return true; + } + + /** + * Return maxSize array for given maxSize setting + * + * @param $targetMaxSize string + * @return array + */ + public static function getMaxSize( $targetMaxSize ){ + $maxSize = array(); + $targetMaxSize = explode( 'x', $targetMaxSize ); + $maxSize['width'] = intval( $targetMaxSize[0] ); + if ( count( $targetMaxSize ) == 1 ) { + $maxSize['height'] = intval( $targetMaxSize[0] ); + } else { + $maxSize['height'] = intval( $targetMaxSize[1] ); + } + // check for zero size ( audio ) + if( $maxSize['width'] === 0 || $maxSize['height'] == 0 ){ + return 0; + } + $maxSize['aspect'] = $maxSize['width'] / $maxSize['height']; + return $maxSize; + } +} diff --git a/extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscodeJob.php b/extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscodeJob.php new file mode 100644 index 00000000..d437d9c7 --- /dev/null +++ b/extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscodeJob.php @@ -0,0 +1,965 @@ +removeDuplicates = true; + } + + /** + * Local function to debug output ( jobs don't have access to the maintenance output class ) + * @param $msg string + */ + private function output( $msg ){ + print $msg . "\n"; + } + + /** + * @return File + */ + private function getFile() { + if( !$this->file ){ + $this->file = wfLocalFile( $this->title ); + } + return $this->file; + } + + /** + * @return string + */ + private function getTargetEncodePath(){ + if( !$this->targetEncodeFile ){ + $file = $this->getFile(); + $transcodeKey = $this->params[ 'transcodeKey' ]; + $this->targetEncodeFile = WebVideoTranscode::getTargetEncodeFile( $file, $transcodeKey ); + $this->targetEncodeFile->bind( $this ); + } + return $this->targetEncodeFile->getPath(); + } + /** + * purge temporary encode target + */ + private function purgeTargetEncodeFile () { + if ( $this->targetEncodeFile ) { + $this->targetEncodeFile->purge(); + $this->targetEncodeFile = null; + } + } + + /** + * @return string|bool + */ + private function getSourceFilePath(){ + if( !$this->sourceFilePath ){ + $file = $this->getFile(); + $this->source = $file->repo->getLocalReference( $file->getPath() ); + if ( !$this->source ) { + $this->sourceFilePath = false; + } else { + $this->sourceFilePath = $this->source->getPath(); + } + } + return $this->sourceFilePath; + } + + /** + * Update the transcode table with failure time and error + * @param $transcodeKey string + * @param $error string + * + */ + private function setTranscodeError( $transcodeKey, $error ){ + $dbw = wfGetDB( DB_MASTER ); + $dbw->update( + 'transcode', + array( + 'transcode_time_error' => $dbw->timestamp(), + 'transcode_error' => $error + ), + array( + 'transcode_image_name' => $this->getFile()->getName(), + 'transcode_key' => $transcodeKey + ), + __METHOD__ + ); + $this->setLastError( $error ); + } + + /** + * Run the transcode request + * @return boolean success + */ + public function run() { + global $wgVersion, $wgFFmpeg2theoraLocation; + // get a local pointer to the file + $file = $this->getFile(); + + // Validate the file exists: + if( !$file ){ + $this->output( $this->title . ': File not found ' ); + return false; + } + + // Validate the transcode key param: + $transcodeKey = $this->params['transcodeKey']; + // Build the destination target + if( ! isset( WebVideoTranscode::$derivativeSettings[ $transcodeKey ] )){ + $error = "Transcode key $transcodeKey not found, skipping"; + $this->output( $error ); + $this->setLastError( $error ); + return false; + } + + // Validate the source exists: + if( !$this->getSourceFilePath() || !is_file( $this->getSourceFilePath() ) ){ + $status = $this->title . ': Source not found ' . $this->getSourceFilePath(); + $this->output( $status ); + $this->setTranscodeError( $transcodeKey, $status ); + return false; + } + + $options = WebVideoTranscode::$derivativeSettings[ $transcodeKey ]; + + if ( isset( $options[ 'novideo' ] ) ) { + $this->output( "Encoding to audio codec: " . $options['audioCodec'] ); + } else { + $this->output( "Encoding to codec: " . $options['videoCodec'] ); + } + + $dbw = wfGetDB( DB_MASTER ); + + // Check if we have "already started" the transcode ( possible error ) + $dbStartTime = $dbw->selectField( 'transcode', 'transcode_time_startwork', + array( + 'transcode_image_name' => $this->getFile()->getName(), + 'transcode_key' => $transcodeKey + ), + __METHOD__ + ); + if( ! is_null( $dbStartTime ) ){ + $error = 'Error, running transcode job, for job that has already started'; + $this->output( $error ); + return true; + } + + // Update the transcode table letting it know we have "started work": + $jobStartTimeCache = $dbw->timestamp(); + $dbw->update( + 'transcode', + array( 'transcode_time_startwork' => $jobStartTimeCache ), + array( + 'transcode_image_name' => $this->getFile()->getName(), + 'transcode_key' => $transcodeKey + ), + __METHOD__ + ); + // Avoid contention and "server has gone away" errors as + // the transcode will take a very long time in some cases + $dbw->commit( __METHOD__, 'flush' ); + + // Check the codec see which encode method to call; + if ( isset( $options[ 'novideo' ] ) ) { + $status = $this->ffmpegEncode( $options ); + } elseif( $options['videoCodec'] == 'theora' && $wgFFmpeg2theoraLocation !== false ){ + $status = $this->ffmpeg2TheoraEncode( $options ); + } elseif( $options['videoCodec'] == 'vp8' || $options['videoCodec'] == 'vp9' || $options['videoCodec'] == 'h264' || ( $options['videoCodec'] == 'theora' && $wgFFmpeg2theoraLocation === false ) ){ + // Check for twopass: + if( isset( $options['twopass'] ) ){ + // ffmpeg requires manual two pass + $status = $this->ffmpegEncode( $options, 1 ); + if( $status && !is_string($status) ){ + $status = $this->ffmpegEncode( $options, 2 ); + } + } else { + $status = $this->ffmpegEncode( $options ); + } + } else { + wfDebug( 'Error unknown codec:' . $options['videoCodec'] ); + $status = 'Error unknown target encode codec:' . $options['videoCodec']; + } + + // Remove any log files all useful info should be in status and or we are done with 2 passs encoding + $this->removeFffmpgeLogFiles(); + + // Do a quick check to confirm the job was not restarted or removed while we were transcoding + // Confirm the in memory $jobStartTimeCache matches db start time + $dbStartTime = $dbw->selectField( 'transcode', 'transcode_time_startwork', + array( + 'transcode_image_name' => $this->getFile()->getName(), + 'transcode_key' => $transcodeKey + ) + ); + + // Check for ( hopefully rare ) issue of or job restarted while transcode in progress + if( $dbw->timestamp( $jobStartTimeCache ) != $dbw->timestamp( $dbStartTime ) ){ + $this->output('Possible Error, transcode task restarted, removed, or completed while transcode was in progress'); + // if an error; just error out, we can't remove temp files or update states, because the new job may be doing stuff. + if( $status !== true ){ + $this->setTranscodeError( $transcodeKey, $status ); + return false; + } + // else just continue with db updates, and when the new job comes around it won't start because it will see + // that the job has already been started. + } + + // If status is oky and target does not exist, reset status + if( $status === true && !is_file( $this->getTargetEncodePath() ) ) { + $status = 'Target does not exist: ' . $this->getTargetEncodePath(); + } + // If status is ok and target is larger than 0 bytes + if( $status === true && filesize( $this->getTargetEncodePath() ) > 0 ){ + $file = $this->getFile(); + $storeOptions = null; + if ( version_compare( $wgVersion, '1.23c', '>' ) && + strpos( $options['type'], '/ogg' ) !== false && + $file->getLength() + ) { + // Ogg files need a duration header for firefox + $storeOptions['headers']['X-Content-Duration'] = floatval( $file->getLength() ); + } + + // Copy derivative from the FS into storage at $finalDerivativeFilePath + $result = $file->getRepo()->quickImport( + $this->getTargetEncodePath(), // temp file + WebVideoTranscode::getDerivativeFilePath( $file, $transcodeKey ), // storage + $storeOptions + ); + if ( !$result->isOK() ) { + // no need to invalidate all pages with video. Because all pages remain valid ( no $transcodeKey derivative ) + // just clear the file page ( so that the transcode table shows the error ) + $this->title->invalidateCache(); + $this->setTranscodeError( $transcodeKey, $result->getWikiText() ); + $status = false; + } else { + $bitrate = round( intval( filesize( $this->getTargetEncodePath() ) / $file->getLength() ) * 8 ); + //wfRestoreWarnings(); + // Update the transcode table with success time: + $dbw->update( + 'transcode', + array( + 'transcode_error' => '', + 'transcode_time_success' => $dbw->timestamp(), + 'transcode_final_bitrate' => $bitrate + ), + array( + 'transcode_image_name' => $this->getFile()->getName(), + 'transcode_key' => $transcodeKey, + ), + __METHOD__ + ); + $dbw->commit( __METHOD__, 'flush' ); + WebVideoTranscode::invalidatePagesWithFile( $this->title ); + } + } else { + // Update the transcode table with failure time and error + $this->setTranscodeError( $transcodeKey, $status ); + // no need to invalidate all pages with video. Because all pages remain valid ( no $transcodeKey derivative ) + // just clear the file page ( so that the transcode table shows the error ) + $this->title->invalidateCache(); + } + //done with encoding target, clean up + $this->purgeTargetEncodeFile(); + + // Clear the webVideoTranscode cache ( so we don't keep out dated table cache around ) + WebVideoTranscode::clearTranscodeCache( $this->title->getDBkey() ); + + $url = WebVideoTranscode::getTranscodedUrlForFile( $file, $transcodeKey ); + SquidUpdate::purge( array( $url ) ); + + if ($status !== true) { + $this->setLastError( $status ); + } + return $status === true; + } + + function removeFffmpgeLogFiles(){ + $path = $this->getTargetEncodePath(); + $dir = dirname( $path ); + if ( is_dir( $dir ) ) { + $dh = opendir( $dir ); + if ( $dh ) { + while ( ($file = readdir($dh)) !== false ) { + $log_path = "$dir/$file"; + $ext = strtolower( pathinfo( $log_path, PATHINFO_EXTENSION ) ); + if( $ext == 'log' && substr( $log_path, 0 , strlen($path) ) == $path ){ + wfSuppressWarnings(); + unlink( $log_path ); + wfRestoreWarnings(); + } + } + closedir( $dh ); + } + } + } + + /** + * Utility helper for ffmpeg and ffmpeg2theora mapping + * @param $options array + * @param $pass int + * @return bool|string + */ + function ffmpegEncode( $options, $pass=0 ){ + global $wgFFmpegLocation, $wgTranscodeBackgroundMemoryLimit; + + if( !is_file( $this->getSourceFilePath() ) ) { + return "source file is missing, " . $this->getSourceFilePath() . ". Encoding failed."; + } + + // Set up the base command + $cmd = wfEscapeShellArg( $wgFFmpegLocation ) . ' -y -i ' . wfEscapeShellArg( $this->getSourceFilePath() ); + + + if( isset( $options['vpre'] ) ){ + $cmd.= ' -vpre ' . wfEscapeShellArg( $options['vpre'] ); + } + + if ( isset( $options['novideo'] ) ) { + $cmd.= " -vn "; + } elseif( $options['videoCodec'] == 'vp8' || $options['videoCodec'] == 'vp9' ){ + $cmd.= $this->ffmpegAddWebmVideoOptions( $options, $pass ); + } elseif( $options['videoCodec'] == 'h264'){ + $cmd.= $this->ffmpegAddH264VideoOptions( $options, $pass ); + } elseif( $options['videoCodec'] == 'theora'){ + $cmd.= $this->ffmpegAddTheoraVideoOptions( $options, $pass ); + } + // Add size options: + $cmd .= $this->ffmpegAddVideoSizeOptions( $options ) ; + + // Check for start time + if( isset( $options['starttime'] ) ){ + $cmd.= ' -ss ' . wfEscapeShellArg( $options['starttime'] ); + } else { + $options['starttime'] = 0; + } + // Check for end time: + if( isset( $options['endtime'] ) ){ + $cmd.= ' -t ' . intval( $options['endtime'] ) - intval($options['starttime'] ) ; + } + + if ( $pass == 1 || isset( $options['noaudio'] ) ) { + $cmd.= ' -an'; + } else { + $cmd.= $this->ffmpegAddAudioOptions( $options, $pass ); + } + + if ( $pass != 0 ) { + $cmd.=" -pass " .wfEscapeShellArg( $pass ) ; + $cmd.=" -passlogfile " . wfEscapeShellArg( $this->getTargetEncodePath() .'.log' ); + } + // And the output target: + if ($pass == 1) { + $cmd.= ' /dev/null'; + } else{ + $cmd.= " " . $this->getTargetEncodePath(); + } + + $this->output( "Running cmd: \n\n" .$cmd . "\n" ); + + // Right before we output remove the old file + $retval = 0; + $shellOutput = $this->runShellExec( $cmd, $retval ); + + if( $retval != 0 ){ + return $cmd . + "\n\nExitcode: $retval\nMemory: $wgTranscodeBackgroundMemoryLimit\n\n" . + $shellOutput; + } + return true; + } + + /** + * Adds ffmpeg shell options for h264 + * + * @param $options + * @param $pass + * @return string + */ + function ffmpegAddH264VideoOptions( $options, $pass ){ + global $wgFFmpegThreads; + // Set the codec: + $cmd= " -threads " . intval( $wgFFmpegThreads ) . " -vcodec libx264"; + // Check for presets: + if( isset( $options['preset'] ) ){ + // Add the two vpre types: + switch( $options['preset'] ){ + case 'ipod320': + $cmd .= " -profile:v baseline -preset slow -coder 0 -bf 0 -weightb 1 -level 13 -maxrate 768k -bufsize 3M"; + break; + case '720p': + case 'ipod640': + $cmd .= " -profile:v baseline -preset slow -coder 0 -bf 0 -refs 1 -weightb 1 -level 31 -maxrate 10M -bufsize 10M"; + break; + default: + // in the default case just pass along the preset to ffmpeg + $cmd.= " -vpre " . wfEscapeShellArg( $options['preset'] ); + break; + } + } + if( isset( $options['videoBitrate'] ) ){ + $cmd.= " -b " . wfEscapeShellArg ( $options['videoBitrate'] ); + } + // Output mp4 + $cmd.=" -f mp4"; + return $cmd; + } + + function ffmpegAddVideoSizeOptions( $options ){ + $cmd = ''; + // Get a local pointer to the file object + $file = $this->getFile(); + + // Check for aspect ratio ( we don't do anything with this right now) + if ( isset( $options['aspect'] ) ) { + $aspectRatio = $options['aspect']; + } else { + $aspectRatio = $file->getWidth() . ':' . $file->getHeight(); + } + if (isset( $options['maxSize'] )) { + // Get size transform ( if maxSize is > file, file size is used: + + list( $width, $height ) = WebVideoTranscode::getMaxSizeTransform( $file, $options['maxSize'] ); + $cmd.= ' -s ' . intval( $width ) . 'x' . intval( $height ); + } elseif ( + (isset( $options['width'] ) && $options['width'] > 0 ) + && + (isset( $options['height'] ) && $options['height'] > 0 ) + ){ + $cmd.= ' -s ' . intval( $options['width'] ) . 'x' . intval( $options['height'] ); + } + + // Handle crop: + $optionMap = array( + 'cropTop' => '-croptop', + 'cropBottom' => '-cropbottom', + 'cropLeft' => '-cropleft', + 'cropRight' => '-cropright' + ); + foreach( $optionMap as $name => $cmdArg ){ + if( isset($options[$name]) ){ + $cmd.= " $cmdArg " . wfEscapeShellArg( $options[$name] ); + } + } + return $cmd; + } + /** + * Adds ffmpeg shell options for webm + * + * @param $options + * @param $pass + * @return string + */ + function ffmpegAddWebmVideoOptions( $options, $pass ){ + global $wgFFmpegThreads; + + // Get a local pointer to the file object + $file = $this->getFile(); + + $cmd =' -threads ' . intval( $wgFFmpegThreads ); + + // check for presets: + if( isset($options['preset']) ){ + if ($options['preset'] == "360p") { + $cmd.= " -vpre libvpx-360p"; + } elseif ( $options['preset'] == "720p" ) { + $cmd.= " -vpre libvpx-720p"; + } elseif ( $options['preset'] == "1080p" ) { + $cmd.= " -vpre libvpx-1080p"; + } + } + + // Add the boiler plate vp8 ffmpeg command: + $cmd.=" -skip_threshold 0 -bufsize 6000k -rc_init_occupancy 4000"; + + // Check for video quality: + if ( isset( $options['videoQuality'] ) && $options['videoQuality'] >= 0 ) { + // Map 0-10 to 63-0, higher values worse quality + $quality = 63 - intval( intval( $options['videoQuality'] )/10 * 63 ); + $cmd .= " -qmin " . wfEscapeShellArg( $quality ); + $cmd .= " -qmax " . wfEscapeShellArg( $quality ); + } + + // Check for video bitrate: + if ( isset( $options['videoBitrate'] ) ) { + $cmd.= " -qmin 1 -qmax 51"; + $cmd.= " -vb " . wfEscapeShellArg( $options['videoBitrate'] * 1000 ); + } + // Set the codec: + if ( $options['videoCodec'] === 'vp9' ) { + $cmd.= " -vcodec libvpx-vp9"; + if ( isset( $options['tileColumns'] ) ) { + $cmd.= ' -tile-columns ' . wfEscapeShellArg( $options['tileColumns'] ); + } + } else { + $cmd.= " -vcodec libvpx"; + } + + // Check for keyframeInterval + if( isset( $options['keyframeInterval'] ) ){ + $cmd.= ' -g ' . wfEscapeShellArg( $options['keyframeInterval'] ); + $cmd.= ' -keyint_min ' . wfEscapeShellArg( $options['keyframeInterval'] ); + } + if( isset( $options['deinterlace'] ) ){ + $cmd.= ' -deinterlace'; + } + + // Output WebM + $cmd.=" -f webm"; + + return $cmd; + } + + /** + * Adds ffmpeg/avconv shell options for ogg + * + * Used only when $wgFFmpeg2theoraLocation set to false. + * Warning: does not create Ogg skeleton metadata track. + * + * @param $options + * @param $pass + * @return string + */ + function ffmpegAddTheoraVideoOptions( $options, $pass ){ + global $wgFFmpegThreads; + + // Get a local pointer to the file object + $file = $this->getFile(); + + $cmd =' -threads ' . intval( $wgFFmpegThreads ); + + // Check for video quality: + if ( isset( $options['videoQuality'] ) && $options['videoQuality'] >= 0 ) { + // Map 0-10 to 63-0, higher values worse quality + $quality = 63 - intval( intval( $options['videoQuality'] )/10 * 63 ); + $cmd .= " -qmin " . wfEscapeShellArg( $quality ); + $cmd .= " -qmax " . wfEscapeShellArg( $quality ); + } + + // Check for video bitrate: + if ( isset( $options['videoBitrate'] ) ) { + $cmd.= " -qmin 1 -qmax 51"; + $cmd.= " -vb " . wfEscapeShellArg( $options['videoBitrate'] * 1000 ); + } + // Set the codec: + $cmd.= " -vcodec theora"; + + // Check for keyframeInterval + if( isset( $options['keyframeInterval'] ) ){ + $cmd.= ' -g ' . wfEscapeShellArg( $options['keyframeInterval'] ); + $cmd.= ' -keyint_min ' . wfEscapeShellArg( $options['keyframeInterval'] ); + } + if( isset( $options['deinterlace'] ) ){ + $cmd.= ' -deinterlace'; + } + if( isset( $options['framerate'] ) ) { + $cmd.= ' -r ' . wfEscapeShellArg( $options['framerate'] ); + } + + // Output Ogg + $cmd.=" -f ogg"; + + return $cmd; + } + + /** + * @param $options array + * @param $pass + * @return string + */ + function ffmpegAddAudioOptions( $options, $pass ){ + $cmd =''; + if( isset( $options['audioQuality'] ) ){ + $cmd.= " -aq " . wfEscapeShellArg( $options['audioQuality'] ); + } + if( isset( $options['audioBitrate'] )){ + $cmd.= ' -ab ' . intval( $options['audioBitrate'] ) * 1000; + } + if( isset( $options['samplerate'] ) ){ + $cmd.= " -ar " . wfEscapeShellArg( $options['samplerate'] ); + } + if( isset( $options['channels'] ) ){ + $cmd.= " -ac " . wfEscapeShellArg( $options['channels'] ); + } + + if( isset( $options['audioCodec'] ) ){ + $encoders = array( + 'vorbis' => 'libvorbis', + 'opus' => 'libopus', + 'mp3' => 'libmp3lame', + ); + if ( isset( $encoders[ $options['audioCodec'] ] ) ) { + $codec = $encoders[ $options['audioCodec'] ]; + } else { + $codec = $options['audioCodec']; + } + $cmd.= " -acodec " . wfEscapeShellArg( $codec ); + if ( $codec === 'aac' ) { + // the aac encoder is currently "experimental" in libav 9? :P + $cmd .= ' -strict experimental'; + } + } else { + // if no audio codec set use vorbis : + $cmd.= " -acodec libvorbis "; + } + return $cmd; + } + + /** + * ffmpeg2Theora mapping is much simpler since it is the basis of the the firefogg API + * @param $options array + * @return bool|string + */ + function ffmpeg2TheoraEncode( $options ){ + global $wgFFmpeg2theoraLocation, $wgTranscodeBackgroundMemoryLimit; + + if( !is_file( $this->getSourceFilePath() ) ) { + return "source file is missing, " . $this->getSourceFilePath() . ". Encoding failed."; + } + + // Set up the base command + $cmd = wfEscapeShellArg( $wgFFmpeg2theoraLocation ) . ' ' . wfEscapeShellArg( $this->getSourceFilePath() ); + + $file = $this->getFile(); + + if( isset( $options['maxSize'] ) ){ + list( $width, $height ) = WebVideoTranscode::getMaxSizeTransform( $file, $options['maxSize'] ); + $options['width'] = $width; + $options['height'] = $height; + $options['aspect'] = $width . ':' . $height; + unset( $options['maxSize'] ); + } + + // Add in the encode settings + foreach( $options as $key => $val ){ + if( isset( self::$foggMap[$key] ) ){ + if( is_array( self::$foggMap[$key] ) ){ + $cmd.= ' '. implode(' ', self::$foggMap[$key] ); + } elseif ($val == 'true' || $val === true){ + $cmd.= ' '. self::$foggMap[$key]; + } elseif ($val == 'false' || $val === false){ + //ignore "false" flags + } else { + //normal get/set value + $cmd.= ' '. self::$foggMap[$key] . ' ' . wfEscapeShellArg( $val ); + } + } + } + + // Add the output target: + $outputFile = $this->getTargetEncodePath(); + $cmd.= ' -o ' . wfEscapeShellArg ( $outputFile ); + + $this->output( "Running cmd: \n\n" .$cmd . "\n" ); + + $retval = 0; + $shellOutput = $this->runShellExec( $cmd, $retval ); + + // ffmpeg2theora returns 0 status on some errors, so also check for file + if( $retval != 0 || !is_file( $outputFile ) || filesize( $outputFile ) === 0 ){ + return $cmd . + "\n\nExitcode: $retval\nMemory: $wgTranscodeBackgroundMemoryLimit\n\n" . + $shellOutput; + } + return true; + } + + /** + * Runs the shell exec command. + * if $wgEnableBackgroundTranscodeJobs is enabled will mannage a background transcode task + * else it just directly passes off to wfShellExec + * + * @param $cmd String Command to be run + * @param $retval String, refrence variable to return the exit code + * @return string + */ + public function runShellExec( $cmd, &$retval ){ + global $wgTranscodeBackgroundTimeLimit, + $wgTranscodeBackgroundMemoryLimit, + $wgTranscodeBackgroundSizeLimit, + $wgEnableNiceBackgroundTranscodeJobs; + + // For profiling + $caller = wfGetCaller(); + + // Check if background tasks are enabled + if( $wgEnableNiceBackgroundTranscodeJobs === false ){ + // Directly execute the shell command: + $limits = array( + "filesize" => $wgTranscodeBackgroundSizeLimit, + "memory" => $wgTranscodeBackgroundMemoryLimit, + "time" => $wgTranscodeBackgroundTimeLimit + ); + return wfShellExec( $cmd . ' 2>&1', $retval , array(), $limits, + array( 'profileMethod' => $caller ) ); + } + + $encodingLog = $this->getTargetEncodePath() . '.stdout.log'; + $retvalLog = $this->getTargetEncodePath() . '.retval.log'; + // Check that we can actually write to these files + //( no point in running the encode if we can't write ) + wfSuppressWarnings(); + if( ! touch( $encodingLog) || ! touch( $retvalLog ) ){ + wfRestoreWarnings(); + $retval = 1; + return "Error could not write to target location"; + } + wfRestoreWarnings(); + + // Fork out a process for running the transcode + $pid = pcntl_fork(); + if ($pid == -1) { + $errorMsg = '$wgEnableNiceBackgroundTranscodeJobs enabled but failed pcntl_fork'; + $retval = 1; + $this->output( $errorMsg); + return $errorMsg; + } elseif ( $pid == 0) { + // we are the child + $this->runChildCmd( $cmd, $retval, $encodingLog, $retvalLog, $caller ); + // dont remove any temp files in the child process, this is done + // once the parent is finished + $this->targetEncodeFile->preserve(); + if ( $this->source instanceof TempFSFile ) { + $this->source->preserve(); + } + // exit with the same code as the transcode: + exit( $retval ); + } else { + // we are the parent monitor and return status + return $this->monitorTranscode($pid, $retval, $encodingLog, $retvalLog); + } + } + + /** + * @param $cmd + * @param $retval + * @param $encodingLog + * @param $retvalLog + * @param string $caller The calling method + */ + public function runChildCmd( $cmd, &$retval, $encodingLog, $retvalLog, $caller ){ + global $wgTranscodeBackgroundTimeLimit, + $wgTranscodeBackgroundMemoryLimit, + $wgTranscodeBackgroundSizeLimit; + // In theory we should use pcntl_exec but not sure how to get the stdout, ensure + // we don't max php memory with the same protections provided by wfShellExec. + + // pcntl_exec requires a direct path to the exe and arguments as an array: + //$cmd = explode(' ', $cmd ); + //$baseCmd = array_shift( $cmd ); + //print "run:" . $baseCmd . " args: " . print_r( $cmd, true ); + //$status = pcntl_exec($baseCmd , $cmd ); + + // Directly execute the shell command: + //global $wgTranscodeBackgroundPriority; + //$status = wfShellExec( 'nice -n ' . $wgTranscodeBackgroundPriority . ' '. $cmd . ' 2>&1', $retval ); + $limits = array( + "filesize" => $wgTranscodeBackgroundSizeLimit, + "memory" => $wgTranscodeBackgroundMemoryLimit, + "time" => $wgTranscodeBackgroundTimeLimit + ); + $status = wfShellExec( $cmd . ' 2>&1', $retval , array(), $limits, + array( 'profileMethod' => $caller ) ); + + // Output the status: + wfSuppressWarnings(); + file_put_contents( $encodingLog, $status ); + // Output the retVal to the $retvalLog + file_put_contents( $retvalLog, $retval ); + wfRestoreWarnings(); + } + + /** + * @param $pid + * @param $retval + * @param $encodingLog + * @param $retvalLog + * @return string + */ + public function monitorTranscode( $pid, &$retval, $encodingLog, $retvalLog ){ + global $wgTranscodeBackgroundTimeLimit, $wgLang; + $errorMsg = ''; + $loopCount = 0; + $oldFileSize = 0; + $startTime = time(); + $fileIsNotGrowing = false; + + $this->output( "Encoding with pid: $pid \npcntl_waitpid: " . pcntl_waitpid( $pid, $status, WNOHANG OR WUNTRACED) . + "\nisProcessRunning: " . self::isProcessRunningKillZombie( $pid ) . "\n" ); + + // Check that the child process is still running ( note this does not work well with pcntl_waitpid + // for some reason :( + while( self::isProcessRunningKillZombie( $pid ) ) { + //$this->output( "$pid is running" ); + + // Check that the target file is growing ( every 5 seconds ) + if( $loopCount == 10 ){ + // only run check if we are outputing to target file + // ( two pass encoding does not output to target on first pass ) + clearstatcache(); + $newFileSize = is_file( $this->getTargetEncodePath() ) ? filesize( $this->getTargetEncodePath() ) : 0; + // Don't start checking for file growth until we have an initial positive file size: + if( $newFileSize > 0 ){ + $this->output( $wgLang->formatSize( $newFileSize ). ' Total size, encoding ' . + $wgLang->formatSize( ( $newFileSize - $oldFileSize ) / 5 ) . ' per second' ); + if( $newFileSize == $oldFileSize ){ + if( $fileIsNotGrowing ){ + $errorMsg = "Target File is not increasing in size, kill process."; + $this->output( $errorMsg ); + // file is not growing in size, kill proccess + $retval = 1; + + //posix_kill( $pid, 9); + self::killProcess( $pid ); + break; + } + // Wait an additional 5 seconds of the file not growing to confirm + // the transcode is frozen. + $fileIsNotGrowing = true; + } else { + $fileIsNotGrowing = false; + } + $oldFileSize = $newFileSize; + } + // reset the loop counter + $loopCount = 0; + } + + // Check if we have global job run-time has been exceeded: + if ( $wgTranscodeBackgroundTimeLimit && time() - $startTime > $wgTranscodeBackgroundTimeLimit ){ + $errorMsg = "Encoding exceeded max job run time ( " + . TimedMediaHandler::seconds2npt( $wgTranscodeBackgroundTimeLimit ) . " ), kill process."; + $this->output( $errorMsg ); + // File is not growing in size, kill proccess + $retval = 1; + //posix_kill( $pid, 9); + self::killProcess( $pid ); + break; + } + + // Sleep for one second before repeating loop + $loopCount++; + sleep( 1 ); + } + + $returnPcntl = pcntl_wexitstatus( $status ); + // check status + wfSuppressWarnings(); + $returnCodeFile = file_get_contents( $retvalLog ); + wfRestoreWarnings(); + //$this->output( "TranscodeJob:: Child pcntl return:". $returnPcntl . ' Log file exit code:' . $returnCodeFile . "\n" ); + + // File based exit code seems more reliable than pcntl_wexitstatus + $retval = $returnCodeFile; + + // return the encoding log contents ( will be inserted into error table if an error ) + // ( will be ignored and removed if success ) + if( $errorMsg!= '' ){ + $errorMsg.="\n\n"; + } + return $errorMsg . file_get_contents( $encodingLog ); + } + + /** + * check if proccess is running and not a zombie + * @param $pid int + * @return bool + */ + public static function isProcessRunningKillZombie( $pid ){ + exec( "ps $pid", $processState ); + if( !isset( $processState[1] ) ){ + return false; + } + if( strpos( $processState[1], '' ) !== false ){ + // posix kill does not seem to work + //posix_kill( $pid, 9); + self::killProcess( $pid ); + return false; + } + return true; + } + + /** + * Kill Application PID + * + * @param $pid int + * @return bool + */ + public static function killProcess( $pid ){ + exec( "kill -9 $pid" ); + exec( "ps $pid", $processState ); + if( isset( $processState[1] ) ){ + return false; + } + return true; + } + + /** + * Mapping between firefogg api and ffmpeg2theora command line + * + * This lets us share a common api between firefogg and WebVideoTranscode + * also see: http://firefogg.org/dev/index.html + */ + public static $foggMap = array( + // video + 'width' => "--width", + 'height' => "--height", + 'maxSize' => "--max_size", + 'noUpscaling' => "--no-upscaling", + 'videoQuality'=> "-v", + 'videoBitrate' => "-V", + 'twopass' => "--two-pass", + 'optimize' => "--optimize", + 'framerate' => "-F", + 'aspect' => "--aspect", + 'starttime' => "--starttime", + 'endtime' => "--endtime", + 'cropTop' => "--croptop", + 'cropBottom' => "--cropbottom", + 'cropLeft' => "--cropleft", + 'cropRight' => "--cropright", + 'keyframeInterval'=> "--keyint", + 'denoise' => array("--pp", "de"), + 'deinterlace' => "--deinterlace", + 'novideo' => array("--novideo", "--no-skeleton"), + 'bufDelay' => "--buf-delay", + // audio + 'audioQuality' => "-a", + 'audioBitrate' => "-A", + 'samplerate' => "-H", + 'channels' => "-c", + 'noaudio' => "--noaudio", + // metadata + 'artist' => "--artist", + 'title' => "--title", + 'date' => "--date", + 'location' => "--location", + 'organization' => "--organization", + 'copyright' => "--copyright", + 'license' => "--license", + 'contact' => "--contact" + ); + +} diff --git a/extensions/TimedMediaHandler/archives/transcode_name_key.sql b/extensions/TimedMediaHandler/archives/transcode_name_key.sql new file mode 100644 index 00000000..85b00858 --- /dev/null +++ b/extensions/TimedMediaHandler/archives/transcode_name_key.sql @@ -0,0 +1,3 @@ +-- Add unique constrain on name/key +DROP INDEX /*i*/transcode_name_inx ON /*_*/transcode; +CREATE UNIQUE INDEX /*i*/transcode_name_key ON /*_*/transcode (transcode_image_name,transcode_key); diff --git a/extensions/TimedMediaHandler/composer.json b/extensions/TimedMediaHandler/composer.json new file mode 100644 index 00000000..3d55a97d --- /dev/null +++ b/extensions/TimedMediaHandler/composer.json @@ -0,0 +1,10 @@ +{ + "require-dev": { + "jakub-onderka/php-parallel-lint": "0.9.*" + }, + "scripts": { + "test": [ + "parallel-lint . --exclude vendor" + ] + } +} diff --git a/extensions/TimedMediaHandler/gitinfo.json b/extensions/TimedMediaHandler/gitinfo.json new file mode 100644 index 00000000..d26a9734 --- /dev/null +++ b/extensions/TimedMediaHandler/gitinfo.json @@ -0,0 +1 @@ +{"headSHA1": "7cab54e2e482d51df69e4b9c880182b23cabf0bc\n", "head": "7cab54e2e482d51df69e4b9c880182b23cabf0bc\n", "remoteURL": "https://gerrit.wikimedia.org/r/mediawiki/extensions/TimedMediaHandler", "branch": "7cab54e2e482d51df69e4b9c880182b23cabf0bc\n", "headCommitDate": "1453929642"} \ No newline at end of file diff --git a/extensions/TimedMediaHandler/handlers/FLACHandler/FLACHandler.php b/extensions/TimedMediaHandler/handlers/FLACHandler/FLACHandler.php new file mode 100644 index 00000000..f08c9b90 --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/FLACHandler/FLACHandler.php @@ -0,0 +1,74 @@ +unpackMetadata( $file->getMetadata() ); + + if ( !$metadata || isset( $metadata['error'] ) ) { + return false; + } + + if( isset( $metadata['audio'] ) && $metadata['audio']['dataformat'] == 'flac' ){ + $streamTypes[] = 'FLAC'; + } + + return $streamTypes; + } + + /** + * @param $file File + * @return String + */ + function getShortDesc( $file ) { + global $wgLang; + + $streamTypes = $this->getStreamTypes( $file ); + if ( !$streamTypes ) { + return parent::getShortDesc( $file ); + } + return wfMessage( 'timedmedia-flac-short-audio', + $wgLang->formatTimePeriod( $this->getLength( $file ) ) )->text(); + } + + /** + * @param $file File + * @return String + */ + function getLongDesc( $file ) { + global $wgLang; + $streamTypes = $this->getStreamTypes( $file ); + if ( !$streamTypes ) { + return parent::getLongDesc( $file ); + } + return wfMessage('timedmedia-flac-long-audio', + $wgLang->formatTimePeriod( $this->getLength($file) ), + $wgLang->formatBitrate( $this->getBitRate( $file ) ) + )->text(); + + } + +} diff --git a/extensions/TimedMediaHandler/handlers/ID3Handler/ID3Handler.php b/extensions/TimedMediaHandler/handlers/ID3Handler/ID3Handler.php new file mode 100644 index 00000000..1e3ae4fb --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/ID3Handler/ID3Handler.php @@ -0,0 +1,107 @@ +option_tag_id3v1 = false; // Read and process ID3v1 tags + $getID3->option_tag_id3v2 = false; // Read and process ID3v2 tags + $getID3->option_tag_lyrics3 = false; // Read and process Lyrics3 tags + $getID3->option_tag_apetag = false; // Read and process APE tags + $getID3->option_tags_process = false; // Copy tags to root key 'tags' and encode to $this->encoding + $getID3->option_tags_html = false; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities + + // Analyze file to get metadata structure: + $id3 = $getID3->analyze( $path ); + + // remove file paths + unset( $id3['filename'] ); + unset( $id3['filepath'] ); + unset( $id3['filenamepath']); + + // Update the version + $id3['version'] = self::METADATA_VERSION; + + return $id3; + } + + /** + * @param $file File + * @param $path string + * @return string + */ + function getMetadata( $file, $path ) { + $id3 = $this->getID3( $path ); + return serialize( $id3 ); + } + + /** + * @param $metadata + * @return bool|mixed + */ + function unpackMetadata( $metadata ) { + wfSuppressWarnings(); + $unser = unserialize( $metadata ); + wfRestoreWarnings(); + if ( isset( $unser['version'] ) && $unser['version'] == self::METADATA_VERSION ) { + return $unser; + } else { + return false; + } + } + + /** + * @param $file File + * @return mixed + */ + function getBitrate( $file ){ + $metadata = $this->unpackMetadata( $file->getMetadata() ); + if ( !$metadata || isset( $metadata['error'] ) || !isset( $metadata['bitrate'] ) ) { + return 0; + } else { + return $metadata['bitrate']; + } + } + + /** + * @param $file File + * @return int + */ + function getLength( $file ) { + $metadata = $this->unpackMetadata( $file->getMetadata() ); + if ( !$metadata || isset( $metadata['error'] ) || !isset( $metadata['playtime_seconds'] ) ) { + return 0; + } else { + return $metadata['playtime_seconds']; + } + } + + /** + * @param $file File + * @return bool|int + */ + function getFramerate( $file ){ + $metadata = $this->unpackMetadata( $file->getMetadata() ); + if ( !$metadata || isset( $metadata['error'] ) ) { + return 0; + } else { + // return the frame rate of the first found video stream: + if( isset( $metadata['video'] ) + && isset( $metadata['video']['frame_rate'] ) ) { + return $metadata['video']['frame_rate']; + } + return false; + } + } +} diff --git a/extensions/TimedMediaHandler/handlers/Mp4Handler/Mp4Handler.php b/extensions/TimedMediaHandler/handlers/Mp4Handler/Mp4Handler.php new file mode 100644 index 00000000..897e1017 --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/Mp4Handler/Mp4Handler.php @@ -0,0 +1,139 @@ +getMetadata(); + } + $metadata = $this->unpackMetadata( $metadata ); + if ( isset( $metadata['error'] ) ) { + return false; + } + if( isset( $metadata['video']['resolution_x']) + && + isset( $metadata['video']['resolution_y']) + ){ + return array ( + $metadata['video']['resolution_x'], + $metadata['video']['resolution_y'] + ); + } + return array( false, false ); + } + + /** + * @param $image + * @return string + */ + function getMetadataType( $image ) { + return 'mp4'; + } + /** + * @param $file File + */ + function getWebType( $file ) { + /** + * h.264 profile types: + H.264 Simple baseline profile video (main and extended video compatible) level 3 and Low-Complexity AAC audio in MP4 container: + type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"' + + H.264 Extended profile video (baseline-compatible) level 3 and Low-Complexity AAC audio in MP4 container: + type='video/mp4; codecs="avc1.58A01E, mp4a.40.2"' + + H.264 Main profile video level 3 and Low-Complexity AAC audio in MP4 container + type='video/mp4; codecs="avc1.4D401E, mp4a.40.2"' + + H.264 ‘High’ profile video (incompatible with main, baseline, or extended profiles) level 3 and Low-Complexity AAC audio in MP4 container + type='video/mp4; codecs="avc1.64001E, mp4a.40.2"' + */ + // all h.264 encodes are currently simple profile + return 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'; + } + /** + * @param $file File + * @return array|bool + */ + function getStreamTypes( $file ) { + $streamTypes = array(); + $metadata = self::unpackMetadata( $file->getMetadata() ); + if ( !$metadata || isset( $metadata['error'] ) ) { + return false; + } + if( isset( $metadata['audio'] ) && $metadata['audio']['dataformat'] == 'mp4' ){ + if( isset( $metadata['audio']['codec'] ) + && + strpos( $metadata['audio']['codec'] , 'AAC' ) !== false + ){ + $streamTypes[] = 'AAC'; + } else { + $streamTypes[] = $metadata['audio']['codec']; + } + } + // id3 gives 'V_VP8' for what we call VP8 + if( $metadata['video']['dataformat'] == 'quicktime' ){ + $streamTypes[] = 'h.264'; + } + + return $streamTypes; + } + + /** + * @param $file File + * @return String + */ + function getShortDesc( $file ) { + $streamTypes = $this->getStreamTypes( $file ); + if ( !$streamTypes ) { + return parent::getShortDesc( $file ); + } + return wfMessage( 'timedmedia-mp4-short-video', implode( '/', $streamTypes ) + )->timeperiodParams( + $this->getLength( $file ) + )->text(); + } + + /** + * @param $file File + * @return String + */ + function getLongDesc( $file ) { + $streamTypes = $this->getStreamTypes( $file ); + if ( !$streamTypes ) { + return parent::getLongDesc( $file ); + } + return wfMessage('timedmedia-mp4-long-video', + implode( '/', $streamTypes ) + )->timeperiodParams( + $this->getLength( $file ) + )->bitrateParams( + $this->getBitRate( $file ) + )->numParams( + $file->getWidth(), + $file->getHeight() + )->text(); + + } + +} diff --git a/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg.php b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg.php new file mode 100644 index 00000000..ca281e22 --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg.php @@ -0,0 +1,631 @@ + | +// | Tim Starling | +// +----------------------------------------------------------------------------+ +// | This library is free software; you can redistribute it and/or | +// | modify it under the terms of the GNU Lesser General Public | +// | License as published by the Free Software Foundation; either | +// | version 2.1 of the License, or (at your option) any later version. | +// | | +// | This library 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 | +// | Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public | +// | License along with this library; if not, write to the Free Software | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | +// +----------------------------------------------------------------------------+ + +/** + * @author David Grant , Tim Starling + * @category File + * @copyright David Grant , Tim Starling + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL + * @link http://pear.php.net/package/File_Ogg + * @package File_Ogg + * @version CVS: $Id: Ogg.php,v 1.14 2005/11/19 09:06:30 djg Exp $ + */ + +/** + * @access public + */ +define("OGG_STREAM_VORBIS", 1); +/** + * @access public + */ +define("OGG_STREAM_THEORA", 2); +/** + * @access public + */ +define("OGG_STREAM_SPEEX", 3); +/** + * @access public + */ +define("OGG_STREAM_FLAC", 4); +/** + * @access public + */ +define("OGG_STREAM_OPUS", 5); + +/** + * Capture pattern to determine if a file is an Ogg physical stream. + * + * @access private + */ +define("OGG_CAPTURE_PATTERN", "OggS"); +/** + * Maximum size of an Ogg stream page plus four. This value is specified to allow + * efficient parsing of the physical stream. The extra four is a paranoid measure + * to make sure a capture pattern is not split into two parts accidentally. + * + * @access private + */ +define("OGG_MAXIMUM_PAGE_SIZE", 65311); +/** + * Capture pattern for an Ogg Vorbis logical stream. + * + * @access private + */ +define("OGG_STREAM_CAPTURE_VORBIS", "vorbis"); +/** + * Capture pattern for an Ogg Speex logical stream. + * @access private + */ +define("OGG_STREAM_CAPTURE_SPEEX", "Speex "); +/** + * Capture pattern for an Ogg FLAC logical stream. + * + * @access private + */ +define("OGG_STREAM_CAPTURE_FLAC", "FLAC"); +/** + * Capture pattern for an Ogg Theora logical stream. + * + * @access private + */ +define("OGG_STREAM_CAPTURE_THEORA", "theora"); +/** + * Capture pattern for an Ogg Opus logical stream. + * @access private + */ +define("OGG_STREAM_CAPTURE_OPUS", "OpusHead"); +/** + * Error thrown if the file location passed is nonexistant or unreadable. + * + * @access private + */ +define("OGG_ERROR_INVALID_FILE", 1); +/** + * Error thrown if the user attempts to extract an unsupported logical stream. + * + * @access private + */ +define("OGG_ERROR_UNSUPPORTED", 2); +/** + * Error thrown if the user attempts to extract an logical stream with no + * corresponding serial number. + * + * @access private + */ +define("OGG_ERROR_BAD_SERIAL", 3); +/** + * Error thrown if the stream appears to be corrupted. + * + * @access private + */ +define("OGG_ERROR_UNDECODABLE", 4); + + +/** + * Class for parsing a ogg bitstream. + * + * This class provides a means to access several types of logical bitstreams (e.g. Vorbis) + * within a Ogg physical bitstream. + * + * @link http://www.xiph.org/ogg/doc/ + * @package File_Ogg + */ +class File_Ogg +{ + /** + * File pointer to Ogg container. + * + * This is the file pointer used for extracting data from the Ogg stream. It is + * the result of a standard fopen call. + * + * @var pointer + * @access private + */ + var $_filePointer; + + /** + * The container for all logical streams. + * + * List of all of the unique streams in the Ogg physical stream. The key + * used is the unique serial number assigned to the logical stream by the + * encoding application. + * + * @var array + * @access private + */ + var $_streamList = array(); + var $_streams = array(); + + /** + * Length in seconds of each stream group + */ + var $_groupLengths = array(); + + /** + * Total length in seconds of the entire file + */ + var $_totalLength; + var $_startOffset = false; + + /** + * Maximum number of pages to store detailed metadata for, per stream. + * We can't store every page because there could be millions, causing an OOM. + * This must be big enough so that all the codecs can get the metadata they + * need without re-reading the file. + */ + var $_maxPageCacheSize = 4; + + /** + * Returns an interface to an Ogg physical stream. + * + * This method takes the path to a local file and examines it for a physical + * ogg bitsream. After instantiation, the user should query the object for + * the logical bitstreams held within the ogg container. + * + * @access public + * @param string $fileLocation The path of the file to be examined. + */ + function __construct($fileLocation) + { + clearstatcache(); + if (! file_exists($fileLocation)) { + throw new OggException("Couldn't Open File. Check File Path.", OGG_ERROR_INVALID_FILE); + } + + // Open this file as a binary, and split the file into streams. + $this->_filePointer = fopen($fileLocation, "rb"); + if (!is_resource($this->_filePointer)) + throw new OggException("Couldn't Open File. Check File Permissions.", OGG_ERROR_INVALID_FILE); + + // Check for a stream at the start + $magic = fread($this->_filePointer, strlen(OGG_CAPTURE_PATTERN)); + if ($magic !== OGG_CAPTURE_PATTERN) { + throw new OggException("Couldn't read file: Incorrect magic number.", OGG_ERROR_UNDECODABLE); + } + fseek($this->_filePointer, 0, SEEK_SET); + + $this->_splitStreams(); + fclose($this->_filePointer); + } + + /** + * Little-endian equivalent for bin2hex + * @static + */ + static function _littleEndianBin2Hex( $bin ) { + $bigEndian = bin2hex( $bin ); + // Reverse entire string + $reversed = strrev( $bigEndian ); + // Swap nibbles back + for ( $i = 0; $i < strlen( $bigEndian ); $i += 2 ) { + $temp = $reversed[$i]; + $reversed[$i] = $reversed[$i+1]; + $reversed[$i+1] = $temp; + } + return $reversed; + } + + + /** + * Read a binary structure from a file. An array of unsigned integers are read. + * Large integers are upgraded to floating point on overflow. + * + * Format is big-endian as per Theora bit packing convention, this function + * won't work for Vorbis. + * + * @param resource $file + * @param array $fields Associative array mapping name to length in bits + */ + static function _readBigEndian($file, $fields) + { + $bufferLength = ceil(array_sum($fields) / 8); + $buffer = fread($file, $bufferLength); + if (strlen($buffer) != $bufferLength) { + throw new OggException('Unexpected end of file', OGG_ERROR_UNDECODABLE); + } + $bytePos = 0; + $bitPos = 0; + $byteValue = ord($buffer[0]); + $output = array(); + foreach ($fields as $name => $width) { + if ($width % 8 == 0 && $bitPos == 0) { + // Byte aligned case + $bytes = $width / 8; + $endBytePos = $bytePos + $bytes; + $value = 0; + while ($bytePos < $endBytePos) { + $value = ($value * 256) + ord($buffer[$bytePos]); + $bytePos++; + } + if ($bytePos < strlen($buffer)) { + $byteValue = ord($buffer[$bytePos]); + } + } else { + // General case + $bitsRemaining = $width; + $value = 0; + while ($bitsRemaining > 0) { + $bitsToRead = min($bitsRemaining, 8 - $bitPos); + $byteValue <<= $bitsToRead; + $overflow = ($byteValue & 0xff00) >> 8; + $byteValue &= $byteValue & 0xff; + + $bitPos += $bitsToRead; + $bitsRemaining -= $bitsToRead; + $value += $overflow * pow(2, $bitsRemaining); + + if ($bitPos >= 8) { + $bitPos = 0; + $bytePos++; + if ($bitsRemaining <= 0) { + break; + } + $byteValue = ord($buffer[$bytePos]); + } + } + } + $output[$name] = $value; + assert($bytePos <= $bufferLength); + } + return $output; + } + + /** + * Read a binary structure from a file. An array of unsigned integers are read. + * Large integers are upgraded to floating point on overflow. + * + * Format is little-endian as per Vorbis bit packing convention. + * + * @param resource $file + * @param array $fields Associative array mapping name to length in bits + */ + static function _readLittleEndian( $file, $fields ) { + $bufferLength = ceil(array_sum($fields) / 8); + $buffer = fread($file, $bufferLength); + if (strlen($buffer) != $bufferLength) { + throw new OggException('Unexpected end of file', OGG_ERROR_UNDECODABLE); + } + + $bytePos = 0; + $bitPos = 0; + $byteValue = ord($buffer[0]) << 8; + $output = array(); + foreach ($fields as $name => $width) { + if ($width % 8 == 0 && $bitPos == 0) { + // Byte aligned case + $bytes = $width / 8; + $value = 0; + for ($i = 0; $i < $bytes; $i++, $bytePos++) { + $value += pow(256, $i) * ord($buffer[$bytePos]); + } + if ($bytePos < strlen($buffer)) { + $byteValue = ord($buffer[$bytePos]) << 8; + } + } else { + // General case + $bitsRemaining = $width; + $value = 0; + while ($bitsRemaining > 0) { + $bitsToRead = min($bitsRemaining, 8 - $bitPos); + $byteValue >>= $bitsToRead; + $overflow = ($byteValue & 0xff) >> (8 - $bitsToRead); + $byteValue &= 0xff00; + + $value += $overflow * pow(2, $width - $bitsRemaining); + $bitPos += $bitsToRead; + $bitsRemaining -= $bitsToRead; + + if ($bitPos >= 8) { + $bitPos = 0; + $bytePos++; + if ($bitsRemaining <= 0) { + break; + } + $byteValue = ord($buffer[$bytePos]) << 8; + } + } + } + $output[$name] = $value; + assert($bytePos <= $bufferLength); + } + return $output; + } + + + /** + * @access private + */ + function _decodePageHeader($pageData, $pageOffset, $groupId) + { + // Extract the various bits and pieces found in each packet header. + if (substr($pageData, 0, 4) != OGG_CAPTURE_PATTERN) + return (false); + + $stream_version = unpack("C1data", substr($pageData, 4, 1)); + if ($stream_version['data'] != 0x00) + return (false); + + $header_flag = unpack("Cdata", substr($pageData, 5, 1)); + + // Exact granule position + $abs_granule_pos = self::_littleEndianBin2Hex( substr($pageData, 6, 8)); + + // Approximate (floating point) granule position + $pos = unpack("Va/Vb", substr($pageData, 6, 8)); + $approx_granule_pos = $pos['a'] + $pos['b'] * pow(2, 32); + + // Serial number for the current datastream. + $stream_serial = unpack("Vdata", substr($pageData, 14, 4)); + $page_sequence = unpack("Vdata", substr($pageData, 18, 4)); + $checksum = unpack("Vdata", substr($pageData, 22, 4)); + $page_segments = unpack("Cdata", substr($pageData, 26, 1)); + $segments_total = 0; + for ($i = 0; $i < $page_segments['data']; ++$i) { + $segment_length = unpack("Cdata", substr($pageData, 26 + ($i + 1), 1)); + $segments_total += $segment_length['data']; + } + $pageFinish = $pageOffset + 27 + $page_segments['data'] + $segments_total; + $page = array( + 'stream_version' => $stream_version['data'], + 'header_flag' => $header_flag['data'], + 'abs_granule_pos' => $abs_granule_pos, + 'approx_granule_pos' => $approx_granule_pos, + 'checksum' => sprintf("%u", $checksum['data']), + 'segments' => $page_segments['data'], + 'head_offset' => $pageOffset, + 'body_offset' => $pageOffset + 27 + $page_segments['data'], + 'body_finish' => $pageFinish, + 'data_length' => $pageFinish - $pageOffset, + 'group' => $groupId, + ); + if ( !isset( $this->_streamList[$stream_serial['data']] ) ) { + $this->_streamList[$stream_serial['data']] = array( + 'pages' => array(), + 'data_length' => 0, + 'first_granule_pos' => null, + 'last_granule_pos' => null, + ); + } + $stream =& $this->_streamList[$stream_serial['data']]; + if ( count( $stream['pages'] ) < $this->_maxPageCacheSize ) { + $stream['pages'][$page_sequence['data']] = $page; + } + $stream['last_page'] = $page; + $stream['data_length'] += $page['data_length']; + + # Reject -1 as a granule pos, that means no segment finished in the packet + if ( $abs_granule_pos !== 'ffffffffffffffff' ) { + if ( $stream['first_granule_pos'] === null ) { + $stream['first_granule_pos'] = $abs_granule_pos; + } + $stream['last_granule_pos'] = $abs_granule_pos; + } + + $pageData = null; + return $page; + } + + /** + * @access private + */ + function _splitStreams() + { + // Loop through the physical stream until there are no more pages to read. + $groupId = 0; + $openStreams = 0; + $this_page_offset = 0; + while (!feof($this->_filePointer)) { + $pageData = fread($this->_filePointer, 282); + if (strval($pageData) === '') { + break; + } + $page = $this->_decodePageHeader($pageData, $this_page_offset, $groupId); + if ($page === false) { + throw new OggException("Cannot decode Ogg file: Invalid page at offset $this_page_offset", OGG_ERROR_UNDECODABLE); + } + + // Keep track of multiplexed groups + if ($page['header_flag'] & 2/*bos*/) { + $openStreams++; + } elseif ($page['header_flag'] & 4/*eos*/) { + $openStreams--; + if (!$openStreams) { + // End of group + $groupId++; + } + } + if ($openStreams < 0) { + throw new OggException("Unexpected end of stream", OGG_ERROR_UNDECODABLE); + } + + $this_page_offset = $page['body_finish']; + fseek( $this->_filePointer, $this_page_offset, SEEK_SET ); + } + // Loop through the streams, and find out what type of stream is available. + $groupLengths = array(); + foreach ($this->_streamList as $stream_serial => $streamData) { + fseek($this->_filePointer, $streamData['pages'][0]['body_offset'], SEEK_SET); + $pattern = fread($this->_filePointer, 8); + if (preg_match("/" . OGG_STREAM_CAPTURE_VORBIS . "/", $pattern)) { + $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_VORBIS; + $stream = new File_Ogg_Vorbis($stream_serial, $streamData, $this->_filePointer); + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_SPEEX . "/", $pattern)) { + $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_SPEEX; + $stream = new File_Ogg_Speex($stream_serial, $streamData, $this->_filePointer); + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_FLAC . "/", $pattern)) { + $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_FLAC; + $stream = new File_Ogg_Flac($stream_serial, $streamData, $this->_filePointer); + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_THEORA . "/", $pattern)) { + $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_THEORA; + $stream = new File_Ogg_Theora($stream_serial, $streamData, $this->_filePointer); + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_OPUS . "/", $pattern)) { + $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_OPUS; + $stream = new File_Ogg_Opus($stream_serial, $streamData, $this->_filePointer); + } else { + $streamData['stream_type'] = "unknown"; + $stream = false; + } + + if ($stream) { + $this->_streams[$stream_serial] = $stream; + $group = $streamData['pages'][0]['group']; + if (isset($groupLengths[$group])) { + $groupLengths[$group] = max($groupLengths[$group], $stream->getLength()); + } else { + $groupLengths[$group] = $stream->getLength(); + } + //just store the startOffset for the first stream: + if( $this->_startOffset === false ){ + $this->_startOffset = $stream->getStartOffset(); + } + + } + } + $this->_groupLengths = $groupLengths; + $this->_totalLength = array_sum( $groupLengths ); + unset($this->_streamList); + } + + /** + * Returns the overead percentage used by the Ogg headers. + * + * This function returns the percentage of the total stream size + * used for Ogg headers. + * + * @return float + */ + function getOverhead() { + $header_size = 0; + $stream_size = 0; + foreach ($this->_streams as $serial => $stream) { + foreach ($stream->_streamList as $offset => $stream_data) { + $header_size += $stream_data['body_offset'] - $stream_data['head_offset']; + $stream_size = $stream_data['body_finish']; + } + } + return sprintf("%0.2f", ($header_size / $stream_size) * 100); + } + + /** + * Returns the appropriate logical bitstream that corresponds to the provided serial. + * + * This function returns a logical bitstream contained within the Ogg physical + * stream, corresponding to the serial used as the offset for that bitstream. + * The returned stream may be Vorbis, Speex, FLAC or Theora, although the only + * usable bitstream is Vorbis. + * + * @return File_Ogg_Bitstream + */ + function &getStream($streamSerial) + { + if (! array_key_exists($streamSerial, $this->_streams)) + throw new OggException("The stream number is invalid.", OGG_ERROR_BAD_SERIAL); + + return $this->_streams[$streamSerial]; + } + + /** + * This function returns true if a logical bitstream of the requested type can be found. + * + * This function checks the contents of this ogg physical bitstream for of logical + * bitstream corresponding to the supplied type. If one is found, the function returns + * true, otherwise it return false. + * + * @param int $streamType + * @return boolean + */ + function hasStream($streamType) + { + foreach ($this->_streams as $stream) { + if ($stream['stream_type'] == $streamType) + return (true); + } + return (false); + } + + /** + * Returns an array of logical streams inside this physical bitstream. + * + * This function returns an array of logical streams found within this physical + * bitstream. If a filter is provided, only logical streams of the requested type + * are returned, as an array of serial numbers. If no filter is provided, this + * function returns a two-dimensional array, with the stream type as the primary key, + * and a value consisting of an array of stream serial numbers. + * + * @param int $filter + * @return array + */ + function listStreams($filter = null) + { + $streams = array(); + // Loops through the streams and assign them to an appropriate index, + // ready for filtering the second part of this function. + foreach ($this->_streams as $serial => $stream) { + $stream_type = 0; + switch (get_class($stream)) { + case "file_ogg_flac": + $stream_type = OGG_STREAM_FLAC; + break; + case "file_ogg_speex": + $stream_type = OGG_STREAM_SPEEX; + break; + case "file_ogg_theora": + $stream_type = OGG_STREAM_THEORA; + break; + case "file_ogg_vorbis": + $stream_type = OGG_STREAM_VORBIS; + break; + } + if (! isset($streams[$stream_type])) + // Initialise the result list for this stream type. + $streams[$stream_type] = array(); + + $streams[$stream_type][] = $serial; + } + + // Perform filtering. + if (is_null($filter)) + return ($streams); + elseif (isset($streams[$filter])) + return ($streams[$filter]); + else + return array(); + } + /** + * getStartOffset + * + * @return unknown + */ + function getStartOffset(){ + if( $this->_startOffset === false) + return 0; + return $this->_startOffset; + } + /** + * Get the total length of the group of streams + */ + function getLength() { + return $this->_totalLength; + } +} +?> diff --git a/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Bitstream.php b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Bitstream.php new file mode 100644 index 00000000..1a462232 --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Bitstream.php @@ -0,0 +1,125 @@ + | +// | Tim Starling | +// +----------------------------------------------------------------------------+ +// | This library is free software; you can redistribute it and/or | +// | modify it under the terms of the GNU Lesser General Public | +// | License as published by the Free Software Foundation; either | +// | version 2.1 of the License, or (at your option) any later version. | +// | | +// | This library 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 | +// | Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public | +// | License along with this library; if not, write to the Free Software | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | +// +----------------------------------------------------------------------------+ + + +/** + * @author David Grant , Tim Starling + * @category File + * @copyright David Grant , Tim Starling + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL + * @link http://pear.php.net/package/File_Ogg + * @package File_Ogg + * @version CVS: $Id: Bitstream.php,v 1.3 2005/11/08 19:36:18 djg Exp $ + */ +class File_Ogg_Bitstream +{ + /** + * The serial number of this logical stream. + * + * @var int + * @access private + */ + var $_streamSerial; + /** + * @access private + */ + var $_streamData; + /** + * @access private + */ + var $_filePointer; + /** + * The number of bits used in this stream. + * + * @var int + * @access private + */ + var $_streamSize; + + /** + * The last granule position in the stream + * @var int + * @access private + */ + var $_lastGranulePos; + + /** + * Constructor for a generic logical stream. + * + * @param int $streamSerial Serial number of the logical stream. + * @param array $streamData Data for the requested logical stream. + * @param string $filePath Location of a file on the filesystem. + * @param pointer $filePointer File pointer for the current physical stream. + * @access private + */ + function __construct($streamSerial, $streamData, $filePointer) + { + $this->_streamSerial = $streamSerial; + $this->_streamData = $streamData; + $this->_filePointer = $filePointer; + $this->_firstGranulePos = $streamData['first_granule_pos']; + $this->_lastGranulePos = $streamData['last_granule_pos']; + $this->_streamSize = $streamData['data_length']; + $this->_group = $streamData['pages'][0]['group']; + } + + /** + * Gives the serial number of this stream. + * + * The stream serial number is of fairly academic importance, as it makes little + * difference to the end user. The serial number is used by the Ogg physical + * stream to distinguish between concurrent logical streams. + * + * @return int + * @access public + */ + function getSerial() + { + return ($this->_streamSerial); + } + + /** + * Gives the size (in bits) of this stream. + * + * This function returns the size of the Vorbis stream within the Ogg + * physical stream. + * + * @return int + * @access public + */ + function getSize() + { + return ($this->_streamSize); + } + + /** + * Get the multiplexed group ID + */ + function getGroup() + { + return $this->_group; + } + +} + +?> diff --git a/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Flac.php b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Flac.php new file mode 100644 index 00000000..6838ba68 --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Flac.php @@ -0,0 +1,133 @@ + | +// | Tim Starling | +// +----------------------------------------------------------------------------+ +// | This library is free software; you can redistribute it and/or | +// | modify it under the terms of the GNU Lesser General Public | +// | License as published by the Free Software Foundation; either | +// | version 2.1 of the License, or (at your option) any later version. | +// | | +// | This library 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 | +// | Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public | +// | License along with this library; if not, write to the Free Software | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | +// +----------------------------------------------------------------------------+ + + +/** + * @author David Grant , Tim Starling + * @category File + * @copyright David Grant , Tim Starling + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL + * @link http://pear.php.net/package/File_Ogg + * @link http://flac.sourceforge.net/documentation.html + * @package File_Ogg + * @version CVS: $Id: Flac.php,v 1.9 2005/11/16 20:43:27 djg Exp $ + */ +class File_Ogg_Flac extends File_Ogg_Media +{ + /** + * @access private + */ + function __construct($streamSerial, $streamData, $filePointer) + { + parent::__construct($streamSerial, $streamData, $filePointer); + $this->_decodeHeader(); + $this->_decodeCommentsHeader(); + $this->_streamLength = $this->_streamInfo['total_samples'] + / $this->_streamInfo['sample_rate']; + } + + /** + * Get a short string describing the type of the stream + * @return string + */ + function getType() { + return 'FLAC'; + } + + /** + * @access private + * @param int $packetType + * @param int $pageOffset + */ + function _decodeHeader() + { + fseek($this->_filePointer, $this->_streamData['pages'][0]['body_offset'], SEEK_SET); + // Check if this is the correct header. + $packet = unpack("Cdata", fread($this->_filePointer, 1)); + if ($packet['data'] != 0x7f) + throw new OggException("Stream Undecodable", OGG_ERROR_UNDECODABLE); + + // The following four characters should be "FLAC". + if (fread($this->_filePointer, 4) != 'FLAC') + throw new OggException("Stream is undecodable due to a malformed header.", OGG_ERROR_UNDECODABLE); + + $version = unpack("Cmajor/Cminor", fread($this->_filePointer, 2)); + $this->_version = "{$version['major']}.{$version['minor']}"; + if ($version['major'] > 1) { + throw new OggException("Cannot decode a version {$version['major']} FLAC stream", OGG_ERROR_UNDECODABLE); + } + $h = File_Ogg::_readBigEndian( $this->_filePointer, + array( + // Ogg-specific + 'num_headers' => 16, + 'flac_native_sig' => 32, + // METADATA_BLOCK_HEADER + 'is_last' => 1, + 'type' => 7, + 'length' => 24, + )); + + // METADATA_BLOCK_STREAMINFO + // The variable names are canonical, and come from the FLAC source (format.h) + $this->_streamInfo = File_Ogg::_readBigEndian( $this->_filePointer, + array( + 'min_blocksize' => 16, + 'max_blocksize' => 16, + 'min_framesize' => 24, + 'max_framesize' => 24, + 'sample_rate' => 20, + 'channels' => 3, + 'bits_per_sample' => 5, + 'total_samples' => 36, + )); + $this->_streamInfo['md5sum'] = bin2hex(fread($this->_filePointer, 16)); + } + + /** + * Get an associative array containing header information about the stream + * @access public + * @return array + */ + function getHeader() + { + return $this->_streamInfo; + } + + function _decodeCommentsHeader() + { + fseek($this->_filePointer, $this->_streamData['pages'][1]['body_offset'], SEEK_SET); + $blockHeader = File_Ogg::_readBigEndian( $this->_filePointer, + array( + 'last_block' => 1, + 'block_type' => 7, + 'length' => 24 + ) + ); + if ($blockHeader['block_type'] != 4) { + throw new OggException("Stream Undecodable", OGG_ERROR_UNDECODABLE); + } + + $this->_decodeBareCommentsHeader(); + } +} +?> diff --git a/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Media.php b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Media.php new file mode 100644 index 00000000..67ddaece --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Media.php @@ -0,0 +1,262 @@ + | +// | Tim Starling | +// +----------------------------------------------------------------------------+ +// | This library is free software; you can redistribute it and/or | +// | modify it under the terms of the GNU Lesser General Public | +// | License as published by the Free Software Foundation; either | +// | version 2.1 of the License, or (at your option) any later version. | +// | | +// | This library 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 | +// | Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public | +// | License along with this library; if not, write to the Free Software | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | +// +----------------------------------------------------------------------------+ + + +/** + * Parent class for media bitstreams + * Contains some functions common to various media formats + */ +abstract class File_Ogg_Media extends File_Ogg_Bitstream +{ + /** + * Maximum size of header comment to parse. + * Set to 1 MB by default. Make sure this is less than your PHP memory_limit. + */ + const COMMENT_MAX_SIZE = 1000000; + + /** + * Array to hold each of the comments. + * + * @access private + * @var array + */ + var $_comments = array(); + + /** + * Vendor string for the stream. + * + * @access private + * @var string + */ + var $_vendor; + + /** + * Length of the stream in seconds + */ + var $_streamLength; + + /* Start offset of the stream in seconds */ + var $_startOffset = 0; + + /** + * Get a short string describing the type of the stream + * @return string + */ + abstract function getType(); + + /** + * Get the 6-byte identification string expected in the common header + * @return string + */ + function getIdentificationString() + { + return ''; + } + + /** + * @access private + * @param int $packetType + * @param int $pageOffset + */ + function _decodeCommonHeader($packetType, $pageOffset) + { + fseek($this->_filePointer, $this->_streamData['pages'][$pageOffset]['body_offset'], SEEK_SET); + if ($packetType !== false) { + // Check if this is the correct header. + $packet = unpack("Cdata", fread($this->_filePointer, 1)); + if ($packet['data'] != $packetType) + throw new OggException("Stream Undecodable", OGG_ERROR_UNDECODABLE); + + // The following six characters should be equal to getIdentificationString() + $id = $this->getIdentificationString(); + if ($id !== '' && fread($this->_filePointer, strlen($id)) !== $id) + throw new OggException("Stream is undecodable due to a malformed header.", OGG_ERROR_UNDECODABLE); + } // else seek only, no common header + } + + /** + * Parse a Vorbis-style comments header. + * + * This function parses the comments header. The comments header contains a series of + * UTF-8 comments related to the audio encoded in the stream. This header also contains + * a string to identify the encoding software. More details on the comments header can + * be found at the following location: http://xiph.org/vorbis/doc/v-comment.html + * + * @access private + */ + function _decodeBareCommentsHeader() + { + // Decode the vendor string length as a 32-bit unsigned integer. + $vendor_len = unpack("Vdata", fread($this->_filePointer, 4)); + if ( $vendor_len['data'] > 0 ) { + // Retrieve the vendor string from the stream. + $this->_vendor = fread($this->_filePointer, $vendor_len['data']); + } else { + $this->_vendor = ''; + } + // Decode the size of the comments list as a 32-bit unsigned integer. + $comment_list_length = unpack("Vdata", fread($this->_filePointer, 4)); + // Iterate through the comments list. + for ($i = 0; $i < $comment_list_length['data']; ++$i) { + // Unpack the length of this comment. + $comment_length = unpack("Vdata", fread($this->_filePointer, 4)); + + // If the comment length is greater than specified limit, skip it. + if ( $comment_length['data'] > self::COMMENT_MAX_SIZE ) { + continue; + } + + // Comments are in the format 'ARTIST=Super Furry Animals', so split it on the equals character. + // NOTE: Equals characters are strictly prohibited in either the COMMENT or DATA parts. + $comment = explode("=", fread($this->_filePointer, $comment_length['data'])); + $comment_title = (string) $comment[0]; + $comment_value = (string) $comment[1]; + + // Check if the comment type (e.g. ARTIST) already exists. If it does, + // take the new value, and the existing value (or array) and insert it + // into a new array. This is important, since each comment type may have + // multiple instances (e.g. ARTIST for a collaboration) and we should not + // overwrite the previous value. + if (isset($this->_comments[$comment_title])) { + if (is_array($this->_comments[$comment_title])) + $this->_comments[$comment_title][] = $comment_value; + else + $this->_comments[$comment_title] = array($this->_comments[$comment_title], $comment_value); + } else + $this->_comments[$comment_title] = $comment_value; + } + } + + /** + * Number of channels used in this stream + * + * This function returns the number of channels used in this stream. This + * can range from 1 to 255, but will likely be 2 (stereo) or 1 (mono). + * + * @access public + * @return int + * @see File_Ogg_Vorbis::isMono() + * @see File_Ogg_Vorbis::isStereo() + * @see File_Ogg_Vorbis::isQuadrophonic() + */ + function getChannels() + { + return ($this->_channels); + } + + /** + * Provides a list of the comments extracted from the Vorbis stream. + * + * It is recommended that the user fully inspect the array returned by this function + * rather than blindly requesting a comment in false belief that it will always + * be present. Whilst the Vorbis specification dictates a number of popular + * comments (e.g. TITLE, ARTIST, etc.) for use in Vorbis streams, they are not + * guaranteed to appear. + * + * @access public + * @return array + */ + function getCommentList() + { + return (array_keys($this->_comments)); + } + + /** + * Provides an interface to the numerous comments located with a Vorbis stream. + * + * A Vorbis stream may contain one or more instances of each comment, so the user + * should check the variable type before printing out the result of this method. + * The situation in which multiple instances of a comment occurring are not as + * rare as one might think, since they are conceivable at least for ARTIST comments + * in the situation where a track is a duet. + * + * @access public + * @param string $commentTitle Comment title to search for, e.g. TITLE. + * @param string $separator String to separate multiple values. + * @return string + */ + function getField($commentTitle, $separator = ", ") + { + if (isset($this->_comments[$commentTitle])) { + if (is_array($this->_comments[$commentTitle])) + return (implode($separator, $this->_comments[$commentTitle])); + else + return ($this->_comments[$commentTitle]); + } else + // The comment doesn't exist in this file. The user should've called getCommentList first. + return (""); + } + + /** + * Get the entire comments array. + * May return an empty array if the bitstream does not support comments. + * + * @access public + * @return array + */ + function getComments() { + return $this->_comments; + } + + /** + * Vendor of software used to encode this stream. + * + * Gives the vendor string for the software used to encode this stream. + * It is common to find libVorbis here. The majority of encoders appear + * to use libvorbis from Xiph.org. + * + * @access public + * @return string + */ + function getVendor() + { + return ($this->_vendor); + } + + /** + * Get an associative array containing header information about the stream + * @access public + * @return array + */ + function getHeader() + { + return array(); + } + + /** + * Get the length of the stream in seconds + * @return float + */ + function getLength() + { + return $this->_streamLength; + } + /** + * Get the start offset of the stream in seconds + * + * @return float + */ + function getStartOffset(){ + return $this->_startOffset; + } +} diff --git a/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Opus.php b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Opus.php new file mode 100644 index 00000000..2c23d928 --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Opus.php @@ -0,0 +1,126 @@ + | +// +----------------------------------------------------------------------------+ +// | This library is free software; you can redistribute it and/or | +// | modify it under the terms of the GNU Lesser General Public | +// | License as published by the Free Software Foundation; either | +// | version 2.1 of the License, or (at your option) any later version. | +// | | +// | This library 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 | +// | Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public | +// | License along with this library; if not, write to the Free Software | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | +// +----------------------------------------------------------------------------+ + + +define( 'OGG_OPUS_COMMENTS_PAGE_OFFSET', 1 ); + +/** + * @author Jan Gerber + * @category File + * @copyright Jan Gerber + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL + * @link http://pear.php.net/package/File_Ogg + * @link http://www.opus-codec.org/ + * @package File_Ogg + * @version 1 + */ +class File_Ogg_Opus extends File_Ogg_Media +{ + /** + * @access private + */ + function __construct($streamSerial, $streamData, $filePointer) + { + parent::__construct($streamSerial, $streamData, $filePointer); + $this->_decodeHeader(); + $this->_decodeCommentsHeader(); + + $endSec = $this->getSecondsFromGranulePos( $this->_lastGranulePos ); + $startSec = $this->getSecondsFromGranulePos( $this->_firstGranulePos ); + + if( $startSec > 1){ + $this->_streamLength = $endSec - $startSec; + $this->_startOffset = $startSec; + }else{ + $this->_streamLength = $endSec; + } + $this->_avgBitrate = $this->_streamLength ? ($this->_streamSize * 8) / $this->_streamLength : 0; + } + + function getSecondsFromGranulePos( $granulePos ){ + return (( '0x' . substr( $granulePos, 0, 8 ) ) * pow(2, 32) + + ( '0x' . substr( $granulePos, 8, 8 ) ) + - $this->_header['pre_skip']) + / 48000; + } + + /** + * Get a short string describing the type of the stream + * @return string + */ + function getType() + { + return 'Opus'; + } + + /** + * Decode the stream header + * @access private + */ + function _decodeHeader() + { + fseek($this->_filePointer, $this->_streamData['pages'][0]['body_offset'], SEEK_SET); + // The first 8 characters should be "OpusHead". + if (fread($this->_filePointer, 8) != 'OpusHead') + throw new OggException("Stream is undecodable due to a malformed header.", OGG_ERROR_UNDECODABLE); + + $this->_header = File_Ogg::_readLittleEndian($this->_filePointer, array( + 'opus_version' => 8, + 'nb_channels' => 8, + 'pre_skip' => 16, + 'audio_sample_rate' => 32, + 'output_gain' => 16, + 'channel_mapping_family'=> 8, + )); + $this->_channels = $this->_header['nb_channels']; + } + + /** + * Get an associative array containing header information about the stream + * @access public + * @return array + */ + function getHeader() { + return $this->_header; + } + + function getSampleRate() + { + //Opus always outputs 48kHz, the header only lists + //the samplerate of the source as reference + return 48000; + } + + /** + * Decode the comments header + * @access private + */ + function _decodeCommentsHeader() + { + $id = 'OpusTags'; + $this->_decodeCommonHeader(false, OGG_OPUS_COMMENTS_PAGE_OFFSET); + if(fread($this->_filePointer, strlen($id)) !== $id) + throw new OggException("Stream is undecodable due to a malformed header.", OGG_ERROR_UNDECODABLE); + $this->_decodeBareCommentsHeader(); + } +} +?> diff --git a/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Speex.php b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Speex.php new file mode 100644 index 00000000..42f9b0eb --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Speex.php @@ -0,0 +1,122 @@ + | +// | Tim Starling | +// +----------------------------------------------------------------------------+ +// | This library is free software; you can redistribute it and/or | +// | modify it under the terms of the GNU Lesser General Public | +// | License as published by the Free Software Foundation; either | +// | version 2.1 of the License, or (at your option) any later version. | +// | | +// | This library 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 | +// | Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public | +// | License along with this library; if not, write to the Free Software | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | +// +----------------------------------------------------------------------------+ + + +/** + * @author David Grant , Tim Starling + * @category File + * @copyright David Grant , Tim Starling + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL + * @link http://pear.php.net/package/File_Ogg + * @link http://www.speex.org/docs.html + * @package File_Ogg + * @version CVS: $Id: Speex.php,v 1.10 2005/11/16 20:43:27 djg Exp $ + */ +class File_Ogg_Speex extends File_Ogg_Media +{ + /** + * @access private + */ + function __construct($streamSerial, $streamData, $filePointer) + { + parent::__construct($streamSerial, $streamData, $filePointer); + $this->_decodeHeader(); + $this->_decodeCommentsHeader(); + $endSec = + (( '0x' . substr( $this->_lastGranulePos, 0, 8 ) ) * pow(2, 32) + + ( '0x' . substr( $this->_lastGranulePos, 8, 8 ) )) + / $this->_header['rate']; + + $startSec = + (( '0x' . substr( $this->_firstGranulePos, 0, 8 ) ) * pow(2, 32) + + ( '0x' . substr( $this->_firstGranulePos, 8, 8 ) )) + / $this->_header['rate']; + + //make sure the offset is worth taking into account oggz_chop related hack + if( $startSec > 1){ + $this->_streamLength = $endSec - $startSec; + $this->_startOffset = $startSec; + }else{ + $this->_streamLength = $endSec; + } + } + + /** + * Get a short string describing the type of the stream + * @return string + */ + function getType() + { + return 'Speex'; + } + + /** + * Decode the stream header + * @access private + */ + function _decodeHeader() + { + fseek($this->_filePointer, $this->_streamData['pages'][0]['body_offset'], SEEK_SET); + // The first 8 characters should be "Speex ". + if (fread($this->_filePointer, 8) != 'Speex ') + throw new OggException("Stream is undecodable due to a malformed header.", OGG_ERROR_UNDECODABLE); + + $this->_version = fread($this->_filePointer, 20); + $this->_header = File_Ogg::_readLittleEndian($this->_filePointer, array( + 'speex_version_id' => 32, + 'header_size' => 32, + 'rate' => 32, + 'mode' => 32, + 'mode_bitstream_version'=> 32, + 'nb_channels' => 32, + 'bitrate' => 32, + 'frame_size' => 32, + 'vbr' => 32, + 'frames_per_packet' => 32, + 'extra_headers' => 32, + 'reserved1' => 32, + 'reserved2' => 32 + )); + $this->_header['speex_version'] = $this->_version; + } + + /** + * Get an associative array containing header information about the stream + * @access public + * @return array + */ + function getHeader() { + return $this->_header; + } + + /** + * Decode the comments header + * @access private + */ + function _decodeCommentsHeader() + { + fseek($this->_filePointer, $this->_streamData['pages'][1]['body_offset'], SEEK_SET); + $this->_decodeBareCommentsHeader(); + } +} +?> diff --git a/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Theora.php b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Theora.php new file mode 100644 index 00000000..b801ea26 --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Theora.php @@ -0,0 +1,240 @@ + | +// | Tim Starling | +// +----------------------------------------------------------------------------+ +// | This library is free software; you can redistribute it and/or | +// | modify it under the terms of the GNU Lesser General Public | +// | License as published by the Free Software Foundation; either | +// | version 2.1 of the License, or (at your option) any later version. | +// | | +// | This library 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 | +// | Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public | +// | License along with this library; if not, write to the Free Software | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | +// +----------------------------------------------------------------------------+ + + +define( 'OGG_THEORA_IDENTIFICATION_HEADER', 0x80 ); +define( 'OGG_THEORA_COMMENTS_HEADER', 0x81 ); +define( 'OGG_THEORA_IDENTIFICATION_PAGE_OFFSET', 0 ); +define( 'OGG_THEORA_COMMENTS_PAGE_OFFSET', 1 ); + + +/** + * @author David Grant , Tim Starling + * @category File + * @copyright David Grant , Tim Starling + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL + * @link http://pear.php.net/package/File_Ogg + * @link http://www.xiph.org/theora/ + * @package File_Ogg + * @version CVS: $Id: Theora.php,v 1.9 2005/11/16 20:43:27 djg Exp $ + */ +class File_Ogg_Theora extends File_Ogg_Media +{ + /** + * @access private + */ + function __construct($streamSerial, $streamData, $filePointer) + { + parent::__construct($streamSerial, $streamData, $filePointer); + $this->_decodeIdentificationHeader(); + $this->_decodeCommentsHeader(); + $endSec = $this->getSecondsFromGranulePos( $this->_lastGranulePos ); + + $startSec = $this->getSecondsFromGranulePos( $this->_firstGranulePos ); + + //make sure the offset is worth taking into account oggz_chop related hack + if( $startSec > 1){ + $this->_streamLength = $endSec - $startSec; + $this->_startOffset = $startSec; + }else{ + $this->_streamLength = $endSec; + } + + $this->_avgBitrate = $this->_streamLength ? ($this->_streamSize * 8) / $this->_streamLength : 0; + } + function getSecondsFromGranulePos($granulePos){ + // Calculate GranulePos seconds + // First make some "numeric strings" + // These might not fit into PHP's integer type, but they will fit into + // the 53-bit mantissa of a double-precision number + $topWord = floatval( base_convert( substr( $granulePos, 0, 8 ), 16, 10 ) ); + $bottomWord = floatval( base_convert( substr( $granulePos, 8, 8 ), 16, 10 ) ); + // Calculate the keyframe position by shifting right by KFGSHIFT + // We don't use PHP's shift operators because they're terribly broken + // This is made slightly simpler by the fact that KFGSHIFT < 32 + $keyFramePos = $topWord / pow(2, $this->_kfgShift - 32) + + floor( $bottomWord / pow(2, $this->_kfgShift) ); + // Calculate the frame offset by masking off the top 64-KFGSHIFT bits + // This requires a bit of floating point trickery + $offset = fmod( $bottomWord, pow(2, $this->_kfgShift) ); + // They didn't teach you that one at school did they? + // Now put it together with the frame rate to calculate time in seconds + return ( $keyFramePos + $offset ) / $this->_frameRate; + } + /** + * Get the 6-byte identification string expected in the common header + */ + function getIdentificationString() + { + return OGG_STREAM_CAPTURE_THEORA; + } + + /** + * Parse the identification header in a Theora stream. + * @access private + */ + function _decodeIdentificationHeader() + { + $this->_decodeCommonHeader(OGG_THEORA_IDENTIFICATION_HEADER, OGG_THEORA_IDENTIFICATION_PAGE_OFFSET); + $h = File_Ogg::_readBigEndian( $this->_filePointer, array( + 'VMAJ' => 8, + 'VMIN' => 8, + 'VREV' => 8, + 'FMBW' => 16, + 'FMBH' => 16, + 'PICW' => 24, + 'PICH' => 24, + 'PICX' => 8, + 'PICY' => 8, + 'FRN' => 32, + 'FRD' => 32, + 'PARN' => 24, + 'PARD' => 24, + 'CS' => 8, + 'NOMBR' => 24, + 'QUAL' => 6, + 'KFGSHIFT' => 5, + 'PF' => 2)); + if ( !$h ) { + throw new OggException("Stream is undecodable due to a truncated header.", OGG_ERROR_UNDECODABLE); + } + + // Theora version + // Seems overly strict but this is what the spec says + // VREV is for backwards-compatible changes, apparently + if ( $h['VMAJ'] != 3 || $h['VMIN'] != 2 ) { + throw new OggException("Stream is undecodable due to an invalid theora version.", OGG_ERROR_UNDECODABLE); + } + $this->_theoraVersion = "{$h['VMAJ']}.{$h['VMIN']}.{$h['VREV']}"; + + // Frame height/width + if ( !$h['FMBW'] || !$h['FMBH'] ) { + throw new OggException("Stream is undecodable because it has frame size of zero.", OGG_ERROR_UNDECODABLE); + } + $this->_frameWidth = $h['FMBW'] * 16; + $this->_frameHeight = $h['FMBH'] * 16; + + // Picture height/width + if ( $h['PICW'] > $this->_frameWidth || $h['PICH'] > $this->_frameHeight ) { + throw new OggException("Stream is undecodable because the picture width is greater than the frame width.", OGG_ERROR_UNDECODABLE); + } + $this->_pictureWidth = $h['PICW']; + $this->_pictureHeight = $h['PICH']; + + // Picture offset + $this->_offsetX = $h['PICX']; + $this->_offsetY = $h['PICY']; + // Frame rate + $this->_frameRate = $h['FRD'] == 0 ? 0 : $h['FRN'] / $h['FRD']; + // Physical aspect ratio + if ( !$h['PARN'] || !$h['PARD'] ) { + $this->_physicalAspectRatio = 1; + } else { + $this->_physicalAspectRatio = $h['PARN'] / $h['PARD']; + } + + // Color space + $colorSpaces = array( + 0 => 'Undefined', + 1 => 'Rec. 470M', + 2 => 'Rec. 470BG', + ); + if ( isset( $colorSpaces[$h['CS']] ) ) { + $this->_colorSpace = $colorSpaces[$h['CS']]; + } else { + $this->_colorSpace = 'Unknown (reserved)'; + } + + $this->_nomBitrate = $h['NOMBR']; + + $this->_quality = $h['QUAL']; + $this->_kfgShift = $h['KFGSHIFT']; + + $pixelFormats = array( + 0 => '4:2:0', + 1 => 'Unknown (reserved)', + 2 => '4:2:2', + 3 => '4:4:4', + ); + $this->_pixelFormat = $pixelFormats[$h['PF']]; + + switch ( $h['PF'] ) { + case 0: + $h['NSBS'] = + floor( ($h['FMBW'] + 1) / 2 ) * + floor( ($h['FMBH'] + 1) / 2 ) + 2 * + floor( ($h['FMBW'] + 3) / 4 ) * + floor( ($h['FMBH'] + 3) / 4 ); + $h['NBS'] = 6 * $h['FMBW'] * $h['FMBH']; + break; + case 2: + $h['NSBS'] = + floor( ($h['FMBW'] + 1) / 2 ) * + floor( ($h['FMBH'] + 1) / 2 ) + 2 * + floor( ($h['FMBW'] + 3) / 4 ) * + floor( ($h['FMBH'] + 1) / 2 ); + $h['NBS'] = 8 * $h['FMBW'] * $h['FMBH']; + break; + case 3: + $h['NSBS'] = + 3 * floor( ($h['FMBW'] + 1) / 2 ) * + floor( ($h['FMBH'] + 1) / 2 ); + $h['NBS'] = 12 * $h['FMBW'] * $h['FMBH']; + break; + default: + $h['NSBS'] = $h['NBS'] = 0; + } + $h['NMBS'] = $h['FMBW'] * $h['FMBH']; + + $this->_idHeader = $h; + } + + /** + * Get an associative array containing header information about the stream + * @access public + * @return array + */ + function getHeader() { + return $this->_idHeader; + } + + /** + * Get a short string describing the type of the stream + * @return string + */ + function getType() { + return 'Theora'; + } + + /** + * Decode the comments header + * @access private + */ + function _decodeCommentsHeader() + { + $this->_decodeCommonHeader(OGG_THEORA_COMMENTS_HEADER, OGG_THEORA_COMMENTS_PAGE_OFFSET); + $this->_decodeBareCommentsHeader(); + } + +} +?> diff --git a/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Vorbis.php b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Vorbis.php new file mode 100644 index 00000000..06c2c180 --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/Ogg/Vorbis.php @@ -0,0 +1,790 @@ + | +// | Tim Starling | +// +----------------------------------------------------------------------------+ +// | This library is free software; you can redistribute it and/or | +// | modify it under the terms of the GNU Lesser General Public | +// | License as published by the Free Software Foundation; either | +// | version 2.1 of the License, or (at your option) any later version. | +// | | +// | This library 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 | +// | Lesser General Public License for more details. | +// | | +// | You should have received a copy of the GNU Lesser General Public | +// | License along with this library; if not, write to the Free Software | +// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | +// +----------------------------------------------------------------------------+ + + +/** + * Check number for the first header in a Vorbis stream. + * + * @access private + */ +define("OGG_VORBIS_IDENTIFICATION_HEADER", 1); +/** + * Check number for the second header in a Vorbis stream. + * + * @access private + */ +define("OGG_VORBIS_COMMENTS_HEADER", 3); +/** + * Check number for the third header in a Vorbis stream. + * + * @access private + */ +define("OGG_VORBIS_SETUP_HEADER", 5); +/** + * Error thrown if the stream appears to be corrupted. + * + * @access private + */ +define("OGG_VORBIS_ERROR_UNDECODABLE", OGG_ERROR_UNDECODABLE); +/** + * Error thrown if the user attempts to extract a comment using a comment key + * that does not exist. + * + * @access private + */ +define("OGG_VORBIS_ERROR_INVALID_COMMENT", 2); + +define("OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET", 0); +define("OGG_VORBIS_COMMENTS_PAGE_OFFSET", 1); + +/** + * Error thrown if the user attempts to write a comment containing an illegal + * character + * + * @access private + */ +define("OGG_VORBIS_ERROR_ILLEGAL_COMMENT", 3); + +/** + * Extract the contents of a Vorbis logical stream. + * + * This class provides an interface to a Vorbis logical stream found within + * a Ogg stream. A variety of information may be extracted, including comment + * tags, running time, and bitrate. For more information, please see the following + * links. + * + * @author David Grant , Tim Starling + * @category File + * @copyright David Grant , Tim Starling + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL + * @link http://pear.php.net/package/File_Ogg + * @link http://www.xiph.org/vorbis/doc/ + * @package File_Ogg + * @version CVS: $Id: Vorbis.php,v 1.13 2005/11/19 09:06:32 djg Exp $ + */ +class File_Ogg_Vorbis extends File_Ogg_Media +{ + + /** + * Version of vorbis specification used. + * + * @access private + * @var int + */ + var $_version; + + /** + * Number of channels in the vorbis stream. + * + * @access private + * @var int + */ + var $_channels; + + /** + * Number of samples per second in the vorbis stream. + * + * @access private + * @var int + */ + var $_sampleRate; + + /** + * Minimum bitrate for the vorbis stream. + * + * @access private + * @var int + */ + var $_minBitrate; + + /** + * Maximum bitrate for the vorbis stream. + * + * @access private + * @var int + */ + var $_maxBitrate; + + /** + * Nominal bitrate for the vorbis stream. + * + * @access private + * @var int + */ + var $_nomBitrate; + + /** + * Average bitrate for the vorbis stream. + * + * @access private + * @var float + */ + var $_avgBitrate; + + /** + * The length of this stream in seconds. + * + * @access private + * @var int + */ + var $_streamLength; + + /** + * the start offset of this stream in seconds + */ + var $_startOffset; + /** + * Constructor for accessing a Vorbis logical stream. + * + * This method is the constructor for the native-PHP interface to a Vorbis logical + * stream, embedded within an Ogg physical stream. + * + * @param int $streamSerial Serial number of the logical stream. + * @param array $streamData Data for the requested logical stream. + * @param string $filePath Location of a file on the filesystem. + * @param pointer $filePointer File pointer for the current physical stream. + * @access private + */ + function __construct($streamSerial, $streamData, $filePointer) + { + parent::__construct($streamSerial, $streamData, $filePointer); + $this->_decodeIdentificationHeader(); + $this->_decodeCommentsHeader(OGG_VORBIS_COMMENTS_HEADER, OGG_VORBIS_COMMENTS_PAGE_OFFSET); + + $endSec = $this->getSecondsFromGranulePos( $this->_lastGranulePos ); + $startSec = $this->getSecondsFromGranulePos( $this->_firstGranulePos ); + + //make sure the offset is worth taking into account oggz_chop related hack + if( $startSec > 1){ + $this->_streamLength = $endSec - $startSec; + $this->_startOffset = $startSec; + }else{ + $this->_streamLength = $endSec; + } + + $this->_avgBitrate = $this->_streamLength ? ($this->_streamSize * 8) / $this->_streamLength : 0; + } + function getSecondsFromGranulePos( $granulePos ){ + return (( '0x' . substr( $granulePos, 0, 8 ) ) * pow(2, 32) + + ( '0x' . substr( $granulePos, 8, 8 ) )) + / $this->_idHeader['audio_sample_rate']; + } + /** + * Get a short string describing the type of the stream + */ + function getType() + { + return 'Vorbis'; + } + + /** + * Parse the identification header (the first of three headers) in a Vorbis stream. + * + * This function parses the identification header. The identification header + * contains simple audio characteristics, such as sample rate and number of + * channels. There are a number of error-checking provisions laid down in the Vorbis + * specification to ensure the stream is pure. + * + * @access private + */ + function _decodeIdentificationHeader() + { + $this->_decodeCommonHeader(OGG_VORBIS_IDENTIFICATION_HEADER, OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET); + + $h = File_Ogg::_readLittleEndian($this->_filePointer, array( + 'vorbis_version' => 32, + 'audio_channels' => 8, + 'audio_sample_rate' => 32, + 'bitrate_maximum' => 32, + 'bitrate_nominal' => 32, + 'bitrate_minimum' => 32, + 'blocksize_0' => 4, + 'blocksize_1' => 4, + 'framing_flag' => 1 + )); + + // The Vorbis stream version must be 0. + if ($h['vorbis_version'] == 0) + $this->_version = $h['vorbis_version']; + else + throw new OggException("Stream is undecodable due to an invalid vorbis stream version.", OGG_VORBIS_ERROR_UNDECODABLE); + + // The number of channels MUST be greater than 0. + if ($h['audio_channels'] == 0) + throw new OggException("Stream is undecodable due to zero channels.", OGG_VORBIS_ERROR_UNDECODABLE); + else + $this->_channels = $h['audio_channels']; + + // The sample rate MUST be greater than 0. + if ($h['audio_sample_rate'] == 0) + throw new OggException("Stream is undecodable due to a zero sample rate.", OGG_VORBIS_ERROR_UNDECODABLE); + else + $this->_sampleRate = $h['audio_sample_rate']; + + // Extract the various bitrates + $this->_maxBitrate = $h['bitrate_maximum']; + $this->_nomBitrate = $h['bitrate_nominal']; + $this->_minBitrate = $h['bitrate_minimum']; + + // Powers of two between 6 and 13 inclusive. + $valid_block_sizes = array(64, 128, 256, 512, 1024, 2048, 4096, 8192); + + // blocksize_0 MUST be a valid blocksize. + $blocksize_0 = pow(2, $h['blocksize_0']); + if (FALSE == in_array($blocksize_0, $valid_block_sizes)) + throw new OggException("Stream is undecodable because blocksize_0 is $blocksize_0, which is not a valid size.", OGG_VORBIS_ERROR_UNDECODABLE); + + // Extract bits 5 to 8 from the character data. + // blocksize_1 MUST be a valid blocksize. + $blocksize_1 = pow(2, $h['blocksize_1']); + if (FALSE == in_array($blocksize_1, $valid_block_sizes)) + throw new OggException("Stream is undecodable because blocksize_1 is not a valid size.", OGG_VORBIS_ERROR_UNDECODABLE); + + // blocksize 0 MUST be less than or equal to blocksize 1. + if ($blocksize_0 > $blocksize_1) + throw new OggException("Stream is undecodable because blocksize_0 is not less than or equal to blocksize_1.", OGG_VORBIS_ERROR_UNDECODABLE); + + // The framing bit MUST be set to mark the end of the identification header. + // Some encoders are broken though -- TS + /* + if ($h['framing_flag'] == 0) + throw new OggException("Stream in undecodable because the framing bit is not non-zero.", OGG_VORBIS_ERROR_UNDECODABLE); + */ + + $this->_idHeader = $h; + } + + /** + * Decode the comments header + * @access private + * @param int $packetType + * @param int $pageOffset + */ + function _decodeCommentsHeader($packetType, $pageOffset) + { + $this->_decodeCommonHeader($packetType, $pageOffset); + $this->_decodeBareCommentsHeader(); + // The framing bit MUST be set to mark the end of the comments header. + $framing_bit = unpack("Cdata", fread($this->_filePointer, 1)); + if ($framing_bit['data'] != 1) + throw new OggException("Stream Undecodable", OGG_VORBIS_ERROR_UNDECODABLE); + } + + /** + * Get the 6-byte identification string expected in the common header + */ + function getIdentificationString() { + return OGG_STREAM_CAPTURE_VORBIS; + } + + /** + * Version of the Vorbis specification referred to in the encoding of this stream. + * + * This method returns the version of the Vorbis specification (currently 0 (ZERO)) + * referred to by the encoder of this stream. The Vorbis specification is well- + * defined, and thus one does not expect this value to change on a frequent basis. + * + * @access public + * @return int + */ + function getEncoderVersion() + { + return ($this->_version); + } + + /** + * Samples per second. + * + * This function returns the number of samples used per second in this + * recording. Probably the most common value here is 44,100. + * + * @return int + * @access public + */ + function getSampleRate() + { + return ($this->_sampleRate); + } + + /** + * Various bitrate measurements + * + * Gives an array of the values of four different types of bitrates for this + * stream. The nominal, maximum and minimum values are found within the file, + * whereas the average value is computed. + * + * @access public + * @return array + */ + function getBitrates() + { + return (array("nom" => $this->_nomBitrate, "max" => $this->_maxBitrate, "min" => $this->_minBitrate, "avg" => $this->_avgBitrate)); + } + + /** + * Gives the most accurate bitrate measurement from this stream. + * + * This function returns the most accurate bitrate measurement for this + * recording, depending on values set in the stream header. + * + * @access public + * @return float + */ + function getBitrate() + { + if ($this->_avgBitrate != 0) + return ($this->_avgBitrate); + elseif ($this->_nomBitrate != 0) + return ($this->_nomBitrate); + else + return (($this->_minBitrate + $this->_maxBitrate) / 2); + } + + /** + * Gives the length (in seconds) of this stream. + * + * @access public + * @return int + */ + function getLength() + { + return ($this->_streamLength); + } + /** + * Get the start offset of the stream in seconds + * @access public + * @return int + */ + function getStartOffset(){ + return ($this->_startOffset); + } + /** + * States whether this logical stream was encoded in mono. + * + * @access public + * @return boolean + */ + function isMono() + { + return ($this->_channels == 1); + } + + /** + * States whether this logical stream was encoded in stereo. + * + * @access public + * @return boolean + */ + function isStereo() + { + return ($this->_channels == 2); + } + + /** + * States whether this logical stream was encoded in quadrophonic sound. + * + * @access public + * @return boolean + */ + function isQuadrophonic() + { + return ($this->_channels == 4); + } + + /** + * The title of this track, e.g. "What's Up Pussycat?". + * + * @access public + * @return string + */ + function getTitle() + { + return ($this->getField("TITLE")); + } + + /** + * Set the title of this track. + * + * @access public + * @param string $title + * @param boolean $replace + */ + function setTitle($title, $replace = true) + { + $this->setField("TITLE", $title, $replace); + } + + /** + * The version of the track, such as a remix. + * + * @access public + * @return string + */ + function getVersion() + { + return $this->getField("VERSION"); + } + + /** + * Set the version of this track. + * + * @access public + * @param string $version + * @param boolean $replace + */ + function setVersion($version, $replace = true) + { + $this->setField("VERSION", $version, $replace); + } + + /** + * The album or collection from which this track comes. + * + * @access public + * @return string + */ + function getAlbum() + { + return ($this->getField("ALBUM")); + } + + /** + * Set the album or collection for this track. + * + * @access public + * @param string $album + * @param boolean $replace + */ + function setAlbum($album, $replace = true) + { + $this->setField("ALBUM", $album, $replace); + } + + /** + * The number of this track if it is part of a larger collection. + * + * @access public + * @return string + */ + function getTrackNumber() + { + return ($this->getField("TRACKNUMBER")); + } + + /** + * Set the number of this relative to the collection. + * + * @access public + * @param int $number + * @param boolean $replace + */ + function setTrackNumber($number, $replace = true) + { + $this->setField("TRACKNUMBER", $number, $replace); + } + + /** + * The artist responsible for this track. + * + * This function returns the name of the artist responsible for this + * recording, which may be either a solo-artist, duet or group. + * + * @access public + * @return string + */ + function getArtist() + { + return ($this->getField("ARTIST")); + } + + /** + * Set the artist of this track. + * + * @access public + * @param string $artist + * @param boolean $replace + */ + function setArtist($artist, $replace = true) + { + $this->setField("ARTIST", $artist, $replace = true); + } + + /** + * The performer of this track, such as an orchestra + * + * @access public + * @return string + */ + function getPerformer() + { + return ($this->getField("PERFORMER")); + } + + /** + * Set the performer of this track. + * + * @access public + * @param string $performer + * @param boolean $replace + */ + function setPerformer($performer, $replace = true) + { + $this->setField("PERFORMER", $performer, $replace); + } + + /** + * The copyright attribution for this track. + * + * @access public + * @return string + */ + function getCopyright() + { + return ($this->getField("COPYRIGHT")); + } + + /** + * Set the copyright attribution for this track. + * + * @access public + * @param string $copyright + * @param boolean $replace + */ + function setCopyright($copyright, $replace = true) + { + $this->setField("COPYRIGHT", $copyright, $replace); + } + + /** + * The rights of distribution for this track. + * + * This funtion returns the license for this track, and may include + * copyright information, or a creative commons statement. + * + * @access public + * @return string + */ + function getLicense() + { + return ($this->getField("LICENSE")); + } + + /** + * Set the distribution rights for this track. + * + * @access public + * @param string $license + * @param boolean $replace + */ + function setLicense($license, $replace = true) + { + $this->setField("LICENSE", $license, $replace); + } + + /** + * The organisation responsible for this track. + * + * This function returns the name of the organisation responsible for + * the production of this track, such as the record label. + * + * @access public + * @return string + */ + function getOrganization() + { + return ($this->getField("ORGANIZATION")); + } + + /** + * Set the organisation responsible for this track. + * + * @access public + * @param string $organization + * @param boolean $replace + */ + function setOrganziation($organization, $replace = true) + { + $this->setField("ORGANIZATION", $organization, $replace); + } + + /** + * A short description of the contents of this track. + * + * This function returns a short description of this track, which might + * contain extra information that doesn't fit anywhere else. + * + * @access public + * @return string + */ + function getDescription() + { + return ($this->getField("DESCRIPTION")); + } + + /** + * Set the description of this track. + * + * @access public + * @param string $description + * @param boolean $replace + */ + function setDescription($description, $replace = true) + { + $this->setField("DESCRIPTION", $replace); + } + + /** + * The genre of this recording (e.g. Rock) + * + * This function returns the genre of this recording. There are no pre- + * defined genres, so this is completely up to the tagging software. + * + * @access public + * @return string + */ + function getGenre() + { + return ($this->getField("GENRE")); + } + + /** + * Set the genre of this track. + * + * @access public + * @param string $genre + * @param boolean $replace + */ + function setGenre($genre, $replace = true) + { + $this->setField("GENRE", $genre, $replace); + } + + /** + * The date of the recording of this track. + * + * This function returns the date on which this recording was made. There + * is no specification for the format of this date. + * + * @access public + * @return string + */ + function getDate() + { + return ($this->getField("DATE")); + } + + /** + * Set the date of recording for this track. + * + * @access public + * @param string $date + * @param boolean $replace + */ + function setDate($date, $replace = true) + { + $this->setField("DATE", $date, $replace); + } + + /** + * Where this recording was made. + * + * This function returns where this recording was made, such as a recording + * studio, or concert venue. + * + * @access public + * @return string + */ + function getLocation() + { + return ($this->getField("LOCATION")); + } + + /** + * Set the location of the recording of this track. + * + * @access public + * @param string $location + * @param boolean $replace + */ + function setLocation($location, $replace = true) + { + $this->setField("LOCATION", $location, $replace); + } + + /** + * @access public + * @return string + */ + function getContact() + { + return ($this->getField("CONTACT")); + } + + /** + * Set the contact information for this track. + * + * @access public + * @param string $contact + * @param boolean $replace + */ + function setContact($contact, $replace = true) + { + $this->setField("CONTACT", $contact, $replace); + } + + /** + * International Standard Recording Code. + * + * Returns the International Standard Recording Code. This code can be + * validated using the Validate_ISPN package. + * + * @access public + * @return string + */ + function getIsrc() + { + return ($this->getField("ISRC")); + } + + /** + * Set the ISRC for this track. + * + * @access public + * @param string $isrc + * @param boolean $replace + */ + function setIsrc($isrc, $replace = true) + { + $this->setField("ISRC", $isrc, $replace); + } + + /** + * Get an associative array containing header information about the stream + * @access public + * @return array + */ + function getHeader() { + return $this->_idHeader; + } +} +?> diff --git a/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/README b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/README new file mode 100644 index 00000000..963ca07c --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/OggHandler/File_Ogg/File/README @@ -0,0 +1,2 @@ +This was originally a fork of the File_Ogg package in PEAR. + diff --git a/extensions/TimedMediaHandler/handlers/OggHandler/OggException.php b/extensions/TimedMediaHandler/handlers/OggHandler/OggException.php new file mode 100644 index 00000000..a09d36da --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/OggHandler/OggException.php @@ -0,0 +1,4 @@ + self::METADATA_VERSION ); + + try { + $f = new File_Ogg( $path ); + $streams = array(); + foreach ( $f->listStreams() as $streamIDs ) { + foreach ( $streamIDs as $streamID ) { + $stream = $f->getStream( $streamID ); + $streams[$streamID] = array( + 'serial' => $stream->getSerial(), + 'group' => $stream->getGroup(), + 'type' => $stream->getType(), + 'vendor' => $stream->getVendor(), + 'length' => $stream->getLength(), + 'size' => $stream->getSize(), + 'header' => $stream->getHeader(), + 'comments' => $stream->getComments() + ); + } + } + $metadata['streams'] = $streams; + $metadata['length'] = $f->getLength(); + // Get the offset of the file (in cases where the file is a segment copy) + $metadata['offset'] = $f->getStartOffset(); + } catch ( OggException $e ) { + // File not found, invalid stream, etc. + $metadata['error'] = array( + 'message' => $e->getMessage(), + 'code' => $e->getCode() + ); + } + return serialize( $metadata ); + } + + /** + * Display metadata box on file description page. + * + * This is pretty basic, it puts data from all the streams together, + * and only outputs a couple of the most commonly used ogg "comments", + * with comments from all the streams combined + * + * @param File $file + * @param bool|IContextSource $context Context to use (optional) + * @return array|bool + */ + public function formatMetadata( $file, $context = false ) { + $meta = $this->getCommonMetaArray( $file ); + if ( count( $meta ) === 0 ) { + return false; + } + return $this->formatMetadataHelper( $meta, $context ); + } + + /** + * Get some basic metadata properties that are common across file types. + * + * @param File $file + * @return array Array of metadata. See MW's FormatMetadata class for format. + */ + public function getCommonMetaArray( File $file ) { + $metadata = $this->unpackMetadata( $file->getMetadata() ); + if ( !$metadata || isset( $metadata['error'] ) || !isset( $metadata['streams'] ) ) { + return array(); + } + + // See http://www.xiph.org/vorbis/doc/v-comment.html + // http://age.hobba.nl/audio/mirroredpages/ogg-tagging.html + $metadataMap = array( + 'title' => 'ObjectName', + 'artist' => 'Artist', + 'performer' => 'Artist', + 'description' => 'ImageDescription', + 'license' => 'UsageTerms', + 'copyright' => 'Copyright', + 'organization' => 'dc-publisher', + 'date' => 'DateTimeDigitized', + 'location' => 'LocationDest', + 'contact' => 'Contact', + 'encoded_using' => 'Software', + 'encoder' => 'Software', + // OpenSubtitles.org hash. Identifies source video. + 'source_ohash' => 'OriginalDocumentID', + 'comment' => 'UserComment', + 'language' => 'LanguageCode', + ); + + $props = array(); + + foreach( $metadata['streams'] as $stream ) { + if ( isset( $stream['vendor'] ) ) { + if ( !isset( $props['Software'] ) ) { + $props['Software'] = array(); + } + $props['Software'][] = trim( $stream['vendor'] ); + } + if ( !isset( $stream['comments'] ) ) { + continue; + } + foreach( $stream['comments'] as $name => $rawValue ) { + // $value will be an array if the file has + // a multiple tags with the same name. Otherwise it + // is a string. + foreach( (array) $rawValue as $value ) { + $trimmedValue = trim( $value ); + if ( $trimmedValue === '' ) { + continue; + } + $lowerName = strtolower( $name ); + if ( isset( $metadataMap[$lowerName] ) ) { + $convertedName = $metadataMap[$lowerName]; + if ( !isset( $props[$convertedName] ) ) { + $props[$convertedName] = array(); + } + $props[$convertedName][] = $trimmedValue; + } + } + } + + } + // properties might be duplicated across streams + foreach( $props as &$type ) { + $type = array_unique( $type ); + $type = array_values( $type ); + } + + return $props; + } + + /** + * Get the "media size" + * + * @param $file File + * @param $path string + * @param $metadata bool + * @return array|bool + */ + function getImageSize( $file, $path, $metadata = false ) { + global $wgMediaVideoTypes; + // Just return the size of the first video stream + if ( $metadata === false ) { + $metadata = $file->getMetadata(); + } + $metadata = $this->unpackMetadata( $metadata ); + if ( isset( $metadata['error'] ) || !isset( $metadata['streams'] ) ) { + return false; + } + foreach ( $metadata['streams'] as $stream ) { + if ( in_array( $stream['type'], $wgMediaVideoTypes ) ) { + $pictureWidth = $stream['header']['PICW']; + $parNumerator = $stream['header']['PARN']; + $parDenominator = $stream['header']['PARD']; + if( $parNumerator && $parDenominator ) { + // Compensate for non-square pixel aspect ratios + $pictureWidth = $pictureWidth * $parNumerator / $parDenominator; + } + return array( + intval( $pictureWidth ), + intval( $stream['header']['PICH'] ) + ); + } + } + return array( false, false ); + } + + /** + * @param $metadata + * @return bool|mixed + */ + function unpackMetadata( $metadata ) { + wfSuppressWarnings(); + $unser = unserialize( $metadata ); + wfRestoreWarnings(); + if ( isset( $unser['version'] ) && $unser['version'] == self::METADATA_VERSION ) { + return $unser; + } else { + return false; + } + } + + /** + * @param $image + * @return string + */ + function getMetadataType( $image ) { + return 'ogg'; + } + /** + * @param $file File + */ + function getWebType( $file ) { + $baseType = ( $file->getWidth() == 0 && $file->getHeight() == 0 )? 'audio' : 'video'; + $baseType .= '/ogg'; + $streamTypes = $this->getStreamTypes( $file ); + if ( !$streamTypes ) { + return $baseType; + } + $codecs = strtolower( implode( ", ", $streamTypes ) ); + return $baseType . '; codecs="' . $codecs . '"'; + } + /** + * @param $file File + * @return array|bool + */ + function getStreamTypes( $file ) { + $streamTypes = array(); + $metadata = $this->unpackMetadata( $file->getMetadata() ); + if ( !$metadata || isset( $metadata['error'] ) ) { + return false; + } + foreach ( $metadata['streams'] as $stream ) { + $streamTypes[] = $stream['type']; + } + return array_unique( $streamTypes ); + } + + /** + * @param $file File + * @return int + */ + function getOffset( $file ){ + $metadata = $this->unpackMetadata( $file->getMetadata() ); + if ( !$metadata || isset( $metadata['error'] ) || !isset( $metadata['offset']) ) { + return 0; + } else { + return $metadata['offset']; + } + } + + /** + * @param $file File + * @return int + */ + function getLength( $file ) { + $metadata = $this->unpackMetadata( $file->getMetadata() ); + if ( !$metadata || isset( $metadata['error'] ) ) { + return 0; + } else { + return $metadata['length']; + } + } + + /** + * Get useful response headers for GET/HEAD requests for a file with the given metadata + * @param $metadata mixed Result this handlers getMetadata() for a file + * @return Array + */ + public function getStreamHeaders( $metadata ) { + $metadata = $this->unpackMetadata( $metadata ); + if ( $metadata && !isset( $metadata['error'] ) && isset( $metadata['length'] ) ) { + return array( 'X-Content-Duration' => floatval( $metadata[ 'length' ] ) ); + } + return array(); + } + + /** + * @param $file File + * @return float|int + */ + function getFramerate( $file ){ + $metadata = $this->unpackMetadata( $file->getMetadata() ); + if ( !$metadata || isset( $metadata['error'] ) ) { + return 0; + } else { + // Return the first found theora stream framerate: + foreach ( $metadata['streams'] as $stream ) { + if( $stream['type'] == 'Theora' ){ + return $stream['header']['FRN'] / $stream['header']['FRD']; + } + } + return 0; + } + } + + /** + * @param $file File + * @return String + */ + function getShortDesc( $file ) { + global $wgLang, $wgMediaAudioTypes, $wgMediaVideoTypes; + + $streamTypes = $this->getStreamTypes( $file ); + if ( !$streamTypes ) { + return parent::getShortDesc( $file ); + } + if ( array_intersect( $streamTypes, $wgMediaVideoTypes ) ) { + // Count multiplexed audio/video as video for short descriptions + $msg = 'timedmedia-ogg-short-video'; + } elseif ( array_intersect( $streamTypes, $wgMediaAudioTypes ) ) { + $msg = 'timedmedia-ogg-short-audio'; + } else { + $msg = 'timedmedia-ogg-short-general'; + } + return wfMessage( $msg, implode( '/', $streamTypes ), + $wgLang->formatTimePeriod( $this->getLength( $file ) ) )->text(); + } + + /** + * @param $file File + * @return String + */ + function getLongDesc( $file ) { + global $wgLang, $wgMediaVideoTypes, $wgMediaAudioTypes; + + $streamTypes = $this->getStreamTypes( $file ); + if ( !$streamTypes ) { + $unpacked = $this->unpackMetadata( $file->getMetadata() ); + if ( isset( $unpacked['error']['message'] ) ) { + return wfMessage( 'timedmedia-ogg-long-error', $unpacked['error']['message'] )->text(); + } else { + return wfMessage( 'timedmedia-ogg-long-no-streams' )->text(); + } + } + if ( array_intersect( $streamTypes,$wgMediaVideoTypes ) ) { + if ( array_intersect( $streamTypes, $wgMediaAudioTypes ) ) { + $msg = 'timedmedia-ogg-long-multiplexed'; + } else { + $msg = 'timedmedia-ogg-long-video'; + } + } elseif ( array_intersect( $streamTypes, $wgMediaAudioTypes ) ) { + $msg = 'timedmedia-ogg-long-audio'; + } else { + $msg = 'timedmedia-ogg-long-general'; + } + $size = 0; + $unpacked = $this->unpackMetadata( $file->getMetadata() ); + if ( !$unpacked || isset( $metadata['error'] ) ) { + $length = 0; + } else { + $length = $this->getLength( $file ); + foreach ( $unpacked['streams'] as $stream ) { + if( isset( $stream['size'] ) ) + $size += $stream['size']; + } + } + return wfMessage( + $msg, + implode( '/', $streamTypes ), + $wgLang->formatTimePeriod( $length ), + $wgLang->formatBitrate( $this->getBitRate( $file ) ) + )->numParams( + $file->getWidth(), + $file->getHeight() + )->text(); + } + + /** + * @param $file File + * @return float|int + */ + function getBitRate( &$file ){ + $size = 0; + $unpacked = $this->unpackMetadata( $file->getMetadata() ); + if ( !$unpacked || isset( $unpacked['error'] ) ) { + $length = 0; + } else { + $length = $this->getLength( $file ); + if ( isset( $unpacked['streams'] ) ) { + foreach ( $unpacked['streams'] as $stream ) { + if( isset( $stream['size'] ) ) + $size += $stream['size']; + } + } + } + return $length == 0 ? 0 : $size / $length * 8; + } +} diff --git a/extensions/TimedMediaHandler/handlers/TextHandler/TextHandler.php b/extensions/TimedMediaHandler/handlers/TextHandler/TextHandler.php new file mode 100644 index 00000000..3c229d5f --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/TextHandler/TextHandler.php @@ -0,0 +1,305 @@ +foreignDb = $mDb; + + $wikiID = $this->foreignDb->getWikiID(); + if ( isset( $wgTimedTextForeignNamespaces[ $wikiID ] ) ) { + $this->foreignNs = $wgTimedTextForeignNamespaces[ $wikiID ]; + } else { + $this->foreignNs = NS_TIMEDTEXT; + } + parent::__construct( $query, $moduleName, 'ap' ); + } + + protected function getDB() { + return $this->foreignDb; + } + + protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) { + // foreignnNs might not be defined localy, + // catch the undefined error here + if ( $valueName == 'apnamespace' + && $value == $this->foreignNs + && $allowMultiple == false + ) { + return $this->foreignNs; + } + return parent::parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ); + } + + /** + * An alternative to titleToKey() that doesn't trim trailing spaces + * + * + * @FIXME: I'M A BIG HACK + * + * @param string $titlePart Title part with spaces + * @return string Title part with underscores + */ + public function titlePartToKey( $titlePart, $defaultNamespace = NS_MAIN ) { + return substr( $this->titleToKey( $titlePart . 'x' ), 0, -1 ); + } +} + +class TextHandler { + var $remoteNs = null;//lazy init remote Namespace number + + /** + * @var File + */ + protected $file; + + function __construct( $file ){ + $this->file = $file; + } + + /** + * Get the timed text tracks elements as an associative array + * @return array|mixed + */ + function getTracks(){ + if( $this->file->isLocal() ){ + return $this->getLocalTextSources(); + } elseif ( $this->file->getRepo() instanceof ForeignDBViaLBRepo ){ + return $this->getForeignDBTextSources(); + } else { + return $this->getRemoteTextSources(); + } + } + + /** + * @return bool|int|null + */ + function getTimedTextNamespace(){ + global $wgEnableLocalTimedText; + if( $this->file->isLocal() ) { + if ( $wgEnableLocalTimedText ) { + return NS_TIMEDTEXT; + } else { + return false; + } + } elseif( $this->file->repo instanceof ForeignDBViaLBRepo ){ + global $wgTimedTextForeignNamespaces; + $wikiID = $this->file->getRepo()->getSlaveDB()->getWikiID(); + if ( isset( $wgTimedTextForeignNamespaces[ $wikiID ] ) ) { + return $wgTimedTextForeignNamespaces[ $wikiID ]; + } + // failed to get namespace via ForeignDBViaLBRepo, return NS_TIMEDTEXT + return NS_TIMEDTEXT; + } else { + if( $this->remoteNs !== null ){ + return $this->remoteNs; + } + // Get the namespace data from the image api repo: + // fetchImageQuery query caches results + $data = $this->file->getRepo()->fetchImageQuery( array( + 'meta' =>'siteinfo', + 'siprop' => 'namespaces' + )); + + if( isset( $data['query'] ) && isset( $data['query']['namespaces'] ) ){ + // get the ~last~ timed text namespace defined + foreach( $data['query']['namespaces'] as $ns ){ + if( $ns['*'] == 'TimedText' ){ + $this->remoteNs = $ns['id']; + } + } + } + // Return the remote Ns + return $this->remoteNs; + } + } + + /** + * @return array|bool + */ + function getTextPagesQuery(){ + $ns = $this->getTimedTextNamespace(); + if( $ns === false ){ + wfDebug("Repo: " . $this->file->repo->getName() . " does not have a TimedText namesapce \n"); + // No timed text namespace, don't try to look up timed text tracks + return false; + } + return array( + 'action' => 'query', + 'list' => 'allpages', + 'apnamespace' => $ns, + 'aplimit' => 300, + 'apprefix' => $this->file->getTitle()->getDBkey() + ); + } + + /** + * @return array|mixed + */ + function getRemoteTextSources(){ + global $wgMemc; + // Use descriptionCacheExpiry as our expire for timed text tracks info + if ( $this->file->getRepo()->descriptionCacheExpiry > 0 ) { + wfDebug("Attempting to get text tracks from cache..."); + $key = $this->file->getRepo()->getLocalCacheKey( 'RemoteTextTracks', 'url', $this->file->getName() ); + $obj = $wgMemc->get($key); + if ($obj) { + wfDebug("success!\n"); + return $obj; + } + wfDebug("miss\n"); + } + wfDebug("Get text tracks from remote api \n"); + $query = $this->getTextPagesQuery(); + + // Error in getting timed text namespace return empty array; + if( $query === false ){ + return array(); + } + $data = $this->file->getRepo()->fetchImageQuery( $query ); + $textTracks = $this->getTextTracksFromData( $data ); + if ( $data && $this->file->repo->descriptionCacheExpiry > 0 ) { + $wgMemc->set( $key, $textTracks, $this->file->repo->descriptionCacheExpiry ); + } + return $textTracks; + } + + /** + * @return array + */ + function getLocalTextSources(){ + global $wgEnableLocalTimedText; + if ( $wgEnableLocalTimedText ) { + // Init $this->textTracks + $params = new FauxRequest( $this->getTextPagesQuery() ); + $api = new ApiMain( $params ); + $api->execute(); + if ( defined( 'ApiResult::META_CONTENT' ) ) { + $data = $api->getResult()->getResultData( null, array( 'Strip' => 'all' ) ); + } else { + $data = $api->getResultData(); + } + wfDebug(print_r($data, true)); + // Get the list of language Names + return $this->getTextTracksFromData( $data ); + } else { + return array(); + } + } + + /** + * @return array|mixed + */ + function getForeignDBTextSources(){ + // Init $this->textTracks + $params = new FauxRequest( $this->getTextPagesQuery() ); + $api = new ApiMain( $params ); + $api->profileIn(); + $query = new ApiQuery( $api, 'foo', 'bar' ); + $query->profileIn(); + $module = new ForeignApiQueryAllPages( $this->file->getRepo()->getSlaveDB(), $query, 'allpages' ); + $module->profileIn(); + $module->execute(); + $module->profileOut(); + $query->profileOut(); + $api->profileOut(); + + if ( defined( 'ApiResult::META_CONTENT' ) ) { + $data = $module->getResult()->getResultData( null, array( 'Strip' => 'all' ) ); + } else { + $data = $module->getResultData(); + } + // Get the list of language Names + return $this->getTextTracksFromData( $data ); + } + + /** + * @param $data + * @return array + */ + function getTextTracksFromData( $data ){ + $textTracks = array(); + $providerName = $this->file->repo->getName(); + // commons is called shared in production. normalize it to wikimediacommons + if( $providerName == 'shared' ){ + $providerName = 'wikimediacommons'; + } + // Provider name should be the same as the interwiki map + // @@todo more testing with this: + + $langNames = Language::fetchLanguageNames( null, 'mw' ); + if( $data['query'] && $data['query']['allpages'] ){ + foreach( $data['query']['allpages'] as $page ){ + $subTitle = Title::newFromText( $page['title'] ) ; + $tileParts = explode( '.', $page['title'] ); + if( count( $tileParts) >= 3 ){ + $timedTextExtension = array_pop( $tileParts ); + $languageKey = array_pop( $tileParts ); + $contentType = $this->getContentType( $timedTextExtension ); + } else { + continue; + } + // If there is no valid language continue: + if( !isset( $langNames[ $languageKey ] ) ){ + continue; + } + $namespacePrefix = "TimedText:"; + $textTracks[] = array( + 'kind' => 'subtitles', + 'data-mwtitle' => $namespacePrefix . $subTitle->getDBkey(), + 'data-mwprovider' => $providerName, + 'type' => $contentType, + // @todo Should eventually add special entry point and output proper WebVTT format: + // http://www.whatwg.org/specs/web-apps/current-work/webvtt.html + 'src' => $this->getFullURL( $page['title'], $contentType ), + 'srclang' => $languageKey, + 'data-dir' => Language::factory( $languageKey )->getDir(), + 'label' => wfMessage('timedmedia-subtitle-language', + $langNames[ $languageKey ], + $languageKey )->text() + ); + } + } + return $textTracks; + } + + function getContentType( $timedTextExtension ) { + if ( $timedTextExtension === 'srt' ) { + return 'text/x-srt'; + } else if ( $timedTextExtension === 'vtt' ) { + return 'text/vtt'; + } + return ''; + } + + function getFullURL( $pageTitle, $contentType ){ + if( $this->file->isLocal() ) { + $subTitle = Title::newFromText( $pageTitle ) ; + return $subTitle->getFullURL( array( + 'action' => 'raw', + 'ctype' => $contentType + )); + //} elseif( $this->file->repo instanceof ForeignDBViaLBRepo ){ + } else { + $query = 'title=' . wfUrlencode( $pageTitle ) . '&'; + $query .= wfArrayToCgi( array( + 'action' => 'raw', + 'ctype' => $contentType + ) ); + // Note: This will return false if scriptDirUrl is not set for repo. + return $this->file->repo->makeUrl( $query ); + } + } +} diff --git a/extensions/TimedMediaHandler/handlers/WAVHandler/WAVHandler.php b/extensions/TimedMediaHandler/handlers/WAVHandler/WAVHandler.php new file mode 100644 index 00000000..9fae12b8 --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/WAVHandler/WAVHandler.php @@ -0,0 +1,87 @@ +getID3( $filename ); + + if( + isset( $metadata['audio'] ) + && $metadata['audio']['dataformat'] == 'wav' + && ( $metadata['audio']['codec'] == 'Pulse Code Modulation (PCM)' || $metadata['audio']['codec'] == 'IEEE Float' ) + ){ + return Status::newGood(); + } + + return Status::newFatal( 'timedmedia-wav-pcm-required' ); + } + /** + * @param $file File + * @return array|bool + */ + function getStreamTypes( $file ) { + $streamTypes = array(); + $metadata = $this->unpackMetadata( $file->getMetadata() ); + + if ( !$metadata || isset( $metadata['error'] ) ) { + return false; + } + + if( isset( $metadata['audio'] ) && $metadata['audio']['dataformat'] == 'wav' ){ + $streamTypes[] = 'WAV'; + } + + return $streamTypes; + } + + /** + * @param $file File + * @return String + */ + function getShortDesc( $file ) { + global $wgLang; + + $streamTypes = $this->getStreamTypes( $file ); + if ( !$streamTypes ) { + return parent::getShortDesc( $file ); + } + return wfMessage( 'timedmedia-wav-short-audio', + $wgLang->formatTimePeriod( $this->getLength( $file ) ) )->text(); + } + + /** + * @param $file File + * @return String + */ + function getLongDesc( $file ) { + global $wgLang; + $streamTypes = $this->getStreamTypes( $file ); + if ( !$streamTypes ) { + return parent::getLongDesc( $file ); + } + return wfMessage('timedmedia-wav-long-audio', + $wgLang->formatTimePeriod( $this->getLength($file) ), + $wgLang->formatBitrate( $this->getBitRate( $file ) ) + )->text(); + + } + +} diff --git a/extensions/TimedMediaHandler/handlers/WebMHandler/WebMHandler.php b/extensions/TimedMediaHandler/handlers/WebMHandler/WebMHandler.php new file mode 100644 index 00000000..ebdad0d1 --- /dev/null +++ b/extensions/TimedMediaHandler/handlers/WebMHandler/WebMHandler.php @@ -0,0 +1,175 @@ +getMetadata(); + } + $metadata = $this->unpackMetadata( $metadata ); + if ( isset( $metadata['error'] ) ) { + return false; + } + + $size = array( false, false ); + // display_x/display_y is only set if DisplayUnit + // is pixels, otherwise display_aspect_ratio is set + if ( isset( $metadata['video']['display_x'] ) + && + isset( $metadata['video']['display_y'] ) + ){ + $size = array ( + $metadata['video']['display_x'], + $metadata['video']['display_y'] + ); + } + elseif ( isset( $metadata['video']['resolution_x'] ) + && + isset( $metadata['video']['resolution_y'] ) + ){ + $size = array ( + $metadata['video']['resolution_x'], + $metadata['video']['resolution_y'] + ); + if ( isset($metadata['video']['crop_top']) ) { + $size[1] -= $metadata['video']['crop_top']; + } + if ( isset($metadata['video']['crop_bottom']) ) { + $size[1] -= $metadata['video']['crop_bottom']; + } + if ( isset($metadata['video']['crop_left']) ) { + $size[0] -= $metadata['video']['crop_left']; + } + if ( isset($metadata['video']['crop_right']) ) { + $size[0] -= $metadata['video']['crop_right']; + } + } + if ( $size[0] && $size[1] && isset( $metadata['video']['display_aspect_ratio'] ) ) { + //for wide images (i.e. 16:9) take native height as base + if ( $metadata['video']['display_aspect_ratio'] >= 1 ) { + $size[0] = intval( $size[1] * $metadata['video']['display_aspect_ratio'] ); + } else { //for tall images (i.e. 9:16) take width as base + $size[1] = intval( $size[0] / $metadata['video']['display_aspect_ratio'] ); + } + } + return $size; + } + + /** + * @param $file + * @return string + */ + function getMetadataType( $file ) { + return 'webm'; + } + + /** + * @param $file File + * @return String + */ + function getWebType( $file ) { + $baseType = ( $file->getWidth() == 0 && $file->getHeight() == 0 )? 'audio' : 'video'; + + $streams = $this->getStreamTypes( $file ); + if ( !$streams ) { + return $baseType . '/webm'; + } + + $codecs = strtolower( implode( ', ', $streams ) ); + + return $baseType . '/webm; codecs="' . $codecs . '"'; + } + + /** + * @param $file File + * @return array|bool + */ + function getStreamTypes( $file ) { + $streamTypes = array(); + $metadata = $this->unpackMetadata( $file->getMetadata() ); + if ( !$metadata || isset( $metadata['error'] ) ) { + return false; + } + // id3 gives 'V_VP8' for what we call VP8 + if( isset( $metadata['video'] ) && $metadata['video']['dataformat'] == 'vp8' ){ + $streamTypes[] = 'VP8'; + } elseif( isset( $metadata['video'] ) && + ( $metadata['video']['dataformat'] === 'vp9' + || $metadata['video']['dataformat'] === 'V_VP9' + ) ) { + // Currently getID3 calls it V_VP9. That will probably change to vp9 + // once getID3 actually gets support for the codec. + $streamTypes[] = 'VP9'; + } + if( isset( $metadata['audio'] ) && $metadata['audio']['dataformat'] == 'vorbis' ){ + $streamTypes[] = 'Vorbis'; + } elseif ( isset( $metadata['audio'] ) && + ( $metadata['audio']['dataformat'] == 'opus' + || $metadata['audio']['dataformat'] == 'A_OPUS' + ) ) { + // Currently getID3 calls it A_OPUS. That will probably change to 'opus' + // once getID3 actually gets support for the codec. + $streamTypes[] = 'Opus'; + } + + return $streamTypes; + } + + /** + * @param $file File + * @return String + */ + function getShortDesc( $file ) { + global $wgLang; + + $streamTypes = $this->getStreamTypes( $file ); + if ( !$streamTypes ) { + return parent::getShortDesc( $file ); + } + return wfMessage( 'timedmedia-webm-short-video', implode( '/', $streamTypes ), + $wgLang->formatTimePeriod( $this->getLength( $file ) ) )->text(); + } + + /** + * @param $file File + * @return String + */ + function getLongDesc( $file ) { + global $wgLang; + $streamTypes = $this->getStreamTypes( $file ); + if ( !$streamTypes ) { + return parent::getLongDesc( $file ); + } + return wfMessage('timedmedia-webm-long-video', + implode( '/', $streamTypes ), + $wgLang->formatTimePeriod( $this->getLength($file) ), + $wgLang->formatBitrate( $this->getBitRate( $file ) ) + )->numParams( + $file->getWidth(), + $file->getHeight() + )->text(); + + } + +} diff --git a/extensions/TimedMediaHandler/i18n/ady-cyrl.json b/extensions/TimedMediaHandler/i18n/ady-cyrl.json new file mode 100644 index 00000000..a76e7faa --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ady-cyrl.json @@ -0,0 +1,11 @@ +{ + "@metadata": { + "authors": [ + "GR44 Luc", + "TheRossatron" + ] + }, + "timedmedia-no-player-js": "Къытфэгъэгъу, е уи броузерым и JavaScript гъэкIосагъэ, е гъэлъэгъуалъэ тэрэз тетэп.
\nКлипыр къипщэн е гъэлъэгъуалъэ тебгъэуцон плъэкIыщт, уи броузерым ущеплъынэу.", + "timedmedia-source-file": "Лъапсэу $1", + "timedmedia-source-audio-file-desc": "Оригинал $1-файлыр ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/aeb-latn.json b/extensions/TimedMediaHandler/i18n/aeb-latn.json new file mode 100644 index 00000000..65d57191 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/aeb-latn.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Csisc" + ] + }, + "timedmedia-source-file": "$1 is-sūrs", + "timedmedia-source-audio-file-desc": "Ficyēy $1 aşlī ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/af.json b/extensions/TimedMediaHandler/i18n/af.json new file mode 100644 index 00000000..7708e9ae --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/af.json @@ -0,0 +1,25 @@ +{ + "@metadata": { + "authors": [ + "Naudefj", + "SPQRobin", + "Arnobarnard" + ] + }, + "timedmedia-desc": "Hanteer Ogg Theora- en Vorbis-lêers met 'n JavaScript-mediaspeler", + "timedmedia-ogg-short-audio": "Ogg $1 klanklêer, $2", + "timedmedia-ogg-short-video": "Ogg $1 video lêer, $2", + "timedmedia-ogg-short-general": "Ogg $1 medialêer, $2", + "timedmedia-ogg-long-audio": "Ogg $1 klanklêer, lengte $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 videolêer, lengte $2, $4×$5 pixels, $3", + "timedmedia-ogg-long-general": "Ogg medialêer, lengte $2, $3", + "timedmedia-ogg-long-error": "Ongeldige Ogg-lêer: $1", + "timedmedia-no-player-js": "Jammer, u blaaier het of JavaScript gedeaktiveer of het nie enige ondersteunde speler nie.
\nU kan die knipsel aflaai of 'n speler aflaai om die knipsel te speel in u blaaier.", + "timedmedia-more": "Meer…", + "timedmedia-dismiss": "Sluit", + "timedmedia-download": "Laai lêer af", + "timedmedia-desc-link": "Aangaande die lêer", + "timedmedia-minutes": "{{PLURAL:$1|$1 minuut|$1 minute}}", + "timedmedia-source-file": "Bron van $1", + "timedmedia-source-audio-file-desc": "Oorspronklike $1-lêer ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/akz.json b/extensions/TimedMediaHandler/i18n/akz.json new file mode 100644 index 00000000..641c0b9f --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/akz.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Ulohnanne" + ] + }, + "timedmedia-more": "Maatàasasi..." +} diff --git a/extensions/TimedMediaHandler/i18n/aln.json b/extensions/TimedMediaHandler/i18n/aln.json new file mode 100644 index 00000000..67645c86 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/aln.json @@ -0,0 +1,23 @@ +{ + "@metadata": { + "authors": [ + "Mdupont" + ] + }, + "timedmedia-desc": "Mbajtës për mediat në kohën e duhur (video, audio, timedText) me transcoding të ZQM Theora / Vorbis", + "timedmedia-ogg-short-audio": "Ogg tingull $1 fotografi, $2", + "timedmedia-ogg-short-video": "video file Ogg $1, $2", + "timedmedia-ogg-short-general": "Ogg $1 media file, $2", + "timedmedia-ogg-long-audio": "ZQM file $1 shëndoshë, gjatë $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 video file, gjatë $2, $4 × $5 pixels, $3", + "timedmedia-ogg-long-multiplexed": "ZQM multiplexed audio / video file, $1, gjatë $2, $4 × $5 pixels, $3 e përgjithshme", + "timedmedia-ogg-long-general": "Ogg media file, gjatë $2, $3", + "timedmedia-ogg-long-error": "E pavlefshme Ogg file: $1", + "timedmedia-no-player-js": "Na vjen keq, browser-i juaj ose ka JavaScript paaftë ose nuk ka asnjë lojtar të mbështetur.
Ju mund të shkarkoni clip ose shkarkoni një lojtar për të luajtur clip në shfletuesin tuaj.", + "timedmedia-more": "Më shumë ...", + "timedmedia-dismiss": "Afër", + "timedmedia-download": "Shkarko file", + "timedmedia-desc-link": "Për këtë fotografi", + "timedmedia-oggThumb-version": "OggHandler kërkon version oggThumb $1 ose më vonë.", + "timedmedia-oggThumb-failed": "oggThumb dështuar për të krijuar tablo." +} diff --git a/extensions/TimedMediaHandler/i18n/an.json b/extensions/TimedMediaHandler/i18n/an.json new file mode 100644 index 00000000..8bee5529 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/an.json @@ -0,0 +1,20 @@ +{ + "@metadata": { + "authors": [ + "Juanpabl" + ] + }, + "timedmedia-desc": "Maneyador de fichers sincronizatos (vidio, son y texto sincronizato) con transcodificación Ogg Theora/Vorbis", + "timedmedia-ogg-short-audio": "Fichero de son ogg $1, $2", + "timedmedia-ogg-short-video": "Fichero de vidio ogg $1, $2", + "timedmedia-ogg-short-general": "Fichero multimedia ogg $1, $2", + "timedmedia-ogg-long-audio": "Fichero de son ogg $1, durada $2, $3", + "timedmedia-ogg-long-video": "Fichero de vidio ogg $1, durada $2, $4×$5 píxels, $3", + "timedmedia-ogg-long-multiplexed": "fichero ogg multiplexato audio/vidio, $1, durada $2, $4×$5 píxels, $3 total", + "timedmedia-ogg-long-general": "fichero ogg multimedia durada $2, $3", + "timedmedia-ogg-long-error": "Fichero ogg no conforme: $1", + "timedmedia-more": "Más…", + "timedmedia-dismiss": "Zarrar", + "timedmedia-download": "Escargar fichero", + "timedmedia-desc-link": "Información sobre este fichero" +} diff --git a/extensions/TimedMediaHandler/i18n/ang.json b/extensions/TimedMediaHandler/i18n/ang.json new file mode 100644 index 00000000..077e8718 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ang.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Gott wisst" + ] + }, + "timedmedia-source-file": "$1 fruma", + "timedmedia-source-audio-file-desc": "Frumlicu $1 ymele ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/ar.json b/extensions/TimedMediaHandler/i18n/ar.json new file mode 100644 index 00000000..bcb7c11c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ar.json @@ -0,0 +1,67 @@ +{ + "@metadata": { + "authors": [ + "Alnokta", + "Asaifm", + "Meno25", + "OsamaK", + "روخو", + "Abanima" + ] + }, + "timedmedia-desc": "مداول للصوتيات والمرئيات والنصوص المؤقتة والذي لديه القدرة على دعم ملفات بصيغة WebM وOgg Theora وVorbis وsrt", + "timedmedia-ogg-short-audio": "Ogg $1 ملف صوت، $2", + "timedmedia-ogg-short-video": "Ogg $1 ملف فيديو، $2", + "timedmedia-ogg-short-general": "Ogg $1 ملف ميديا، $2", + "timedmedia-ogg-long-audio": "Ogg $1 ملف صوت، الطول $2، $3", + "timedmedia-ogg-long-video": "Ogg $1 ملف فيديو، الطول $2، $4×$5 بكسل، $3", + "timedmedia-ogg-long-multiplexed": "ملف Ogg مالتي بليكسد أوديو/فيديو، $1، الطول $2، $4×$5 بكسل، $3 إجمالي", + "timedmedia-ogg-long-general": "ملف ميديا Ogg، الطول $2، $3", + "timedmedia-ogg-long-error": "ملف Ogg غير صحيح: $1", + "timedmedia-webm-short-video": "نسق WebM، $1 ملف فيديو $2", + "timedmedia-webm-long-video": "ملف صوت/فيديو بنسق WebM , $1, الطول $2, $4×$5 بكسل، $3 إجمالا", + "timedmedia-no-player-js": "عذرا، متصفحك إما انه لا يدعم الجافا سكريبت أو انها معطلة.
\nتستطيع تحميل المقطع أو تحميل مشغل يستطيع تشغيل المقطع على متصفحك.", + "timedmedia-more": "المزيد...", + "timedmedia-dismiss": "أغلق", + "timedmedia-download": "نزل الملف", + "timedmedia-play-media": "تشغيل الوسائط", + "timedmedia-desc-link": "عن هذا الملف", + "timedmedia-status": "الحالة", + "timedmedia-status-unknown": "حالة غير معروفة", + "timedmedia-actions": "الإجراءات", + "timedmedia-direct-link": "تحميل المشتق", + "timedmedia-not-ready": "غير جاهز", + "timedmedia-percent-done": "وصل لنسبة $1% انتهى", + "timedmedia-in-job-queue": "تمت الإضافة إلى طابور المهام منذ $1", + "timedmedia-days": "{{PLURAL:$1|يوم واحد|يومين|$1 أيام|$1 يوماً|$1 يوم}}", + "timedmedia-hours": "{{PLURAL:$1|ساعة واحدة|ساعتين|$1 ساعات|$1 ساعة}}", + "timedmedia-minutes": "{{PLURAL:$1|دقيقة واحدة|دقيقتين|$1 دقائق|$1 دقيقة}}", + "timedmedia-seconds": "{{PLURAL:$1|ثانية واحدة|ثانيتين|$1 ثوان|$1 ثانية}}", + "timedmedia-ogg": "أو جي جي", + "timedmedia-source-file": "$1 مصدر", + "timedmedia-source-file-desc": "الأصل ملف $1، $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "الملف $1 الأصلي ($2)", + "timedmedia-derivative-desc-160p.ogv": "انخفاض عرض النطاق الترددي أوج فيديو (160P)", + "timedmedia-derivative-desc-720p.ogv": "تحميل أوج فيديو بجودة عالية (720P)", + "timedmedia-subtitle-new": "أنشئ ترجمة جديدة أو حرر ترجمة موجودة", + "timedmedia-subtitle-new-desc": "قم بإختيار اللغة وإضغط على زر '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "اذهب", + "timedmedia-subtitle-language": "$1 ($2) ترجمات", + "timedmedia-subtitle-no-video": "لا يوجد فيديو مقترن بصفحة ترجمة أو نصوص الفيديو الحالية.", + "timedmedia-subtitle-no-subtitles": "لا توجد نصوص أو ترجمات في $1 لهذا الفيديو، يمكنك [{{fullurl:{{FULLPAGENAME}}|action=edit}} تعديل هذه الصفحة] وإضافتهم.", + "timedmedia-subtitle-remote-link": "يمكنك [$1 مطالعة صفحة الوصف] لهذا الملف على $2", + "timedmediahandler": "معالج الميديا الزمنية", + "timedmedia-videos": "{{PLURAL:$1|ملف فيديو واحد|ملفا فيديو|$1 ملفات فيديو|$1 ملف فيديو}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|ملف|ملفا|$1 ملفات|$1 ملف}} فيديو Ogg", + "timedmedia-webm-videos": "{{PLURAL:$1|ملف|ملفا|$1 ملفات|$1 ملف}} فيديو WebM", + "timedmedia-file": "ملف", + "timedmedia-audios": "{{PLURAL:$1|ملف صوتي واحد|$1 ملف صوتي}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|ملف صوتي واحد بصيغة أوغ|$1 ملف صوتي بصيغة أوغ}}", + "timedmedia-flac-audios": "{{PLURAL:$1|ملف|ملفا|$1 ملفات|$1 ملف}} صوتي FLAC", + "timedmedia-wav-audios": "{{PLURAL:$1|ملف|ملفا|$1 ملفات|$1 ملف}} صوتي WAV", + "orphanedtimedtext": "صفحات TimedText يتيمة", + "orphanedtimedtext-summary": "قائمة بصفحات [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] التي ليس لديها ملف موافق.", + "orphanedtimedtext-unsupported": "هذه الصفحة الخاصة لا تدعمها إلا قواعد بيانات MySQL", + "orphanedtimedtext-notimedtext": "TimedText غير مفعل على هذا الويكي", + "apihelp-transcodereset-param-title": "عنوان ملف الميديا." +} diff --git a/extensions/TimedMediaHandler/i18n/arc.json b/extensions/TimedMediaHandler/i18n/arc.json new file mode 100644 index 00000000..7f2df877 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/arc.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Basharh" + ] + }, + "timedmedia-more": "ܝܬܝܪ…" +} diff --git a/extensions/TimedMediaHandler/i18n/arq.json b/extensions/TimedMediaHandler/i18n/arq.json new file mode 100644 index 00000000..a4b24de3 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/arq.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Oldstoneage" + ] + }, + "timedmedia-no-player-js": "اعدُرنا، الفلّاك فيه JavaScript محبّس ولا ما فيه حتا قرّاي موالم.
\nتنجم تتيليشارجي الفيديو ولا نزّل عندك قرّاي يكون موالم باش تفوّت الفيديو فل فلّاك تاعك.", + "timedmedia-source-file": "$1 مصدر", + "timedmedia-source-audio-file-desc": "فيشي $1 أصلي ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/arz.json b/extensions/TimedMediaHandler/i18n/arz.json new file mode 100644 index 00000000..db3b7f88 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/arz.json @@ -0,0 +1,24 @@ +{ + "@metadata": { + "authors": [ + "Ghaly", + "Meno25", + "Ramsis II" + ] + }, + "timedmedia-desc": "متحكم لملفات أو جى جى ثيورا و فوربيس، مع بلاير جافاسكريبت", + "timedmedia-ogg-short-audio": "Ogg $1 ملف صوت، $2", + "timedmedia-ogg-short-video": "Ogg $1 ملف فيديو, $2", + "timedmedia-ogg-short-general": "Ogg $1 ملف ميديا، $2", + "timedmedia-ogg-long-audio": "Ogg $1 ملف صوت، الطول $2، $3", + "timedmedia-ogg-long-video": "Ogg $1 ملف فيديو، الطول $2، $4×$5 بكسل، $3", + "timedmedia-ogg-long-multiplexed": "ملف Ogg مالتى بليكسد أوديو/فيديو، $1، الطول $2، $4×$5 بكسل، $3 إجمالي", + "timedmedia-ogg-long-general": "ملف ميديا Ogg، الطول $2، $3", + "timedmedia-ogg-long-error": "ملف ogg مش صحيح: $1", + "timedmedia-more": "أكتر...", + "timedmedia-dismiss": "اقفل", + "timedmedia-download": "نزل الملف", + "timedmedia-desc-link": "عن الملف دا", + "timedmedia-source-file": "$1 مصدر", + "timedmedia-source-audio-file-desc": "الملف $1 الأصل ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/as.json b/extensions/TimedMediaHandler/i18n/as.json new file mode 100644 index 00000000..519fccc6 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/as.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "IKHazarika" + ] + }, + "timedmedia-no-player-js": "দুঃখিত, আপুনাৰ ব্ৰাইজাৰত নাইবা জাভা-স্ক্ৰিপ্ট নিষ্ক্ৰিয় কৰা হৈ আছে অথবা কোনো সমৰ্থিত প্লেয়াৰ নাই।
\nআপুনাৰ ব্ৰাউজাৰত ক্লিপটো প্লে' কৰিবলৈ প্লে'য়াৰ এটা ডাউনল'ড কৰক অথবা ক্লিপটো ডাউনল'ড কৰক ।", + "timedmedia-source-file": "$1 উৎস", + "timedmedia-source-audio-file-desc": "প্ৰকৃত $1 ফাইল ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/ast.json b/extensions/TimedMediaHandler/i18n/ast.json new file mode 100644 index 00000000..2ff3d0af --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ast.json @@ -0,0 +1,122 @@ +{ + "@metadata": { + "authors": [ + "Esbardu", + "Xuacu" + ] + }, + "timedmedia-desc": "Remanador de soníu, videu y testu cronometráu, con sofitu pa los formatos WebM, Ogg Theora, Vorbis y srt", + "timedmedia-ogg-short-audio": "Archivu de soníu ogg $1, $2", + "timedmedia-ogg-short-video": "Archivu de videu ogg $1, $2", + "timedmedia-ogg-short-general": "Archivu multimedia ogg $1, $2", + "timedmedia-ogg-long-audio": "Archivu de soníu ogg $1, llonxitú $2, $3", + "timedmedia-ogg-long-video": "Archivu de videu ogg $1, llonxitú $2, $4×$5 píxeles, $3", + "timedmedia-ogg-long-multiplexed": "Ficheru de soníu/videu ogg multiplexáu, $1, llonxitú $2, $4×$5 píxeles, $3", + "timedmedia-ogg-long-general": "Archivu multimedia ogg, llonxitú $2, $3", + "timedmedia-ogg-long-error": "Ficheru Ogg inválidu: $1", + "timedmedia-ogg-long-no-streams": "Ficheru multimedia Ogg. Atención: Nun se reconoció nengún codec de los usaos nesti ficheru.", + "timedmedia-webm-short-video": "Ficheru de videu WebM $1, $2", + "timedmedia-webm-long-video": "Ficheru de soníu/videu WebM, $1, llonxitú $2, $4 × $5 pixels, $3 total", + "timedmedia-flac-short-audio": "Ficheru de soníu FLAC, $1", + "timedmedia-flac-long-audio": "Ficheru de soníu FLAC, duración $1, $2 total", + "timedmedia-wav-short-audio": "Ficheru de soníu WAV, $1", + "timedmedia-wav-long-audio": "Ficheru de soníu WAV, duración $1, $2 total", + "timedmedia-wav-pcm-required": "Namái pue xubir WAV tipu PCM (Pulse Code Modulation).", + "timedmedia-mp4-short-video": "Archivu de videu MP4 $1, $2", + "timedmedia-mp4-long-video": "Ficheru de soníu/videu MP4, $1, llonxitú $2, $4 × $5 pixels, $3 total", + "timedmedia-no-player-js": "Sentimoslo, o'l to navegador tien desactiváu JavaScript o nun tienes un reproductor compatible.
\nPues descargar el clip o descargar un reproductor pa reproducir el clip nel to navegador.", + "timedmedia-more": "Más...", + "timedmedia-dismiss": "Zarrar", + "timedmedia-download": "Descargar archivu", + "timedmedia-play-media": "Reproducir el mediu", + "timedmedia-desc-link": "Tocante a esti archivu", + "timedmedia-oggThumb-version": "OggHandler requier oggThumb version $1 o mayor.", + "timedmedia-oggThumb-failed": "oggThumb nun pudo crear la miniatura.", + "timedmedia-status-header": "Estáu de trescodificación", + "timedmedia-update-status": "Anovar l'estáu de trescodificación", + "timedmedia-status": "Estáu", + "timedmedia-status-unknown": "Estáu desconocíu", + "timedmedia-transcodebitrate": "Tasa de bits", + "timedmedia-transcodeduration": "Tiempu de codificación", + "timedmedia-transcodeinfo": "Formatu", + "timedmedia-actions": "Aiciones", + "timedmedia-direct-link": "Descargar", + "timedmedia-not-ready": "Nun ta preparáu", + "timedmedia-completed-on": "Completada a les $1", + "timedmedia-error-on": "Error a les $1", + "timedmedia-started-transcode": "Principió hai $1. $2", + "timedmedia-percent-done": "Fecho alredor del $1%", + "timedmedia-in-job-queue": "Amestáu a la cola de trabayos hai $1", + "timedmedia-unknown-target-size": "Tamañu del destín desconocíu; $1 codificáu", + "timedmedia-days": "{{PLURAL:$1|1 día|$1 díes}}", + "timedmedia-hours": "{{PLURAL:$1|1 hora|$1 hores}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minutu|$1 minutos}}", + "timedmedia-seconds": "{{PLURAL:$1|1 segundu|$1 segundos}}", + "timedmedia-reset": "Reaniciar la trescodificación", + "timedmedia-reset-confirm": "Al reaniciar esta trescodificación desaniciará tolos ficheros esistentes (si los hai), y volverá a amestar la trescodificación a la cola de trabayos. Llevará un tiempu volver a trescodificar.

\n¿Ta seguru de que quier siguir?", + "timedmedia-reset-error": "Error al reaniciar el trabayu de trescodificación.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Fonte $1", + "timedmedia-source-file-desc": "Ficheru $1 orixinal, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Ficheru $1 orixinal ($2)", + "timedmedia-derivative-desc-160p.ogv": "Videu Ogg d'anchu de banda baxu (160P)", + "timedmedia-derivative-desc-240p.ogv": "Videu Ogg tresmitible pela web (240P)", + "timedmedia-derivative-desc-360p.ogv": "Videu Ogg tresmitible pela web (360P)", + "timedmedia-derivative-desc-480p.ogv": "Videu Ogg tresmitible pela web (480P)", + "timedmedia-derivative-desc-720p.ogv": "Videu Ogg descargable d'alta calida (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Videu Ogg descargable'n Full HD (1080P)", + "timedmedia-derivative-desc-160p.webm": "Videu WebM tresmitible pela web (160P)", + "timedmedia-derivative-desc-360p.webm": "Videu WebM tresmitible pela web (360P)", + "timedmedia-derivative-desc-480p.webm": "Videu WebM tresmitible pela web (480P)", + "timedmedia-derivative-desc-720p.webm": "Videu WebM descargable d'alta calida (720P)", + "timedmedia-derivative-desc-1080p.webm": "WebM descargable'n Full HD (1080P)", + "timedmedia-derivative-desc-2160p.webm": "WebM descargable'n Full 4K (2160P)", + "timedmedia-derivative-desc-360p.vp9.webm": "WebM VP9 tresmitible pela web (360P)", + "timedmedia-derivative-desc-480p.vp9.webm": "WebM VP9 tresmitible pela web (480P)", + "timedmedia-derivative-desc-720p.vp9.webm": "WebM VP9 tresmitible'n HD (720P)", + "timedmedia-derivative-desc-1080p.vp9.webm": "WebM VP9 tresmitible'n Full HD (1080P)", + "timedmedia-derivative-desc-2160p.vp9.webm": "WebM VP9 tresmitible'n Full 4K (2160P)", + "timedmedia-derivative-desc-320p.mp4": "MP4 compatible colos preseos (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 tresmitible pela web (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 de calidá HD (720P)", + "timedmedia-derivative-desc-1080p.mp4": "MP4 de calidá Full HD (1080P)", + "timedmedia-derivative-desc-2160p.mp4": "MP4 de calidá Full 4K (2160P)", + "timedmedia-subtitle-new": "Crear una traducción nueva o editar la esistente", + "timedmedia-subtitle-new-desc": "Seleicione la llingua y calque nel botón '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Dir", + "timedmedia-subtitle-language": "Subtítulos en $1 ($2)", + "timedmedia-subtitle-no-video": "Nun hai nengún videu asociáu cola páxina de subtítulos actual", + "timedmedia-subtitle-no-subtitles": "Anguaño nun hai subtítulos en $1 pa esti videu, pue [{{fullurl:{{FULLPAGENAME}}|action=edit}} editar esta páxina] p'amestalos", + "timedmedia-subtitle-remote": "El testu cronometráu d'esti ficheru ta agospiáu en $1", + "timedmedia-subtitle-remote-link": "Pue [$1 ver la páxina de descripción] d'esti ficheru en $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 videu|$1 videos}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 videu|$1 videos}} Ogg", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 videu|$1 videos}} WebM", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 trescodificación|$1 trescodificaciones}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 trescodificación activa|$1 trescodificaciones actives}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 trescodificación|$1 trescodificaciones}} na cola", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 trescodificación fallida|$1 trescodificaciones fallíes}}", + "timedmedia-file": "Ficheru", + "timedmedia-audios": "{{PLURAL:$1|$1 ficheru de soníu|$1 ficheros de soníu}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 ficheru de soníu Ogg|$1 ficheros de soníu Ogg}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 ficheru de soníu FLAC|$1 ficheros de soníu FLAC}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 ficheru de soníu WAV|$1 ficheros de soníu WAV}}", + "right-transcode-reset": "Reaniciar los videos fallíos o trescodificaos pa que vuelvan a ponese na cola de trabayos.", + "right-transcode-status": "Ver [[Special:TimedMediaHandler|información sobre l'actividá de trescodificación actual]]", + "action-transcode-status": "ver l'estáu actual de la trescodificación", + "orphanedtimedtext": "Páxines güérfanes de TimedText", + "orphanedtimedtext-summary": "Llista de páxines de [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] que nun tienen el correspondiente ficheru.", + "orphanedtimedtext-unsupported": "Esta páxina especial sólo tien sofitu con bases de datos MySQL.", + "orphanedtimedtext-notimedtext": "TimedText nun ta activáu nesta wiki.", + "apihelp-query+transcodestatus-description": "Recibir l'estáu de transcode pa una páxina de ficheru determinada.", + "apihelp-query+transcodestatus-example-1": "Recibir l'estáu de transcode pa [[:File:Clip.webm]]", + "apihelp-query+videoinfo-description": "Estende imageinfo pa incluir la información de fonte de videu (derivaos)", + "apihelp-query+videoinfo-param-prop": "Que información de videu algamar:\n;timestamp:Amiesta la marca d'hora de la versión xubida.\n;user:Amiesta l'usuariu que xubió la versión del videu.\n;userid:Amiesta la ID del usuariu que xubió la versión del videu.\n;comment:Comentariu de la versión.\n;parsedcomment:Comentariu analizáu na versión.\n;canonicaltitle:Amiesta'l títulu canónicu del ficheru de videu.\n;url:Da la URL del videu y la páxina de descripción.\n;size:Amiesta'l tamañu del videu en bytes, l'altor y anchor. Si ye aplicable, amiéstase la cuenta de páxines y duración.\n;dimensions:Alcuñu pa \"size\".\n;sha1:Amiesta'l hash SHA-1 del videu.\n;mime:Amiesta'l tipu MIME del videu.\n;thumbmime:Amiesta'l tipu MIME de la miniatura del videu (rique url y parámetru $1urlwidth).\n;mediatype:Amiesta'l tipu de mediu del videu.\n;metadata:Llista de metadatos Exif de la versión del videu.\n;commonmetadata:Llista de metadatos xenéricos del formatu de ficheru de la versión del videu.\n;extmetadata:Llista de metadatos con formatu combinaos dende fontes diverses. Los resultaos tán en formatu HTML.\n;archivename:Amiesta'l nome de la versión d'archivu pa versiones distintes de la última.\n;bitdepth:Amiesta la fondura de bit de la versión.\n;uploadwarning:Usáu pola páxina [[Special:Upload]] p'algamar información d'un ficheru esistente. Nun ta previstu pa usase fuera del nucleu de MediaWiki.\n;derivatives:Amiesta una tabla colos diferentes formatos y calidaes disponibles d'un ficheru de soníu o videu.", + "apihelp-query+videoinfo-example-1": "Buscar información tocante a [[:File:Folgers.ogv]]", + "apihelp-transcodereset-description": "Los usuarios col permisu 'transcode-reset' pueden reaniciar y volver a executar un trabayu transcode.", + "apihelp-transcodereset-param-title": "Títulu del ficheru de multimedia.", + "apihelp-transcodereset-param-transcodekey": "La clave del transcode que quies reaniciar. Cuéyela de [[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "Reaniciar tolos transcodes de [[:File:Clip.webm]]", + "apihelp-transcodereset-example-2": "Reaniciar la clave transcode '360_560kbs.webm' de [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/av.json b/extensions/TimedMediaHandler/i18n/av.json new file mode 100644 index 00000000..2a3131e3 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/av.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Gazimagomedov" + ] + }, + "timedmedia-source-file": "$1 ицц", + "timedmedia-source-audio-file-desc": "Аслияб $1-файл ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/avk.json b/extensions/TimedMediaHandler/i18n/avk.json new file mode 100644 index 00000000..313cd0c9 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/avk.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Sab" + ] + }, + "timedmedia-download": "Iyeltakkalvajara", + "timedmedia-desc-link": "Icde bat iyeltak" +} diff --git a/extensions/TimedMediaHandler/i18n/awa.json b/extensions/TimedMediaHandler/i18n/awa.json new file mode 100644 index 00000000..fe0c3d1a --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/awa.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "1AnuraagPandey" + ] + }, + "timedmedia-no-player-js": "क्षमा करें, आपके ब्राउज़र में या तो जावास्क्रिप्ट अक्षम है या समर्थित प्लेयर नहीं है।
\nआप क्लिप को डाउनलोड कर सकते हैं अथवा क्लिप को अपने ब्राउज़र में चलाने के लिए एक प्लेयर डाउनलोड कर सकते हैं।", + "timedmedia-source-file": "$1 स्रोत", + "timedmedia-source-audio-file-desc": "मूल $1 फ़ाइल ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/az.json b/extensions/TimedMediaHandler/i18n/az.json new file mode 100644 index 00000000..f54d2aed --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/az.json @@ -0,0 +1,13 @@ +{ + "@metadata": { + "authors": [ + "Cekli829", + "Dağlı95", + "Wertuose" + ] + }, + "timedmedia-no-player-js": "Bağışlayın, sizin veb brauzeriniz JavaScript texnologiyasını dəstəkləmir və ya bu əlavə deaktiv edilib.\nSiz dinləmək istədiyiniz faylı yükləyə və ya brauzerinizə, lazımlı əlavəni quraşdıra bilərsiz.", + "timedmedia-status": "Status", + "timedmedia-source-file": "$1 mənbəyi", + "timedmedia-source-audio-file-desc": "Orijinal $1-fayl ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/azb.json b/extensions/TimedMediaHandler/i18n/azb.json new file mode 100644 index 00000000..d0c5b700 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/azb.json @@ -0,0 +1,11 @@ +{ + "@metadata": { + "authors": [ + "Amir a57", + "Koroğlu" + ] + }, + "timedmedia-no-player-js": "اوزگونلوک‌له، سیزین داراییجی‌نیزین جاوا ایسکریپت یازیلیمی چالیشماییر، یا دا سیزین دارییجی‌نیز بو یازیلیم‌دان آرداغلامیر.
\nسیز بو ویدیونو ایندیریب یا دا بیر اوینادیجی ایندیره‌بیلرسینیز.", + "timedmedia-source-file": "$1 کوک", + "timedmedia-source-audio-file-desc": "اصلی $1 فایل ( $2 )" +} diff --git a/extensions/TimedMediaHandler/i18n/ba.json b/extensions/TimedMediaHandler/i18n/ba.json new file mode 100644 index 00000000..10f406c5 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ba.json @@ -0,0 +1,14 @@ +{ + "@metadata": { + "authors": [ + "Haqmar", + "Рустам Нурыев" + ] + }, + "timedmedia-days": "{{PLURAL:$1|1=$1 көн|$1 көн}}", + "timedmedia-hours": "{{PLURAL:$1|1=$1 сәғәт|$1 сәғәт}}", + "timedmedia-minutes": "{{PLURAL:$1|1=$1 минут|$1 минут}}", + "timedmedia-seconds": "{{PLURAL:$1|1=$1 секунд|$1 секунд}}", + "timedmedia-source-file": "$1 сығанаҡ", + "timedmedia-source-audio-file-desc": "Оригинал $1-файл ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/bcc.json b/extensions/TimedMediaHandler/i18n/bcc.json new file mode 100644 index 00000000..11fbd1a8 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/bcc.json @@ -0,0 +1,24 @@ +{ + "@metadata": { + "authors": [ + "Mostafadaneshvar", + "Baloch Afghanistan" + ] + }, + "timedmedia-desc": "دسگیره په فایلان Ogg Theora و Vorbis, گون پخش کنوک جاوا اسکرسیپت", + "timedmedia-ogg-short-audio": "فایل صوتی Ogg $1، $2", + "timedmedia-ogg-short-video": "فایل تصویری Ogg $1، $2", + "timedmedia-ogg-short-general": "فایل مدیا Ogg $1، $2", + "timedmedia-ogg-long-audio": "اوجی جی $1 فایل صوتی, طول $2, $3", + "timedmedia-ogg-long-video": "اوجی جی $1 فایل ویدیو, طول $2, $4×$5 پیکسل, $3", + "timedmedia-ogg-long-multiplexed": "اوجی جی چند دابی فایل صوت/تصویر, $1, طول $2, $4×$5 پیکسل, $3 کل", + "timedmedia-ogg-long-general": "اوجی جی فایل مدیا, طول $2, $3", + "timedmedia-ogg-long-error": "نامعتبرین فایل اوجی جی: $1", + "timedmedia-more": "گیشتیر...", + "timedmedia-dismiss": "بستین", + "timedmedia-download": "ئیر کورتین پایلی", + "timedmedia-desc-link": "ای فایل باره", + "timedmedia-subtitle-new-go": "برا", + "orphanedtimedtext": "یتیم TimedText دیمان", + "orphanedtimedtext-summary": "ها دیمانی لیست که [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] متناظرین پایل ندارنت ." +} diff --git a/extensions/TimedMediaHandler/i18n/bcl.json b/extensions/TimedMediaHandler/i18n/bcl.json new file mode 100644 index 00000000..36042848 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/bcl.json @@ -0,0 +1,90 @@ +{ + "@metadata": { + "authors": [ + "Filipinayzd", + "Geopoet" + ] + }, + "timedmedia-desc": "Parakapot para sa audio, bidyo asin pinag-orasan na teksto, na igwa nin pormat kan suporta para sa WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1 sagunson nin tanog, $2", + "timedmedia-ogg-short-video": "Ogg $1 sagunson nin bidyo, $2", + "timedmedia-ogg-short-general": "Ogg $1 sagunson nin midya, $2", + "timedmedia-ogg-long-audio": "Ogg $1 sagunson nin tanog, an laba $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 sagunson nin bidyo, an laba $2, $4 x $5 piksel, $3", + "timedmedia-ogg-long-multiplexed": "Ogg pinaghalong sagunson nin audio/bidyo, $1, an laba $2, $4 x $5 piksel, $3", + "timedmedia-ogg-long-general": "Ogg sagunson nin midya, an laba $2, $3", + "timedmedia-ogg-long-error": "Imbalidong sagunson nin Ogg: $1", + "timedmedia-webm-short-video": "Webm $1 sagunson nin bidyo, $2", + "timedmedia-webm-long-video": "WebM sagunson nin audio/bidyo, $1, an laba $2, $4 x $5 piksel, $3 sa bilog na kagabsan", + "timedmedia-flac-short-audio": "FLAC sagunson nin audio, $1", + "timedmedia-flac-long-audio": "FLAC sagunson nin audio, an laba $1, $2 sa bilog na kagabsan", + "timedmedia-wav-short-audio": "WAV sagunson nin audio, $1", + "timedmedia-wav-long-audio": "WAV sagunson nin audio, an laba $1, $2 sa bilog na kagabsan", + "timedmedia-wav-pcm-required": "Ika makakarga sana nin PCM (Pulse Code Modulation) WAV.", + "timedmedia-mp4-short-video": "MP$ $1 sagunson nin bidyo, $2", + "timedmedia-mp4-long-video": "MP4 sagunson nin audio/bidyo, $1, an laba $2, $4 x $5 piksel, $3 sa bilog na kagabsan", + "timedmedia-no-player-js": "Sori po, an saimong pangilyaw baya pinagpundo an JavaScript o mayo gayod nin arinmang pinagsuportaran na parapaandar.
Ika makakapagkarga nin klip o baya magkarga nin sarong parapaandar tanganing paandaron an klip kan saimong kilyawan.", + "timedmedia-more": "Dakolon pa..", + "timedmedia-dismiss": "Isara", + "timedmedia-download": "Ikarga an sagunson", + "timedmedia-play-media": "Paandaron an midya", + "timedmedia-desc-link": "Mapanungod kaining sagunson", + "timedmedia-oggThumb-version": "OggHandler minahagad sa oggThumb bersyon na $1 o kahurihi.", + "timedmedia-oggThumb-failed": "oggThumb nagpalya sa pagmukna kan sadit na ladawan", + "timedmedia-status-header": "Kamugtakan kan panggantaw na kodigo", + "timedmedia-update-status": "Sumpayan an kamugtakan kan panggantaw na kodigo", + "timedmedia-status": "Kamugtakan", + "timedmedia-status-unknown": "Bakong aram na kamugtakan", + "timedmedia-transcodeinfo": "Deribatibong deskripsyon kan panggantaw na kodigo", + "timedmedia-actions": "Mga aksyon", + "timedmedia-direct-link": "Ikarga an deribatibo", + "timedmedia-not-ready": "Dae pa handa", + "timedmedia-completed-on": "Pinagkumpletong panggantaw na kodigo $1", + "timedmedia-error-on": "Kasalaan sa panggantaw na kodigo sa $1", + "timedmedia-started-transcode": "An panggantaw na kodigo pinagpoonan mga $1 an nakaagi. $2", + "timedmedia-percent-done": "Haros $1% an tapos na", + "timedmedia-in-job-queue": "Pinagdugang sa haralatan nin Trabaho mga $1 an nakaagi", + "timedmedia-unknown-target-size": "Dae aram an sukol kan target, $1 pinagkodigo na", + "timedmedia-days": "{{PLURAL:$1|1 aldaw|$1 mga aldaw}}", + "timedmedia-hours": "{{PLURAL:$1|1 oras|$1 mga oras}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuto|$1 minutos}}", + "timedmedia-seconds": "{{PLURAL:$1|1 segundo|$1 segundos}}", + "timedmedia-reset": "Pakibaguha an panggantaw na kodigo", + "timedmedia-reset-confirm": "Sa pagbabago kaining panggantaw na kodigo magtatangkas nin arinman na eksistidong sagunson (kun yaon), asin magdudugang otro kan panggantaw na kodigo sa haralatan nin trabaho. Ini minakaipo nin nagkapirang oras tangani na otrong ipaggantaw an kodigo.

Segurado ka na muya mong magpadagos?", + "timedmedia-reset-error": "Kasalaan sa pagbabago kan trabaho nin panggantaw na kodigo.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 pinagkuanan", + "timedmedia-source-file-desc": "Orihinal $1 na sagunson, $2 x $3 ($4)", + "timedmedia-source-audio-file-desc": "Orihinal $1 na sagunson ($2)", + "timedmedia-derivative-desc-160p.ogv": "Hababaong kalawigan Ogg bidyo (160P)", + "timedmedia-derivative-desc-360p.ogv": "Maantabayanan sa Web na bidyo kan Ogg (360P)", + "timedmedia-derivative-desc-480p.ogv": "Maantabayanan sa Web na bidyo kan Ogg (480P)", + "timedmedia-derivative-desc-720p.ogv": "Halangkaw na kalidad na maikakargang bidyo kan Ogg (720P)", + "timedmedia-derivative-desc-160p.webm": "Maantabayanan sa Web na bidyo kan WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "Maantabayanan sa Web na bidyo kan WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "Maantabayanan sa Web na bidyo kan WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "Halangkaw na kalidad na maikakargang bidyo kan WebM (720P)", + "timedmedia-derivative-desc-320p.mp4": "Masayon sa aparato MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "Maantabayanan sa Web na MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "HD na kalidad MP4 (720P)", + "timedmedia-subtitle-new": "Magmukna nin baguhong dakit-taramon o liwaton an eksistido na", + "timedmedia-subtitle-new-desc": "Magpili nin lengguwahe asin pinduton an '''{{int:Timedmedia-subtitle-new-go}}''' na dutdutan", + "timedmedia-subtitle-new-go": "Magduman", + "timedmedia-subtitle-language": "$1 ($2) mga sub-titulo", + "timedmedia-subtitle-no-video": "Mayo tabing bidyo na asosyado na igwa nin sa ngunyan na subtitulo kan pahina", + "timedmedia-subtitle-no-subtitles": "Mayo tabi nin sa ngunyan na mga subtitulo sa $1 para kaining bidyo, ika tabi [{{fullurl:{{FULLPAGENAME}}|action=edit}} makapagliwat kaining pahina] tanganing maidugang sinda", + "timedmedia-subtitle-remote": "Pinag-orasang teksto para kaining sagunson iyo an pinagbunsod sa $1", + "timedmedia-subtitle-remote-link": "Ika tabi [$1 makakatanaw sa pahina kan deskripsyon] para kaining sagunson sa $2", + "timedmediahandler": "Pinag-orasan na Parakapot kan Midya", + "timedmedia-videos": "{{PLURAL:$1|$1 bidyo|$1 mga bidyo}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg bidyo|$1 Ogg mga bidyo}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM bidyo|$1 WebM mga bidyo}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 panggantaw na kodigo|$1 panggantaw na mga kodigo}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 nagdadalagan na panggantaw na kodigo|$1 nagdadalagan na panggantaw na mga kodigo}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 pinaghahalat na panggantaw na kodigo|$1 pinaghahalat na panggantaw na mga kodigo}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 nagpalyang panggantaw na kodigo|$1 nagpalyang panggantaw na mga kodigo}}", + "timedmedia-file": "Sagunson", + "right-transcode-reset": "An pagbágo nagpalya o an pinaggantaw na kodigong mga bidyo kaya sinda pinagsingit giraray sa haralatan kan trabaho.", + "right-transcode-status": "Patanaw [[Special:TimedMediaHandler|impormasyon mapanungod sa ngunyan na aktibidad kan panggantaw na kodigo]]", + "action-transcode-status": "tanawon an sa ngunyan na kamugtakan kan panggangtaw na kodigo" +} diff --git a/extensions/TimedMediaHandler/i18n/be-tarask.json b/extensions/TimedMediaHandler/i18n/be-tarask.json new file mode 100644 index 00000000..1bd50c1d --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/be-tarask.json @@ -0,0 +1,71 @@ +{ + "@metadata": { + "authors": [ + "EugeneZelenko", + "Jim-by", + "Red Winged Duck", + "Wizardist" + ] + }, + "timedmedia-desc": "Апрацоўшчык аўдыё, відэа і субтытраў у фарматах WebM, Ogg Theora, Vorbis і SRT", + "timedmedia-ogg-short-audio": "Аўдыё-файл Ogg $1, $2", + "timedmedia-ogg-short-video": "Відэа-файл у фармаце Ogg $1, $2", + "timedmedia-ogg-short-general": "Мэдыяфайл Ogg $1, $2", + "timedmedia-ogg-long-audio": "аўдыё-файл Ogg $1, даўжыня $2, $3", + "timedmedia-ogg-long-video": "відэа-файл Ogg $1, даўжыня $2, $4×$5 піксэляў, $3", + "timedmedia-ogg-long-multiplexed": "мультыплексны аўдыё/відэа-файл Ogg, $1, даўжыня $2, $4×$5 піксэляў, усяго $3", + "timedmedia-ogg-long-general": "мэдыяфайл Ogg, даўжыня $2, $3", + "timedmedia-ogg-long-error": "Няслушны файл у фармаце Ogg: $1", + "timedmedia-webm-short-video": "WebM $1 відэафайл, $2", + "timedmedia-webm-long-video": "Аўдыё/відэа-файл WebM, $1, працягласьць $2, $4×$5 піксэлаў, усяго $3", + "timedmedia-flac-short-audio": "Аўдыёфайл FLAC, $1", + "timedmedia-flac-long-audio": "Аўдыёфайл FLAC, працягласьць $1, усяго $2", + "timedmedia-mp4-short-video": "MP4 $1 відэафайл, $2", + "timedmedia-no-player-js": "Прабачце, але ў Вашым браўзэры адключаны JavaScript альбо няма неабходнага прайгравальніка.
\nВы можаце загрузіць кліп ці загрузіць прайгравальнік для прайграваньня кліпу ў Вашым браўзэры.", + "timedmedia-more": "Болей…", + "timedmedia-dismiss": "Назад", + "timedmedia-download": "Загрузіць файл", + "timedmedia-play-media": "Прайграць", + "timedmedia-desc-link": "Інфармацыя пра гэты файл", + "timedmedia-oggThumb-version": "OggHandler патрабуе oggThumb вэрсіі $1 ці больш позьняй.", + "timedmedia-oggThumb-failed": "oggThumb не атрымалася стварыць мініятуру.", + "timedmedia-status-header": "Статус транскадаваньня", + "timedmedia-update-status": "Абнавіць статус транскадаваньня", + "timedmedia-status": "Статус", + "timedmedia-status-unknown": "Статус невядомы", + "timedmedia-transcodeinfo": "Фармат", + "timedmedia-actions": "Дзеяньні", + "timedmedia-direct-link": "Спампаваць", + "timedmedia-not-ready": "Не гатовы", + "timedmedia-completed-on": "Завершана $1", + "timedmedia-error-on": "Памылка ў $1", + "timedmedia-started-transcode": "Пачалося $1 таму. $2", + "timedmedia-percent-done": "Гатова каля $1%", + "timedmedia-in-job-queue": "Дададзена ў чаргу заданьняў $1 таму", + "timedmedia-unknown-target-size": "Выніковы памер невядомы, $1 закадавана", + "timedmedia-days": "$1 {{PLURAL:$1|дзень|дні|дзён}}", + "timedmedia-hours": "$1 {{PLURAL:$1|гадзіну|гадзіны|гадзінаў}}", + "timedmedia-minutes": "$1 {{PLURAL:$1|хвіліну|хвіліны|хвілінаў}}", + "timedmedia-seconds": "$1 {{PLURAL:$1|сэкунду|сэкунды|сэкундаў}}", + "timedmedia-reset": "Перазапусьціць транскадаваньне", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Крыніца $1", + "timedmedia-source-file-desc": "Арыгінал у $1, $2×$3 ($4)", + "timedmedia-source-audio-file-desc": "Арыгінал у $1 ($2)", + "timedmedia-derivative-desc-160p.ogv": "Нізкапаточнае Ogg-відэа (160P)", + "timedmedia-derivative-desc-360p.ogv": "Паточнае Ogg-відэа (360 пкс)", + "timedmedia-derivative-desc-480p.ogv": "Паточнае Ogg-відэа (480 пкс)", + "timedmedia-derivative-desc-720p.ogv": "Высакаякаснае Ogg-відэа для загрузкі (720p)", + "timedmedia-derivative-desc-360p.webm": "Паточны WebM (360p)", + "timedmedia-derivative-desc-480p.webm": "Паточны WebM (480p)", + "timedmedia-derivative-desc-720p.webm": "Высакаякаснае WebM-відэа для загрузкі (720p)", + "timedmedia-derivative-desc-720p.mp4": "MP4 HD-якасьці (720P)", + "timedmedia-subtitle-new": "Стварыць новы пераклад ці рэдагаваць існуючы", + "timedmedia-subtitle-new-go": "Пачаць", + "timedmedia-subtitle-language": "Субтытры ($2) на $1", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1}} відэа", + "timedmedia-file": "Файл", + "right-transcode-reset": "скіданьне няўдалых або транскадаваных відэа, каб яны ізноў трапілі ў чаргу заданьняў", + "right-transcode-status": "прагляд [[Special:TimedMediaHandler|зьвестак пра актуальны стан перакадаваньня]]" +} diff --git a/extensions/TimedMediaHandler/i18n/be.json b/extensions/TimedMediaHandler/i18n/be.json new file mode 100644 index 00000000..d24bc36e --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/be.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Чаховіч Уладзіслаў" + ] + }, + "timedmedia-no-player-js": "На жаль, у вашым браўзеры адключаны JavaScript, ці не маецца патрабаванага прайгравальніка.
\nВы можаце загрузіць ролік ці загрузіць прайгравальнік для прайгравання роліка ў браўзеры.", + "timedmedia-source-file": "Крыніца $1", + "timedmedia-source-audio-file-desc": "Арыгінальны $1-файл ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/bg.json b/extensions/TimedMediaHandler/i18n/bg.json new file mode 100644 index 00000000..0ef16819 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/bg.json @@ -0,0 +1,24 @@ +{ + "@metadata": { + "authors": [ + "Borislav", + "DCLXVI", + "Spiritia", + "StanProg" + ] + }, + "timedmedia-desc": "Приложение за файлове тип Ogg Theora и Vorbis, с плейър на JavaScript", + "timedmedia-ogg-short-audio": "Ogg $1 звуков файл, $2", + "timedmedia-ogg-short-video": "Ogg $1 видео файл, $2", + "timedmedia-ogg-long-audio": "Ogg $1 звуков файл, продължителност $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 видео файл, продължителност $2, $4×$5 пиксела, $3", + "timedmedia-ogg-long-general": "Мултимедиен файл в ogg формат с дължина $2, $3", + "timedmedia-ogg-long-error": "Невалиден ogg файл: $1", + "timedmedia-no-player-js": "За съжаление, браузъра ви има изключен JavaScript или не разполага с необходимия плейър.
\nМожете да изтеглите клипа или да изтеглите плейър за да пуснете клипа в браузъра си.", + "timedmedia-more": "Повече...", + "timedmedia-dismiss": "Затваряне", + "timedmedia-download": "Изтегляне на файла", + "timedmedia-desc-link": "Информация за файла", + "timedmedia-source-file": "Източник на $1", + "timedmedia-source-audio-file-desc": "Оригинален $1-файл ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/bgn.json b/extensions/TimedMediaHandler/i18n/bgn.json new file mode 100644 index 00000000..da70a08d --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/bgn.json @@ -0,0 +1,11 @@ +{ + "@metadata": { + "authors": [ + "Baloch Afghanistan", + "Ibrahim khashrowdi" + ] + }, + "timedmedia-no-player-js": "بخشایت اۆڑی، شمی بروزیر ئی سرا جاوا اسکریپت ئی سافٹ ایر پئال نه اینت ، و یا که شمی بروزیر شه ای پورمت ئا پُشتیوانی ئه نه کنت .
\nشما ئه توانیت که ای ویڈیو ئا ائیر بکنیت و یا یک پخش کنۆک ئی په ای ویڈیو یی اجرا ئا ائیر بکنیت.", + "timedmedia-source-file": "$1 زهي", + "timedmedia-source-audio-file-desc": "اورجینال $1 فایل ( $2 )" +} diff --git a/extensions/TimedMediaHandler/i18n/bho.json b/extensions/TimedMediaHandler/i18n/bho.json new file mode 100644 index 00000000..b562c221 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/bho.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "SatyamMishra" + ] + }, + "timedmedia-source-audio-file-desc": "ओरिजनल $1 फाइल ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/bn.json b/extensions/TimedMediaHandler/i18n/bn.json new file mode 100644 index 00000000..d69249bb --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/bn.json @@ -0,0 +1,48 @@ +{ + "@metadata": { + "authors": [ + "Bellayet", + "Zaheen", + "Aftab1995", + "Aftabuzzaman" + ] + }, + "timedmedia-ogg-short-audio": "অগ $1 সাউন্ড ফাইল, $2", + "timedmedia-ogg-short-video": "অগ $1 ভিডিও ফাইল, $2", + "timedmedia-ogg-short-general": "অগ $1 মিডিয়া ফাইল, $2", + "timedmedia-ogg-long-audio": "অগ $1 সাউন্ড ফাইল, দৈর্ঘ্য $2, $3", + "timedmedia-ogg-long-video": "অগ $1 ভিডিও ফাইল, দৈর্ঘ্য $2, $4×$5 পিক্সেল, $3", + "timedmedia-ogg-long-multiplexed": "অগ মাল্টিপ্লেক্সকৃত অডিও/ভিডিও ফাইল, $1, দৈর্ঘ্য $2, $4×$5 পিক্সেল, $3 সামগ্রিক", + "timedmedia-ogg-long-general": "অগ মিডিয়া ফাইল, দৈর্ঘ্য $2, $3", + "timedmedia-ogg-long-error": "অবৈধ অগ ফাইল: $1", + "timedmedia-mp4-short-video": "MP4 $1 ভিডিও ফাইল, $2", + "timedmedia-mp4-long-video": "MP4 অডিও/ভিডিও ফাইল, $1, দৈর্ঘ্য $2, $4 × $5 পিক্সেল, $3 সামগ্রিক", + "timedmedia-no-player-js": "দুঃখিত, আপনার ব্রাউজারে জাভাস্ক্রিপ্ট নিষ্ক্রিয় করা আছে অথবা কোনো সমর্থিত প্লেয়ার নেই।
\nআপনি আপনার ব্রাউজারে ক্লিপটি চালানোর জন্য ক্লিপটি ডাউনলোড করতে পারেন বা একটি প্লেয়ার ডাউনলোড করতে পারেন।", + "timedmedia-more": "আরও...", + "timedmedia-dismiss": "বন্ধ", + "timedmedia-download": "ফাইল ডাউনলোড করুন", + "timedmedia-play-media": "মিডিয়া চালান", + "timedmedia-desc-link": "এই ফাইলের বৃত্তান্ত", + "timedmedia-status-header": "ট্রান্সকোড অবস্থা", + "timedmedia-update-status": "ট্রান্সকোড অবস্থা হালনাগাদ করুন", + "timedmedia-status": "অবস্থা", + "timedmedia-status-unknown": "অবস্থা অজানা", + "timedmedia-transcodeinfo": "ব্যুৎপন্ন বর্ণনা ট্রান্সকোড", + "timedmedia-actions": "কার্য", + "timedmedia-direct-link": "ব্যুৎপন্ন ডাউনলোড করুন", + "timedmedia-not-ready": "তৈরি নয়", + "timedmedia-percent-done": "প্রায় $1% করা হয়েছে", + "timedmedia-days": "{{PLURAL:$1|১ দিন|$1 দিন}}", + "timedmedia-hours": "{{PLURAL:$1|১ ঘণ্টা|$1 ঘণ্টা}}", + "timedmedia-minutes": "{{PLURAL:$1|১ মিনিট|$1 মিনিট}}", + "timedmedia-seconds": "{{PLURAL:$1|১ সেকেন্ড|$1 সেকেন্ড}}", + "timedmedia-source-file": "$1 উৎস", + "timedmedia-source-audio-file-desc": "মূল $1 ফাইল ($2)", + "timedmedia-derivative-desc-240p.ogv": "ওয়েব স্ট্রিমযোগ্য অগ ভিডিও (২৪০P)", + "timedmedia-subtitle-new-go": "যাও", + "timedmedia-subtitle-language": "$1 ($2) সাবটাইটেল", + "timedmedia-videos": "{{PLURAL:$1|$1টি ভিডিও}}", + "timedmedia-file": "ফাইল", + "timedmedia-audios": "{{PLURAL:$1|$1টি অডিও ফাইল}}", + "action-transcode-status": "বর্তমান ট্রান্সকোডিংয়ের অবস্থা দেখুন" +} diff --git a/extensions/TimedMediaHandler/i18n/br.json b/extensions/TimedMediaHandler/i18n/br.json new file mode 100644 index 00000000..dd74c0c5 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/br.json @@ -0,0 +1,51 @@ +{ + "@metadata": { + "authors": [ + "Fohanno", + "Fulup", + "Y-M D" + ] + }, + "timedmedia-desc": "Skor video, son ha testenn sinkronelaet gant ar furmadoù WebM, Ogg Theora hag SRT skoret", + "timedmedia-ogg-short-audio": "Restr son Ogg $1, $2", + "timedmedia-ogg-short-video": "Restr video Ogg $1, $2", + "timedmedia-ogg-short-general": "Restr media Ogg $1, $2", + "timedmedia-ogg-long-audio": "Restr son Ogg $1, pad $2, $3", + "timedmedia-ogg-long-video": "Restr video Ogg $1, pad $2, $4×$5 piksel, $3", + "timedmedia-ogg-long-multiplexed": "Restr Ogg klevet/video liesplezhet $1, pad $2, $4×$5 piksel, $3 hollad", + "timedmedia-ogg-long-general": "Restr media Ogg, pad $2, $3", + "timedmedia-ogg-long-error": "Restr ogg direizh : $1", + "timedmedia-webm-short-video": "Restr video WebM $1, $2", + "timedmedia-webm-long-video": "Restr video/son WebM, $1, pad $2, $4 × $5 piksel, $3 en holl", + "timedmedia-no-player-js": "Ho tigarez, pe eo diweredekaet JavaScript war ho merdeer pen n'eo ket skoret lenner ebet gantañ.
\nPellgargañ ar c'hlip a c'hallit pe pellgargañ ul lenner da lenn ar c'hlip gant ho merdeer.", + "timedmedia-more": "Muioc'h...", + "timedmedia-dismiss": "Serriñ", + "timedmedia-download": "Pellgargañ ar restr", + "timedmedia-play-media": "Lenn ar media", + "timedmedia-desc-link": "Diwar-benn ar restr-mañ", + "timedmedia-oggThumb-version": "Rekis eo stumm $1 oggThumb, pe nevesoc'h, evit implijout OggHandler.", + "timedmedia-oggThumb-failed": "N'eo ket deuet a-benn oggThumb da grouiñ ar munud.", + "timedmedia-status": "Statud", + "timedmedia-status-unknown": "Statud dianav", + "timedmedia-actions": "Oberoù", + "timedmedia-not-ready": "N'eo ket prest", + "timedmedia-percent-done": "War-dro $1 % zo bet graet", + "timedmedia-days": "{{PLURAL:$1|1 devezh|$1 devezh}}", + "timedmedia-hours": "{{PLURAL:$1|1 eurvezh|$1 eurvezh}}", + "timedmedia-minutes": "{{PLURAL:$1|1 vunutenn|$1 munutenn}}", + "timedmedia-seconds": "$1 {{PLURAL:$1|eilenn|eilenn}}", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Mammenn $1", + "timedmedia-source-file-desc": "Orin $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Restr orin $1 ($2)", + "timedmedia-derivative-desc-160p.ogv": "Video Ogg izelgas (160P)", + "timedmedia-derivative-desc-360p.ogv": "Video Ogg lennus war-eeun war ar Gwiad (360p)", + "timedmedia-derivative-desc-480p.ogv": "Video Ogg lennus war-eeun war ar Gwiad (480p)", + "timedmedia-derivative-desc-720p.ogv": "Video Ogg da bellgargañ gant ur c'halite a-feson (720p)", + "timedmedia-derivative-desc-360p.webm": "WebM lennus war-eeun eus ar Gwiad (360p)", + "timedmedia-derivative-desc-480p.webm": "WebM lennus war-eeun eus ar Gwiad (480p)", + "timedmedia-derivative-desc-720p.webm": "Video WebM da bellgargañ gant ur c'halite a-feson (720p)", + "timedmedia-subtitle-new-go": "Mont", + "timedmedia-subtitle-language": "istitloù e $1 ($2)", + "timedmedia-file": "Restr" +} diff --git a/extensions/TimedMediaHandler/i18n/bs.json b/extensions/TimedMediaHandler/i18n/bs.json new file mode 100644 index 00000000..144f854e --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/bs.json @@ -0,0 +1,31 @@ +{ + "@metadata": { + "authors": [ + "CERminator", + "Palapa", + "Semso98" + ] + }, + "timedmedia-desc": "Upravljač za zvuk, video i vremenski tekst sa podrškom formata za WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1 zvučna datoteka, $2", + "timedmedia-ogg-short-video": "Ogg $1 video datoteka, $2", + "timedmedia-ogg-short-general": "Ogg $1 medijalna datoteka, $2", + "timedmedia-ogg-long-audio": "Ogg $1 zvučna datoteka, dužina $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 video datoteka, dužina $2, $4×$5 piksela, $3", + "timedmedia-ogg-long-multiplexed": "Ogg multipleksna zvučna/video datoteka, $1, dužina $2, $4×$5 piksela, $3 sveukupno", + "timedmedia-ogg-long-general": "Ogg medijalna datoteka, dužina $2, $3", + "timedmedia-ogg-long-error": "Nevaljana ogg datoteka: $1", + "timedmedia-wav-long-audio": "WAV audio datoteka, dužina $1, $2 sveukupno", + "timedmedia-wav-pcm-required": "Možete postaviti samo PCM (Pulse Code Modulation) WAV.", + "timedmedia-no-player-js": "Žao nam je, vaš preglednik ili je onemogućio JavaScript ili nema nijednog podržanog playera.
\nMožete učitati klip ili učitati player za reproduciranje klipa u vašem pregledniku.", + "timedmedia-more": "Više...", + "timedmedia-dismiss": "Zatvori", + "timedmedia-download": "Učitaj datoteku", + "timedmedia-desc-link": "O ovoj datoteci", + "timedmedia-oggThumb-version": "OggHandler zahtijeva oggThumb verziju $1 ili kasniju.", + "timedmedia-oggThumb-failed": "oggThumb nije uspio napraviti smanjeni pregled.", + "timedmedia-source-file": "$1 izvor", + "timedmedia-source-file-desc": "Originalna $1 datoteka, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Originalna $1 datoteka ($2)", + "timedmedia-subtitle-language": "$1 ($2) podnaslovi" +} diff --git a/extensions/TimedMediaHandler/i18n/ca.json b/extensions/TimedMediaHandler/i18n/ca.json new file mode 100644 index 00000000..6a72f2d0 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ca.json @@ -0,0 +1,44 @@ +{ + "@metadata": { + "authors": [ + "Aleator", + "Alvaro Vidal-Abarca", + "Paucabot", + "Pitort", + "SMP", + "Toniher", + "Vriullop", + "Joan manel", + "Macofe" + ] + }, + "timedmedia-desc": "Gestor de fitxers de vídeo, àudio i text sincronitzat amb suport pels formats WebM, Ogg Theora, Vorbi i srt", + "timedmedia-ogg-short-audio": "Fitxer OGG d'àudio $1, $2", + "timedmedia-ogg-short-video": "Fitxer OGG de vídeo $1, $2", + "timedmedia-ogg-short-general": "Fitxer multimèdia OGG $1, $2", + "timedmedia-ogg-long-audio": "Ogg $1 fitxer de so, llargada $2, $3", + "timedmedia-ogg-long-video": "Fitxer OGG de vídeo $1, llargada $2, $4×$5 píxels, $3", + "timedmedia-ogg-long-multiplexed": "Arxiu àudio/vídeo multiplex, $1, llargada $2, $4×$5 píxels, $3 de mitjana", + "timedmedia-ogg-long-general": "Fitxer multimèdia OGG, llargada $2, $3", + "timedmedia-ogg-long-error": "Fitxer OGG invàlid: $1", + "timedmedia-no-player-js": "Ho sentim, el vostre navegador té el JavaScript desactivat o bé no té cap reproductor compatible instal·lat.
\nPodeu $1\">descarregar el clip o descarregar un reproductor per a veure el vídeo al vostre navegador.", + "timedmedia-more": "Més...", + "timedmedia-dismiss": "Tanca", + "timedmedia-download": "Descarrega el fitxer", + "timedmedia-desc-link": "Informació del fitxer", + "timedmedia-transcodeinfo": "Format", + "timedmedia-direct-link": "Descarregar", + "timedmedia-days": "{{PLURAL:$1|1 dia|$1 dies}}", + "timedmedia-hours": "{{PLURAL:$1|1 hora|$1 hores}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minut|$1 minuts}}", + "timedmedia-seconds": "{{PLURAL:$1|1 segon|$1 segons}}", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Font $1", + "timedmedia-source-file-desc": "Arxiu original $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Fitxer original $1 ($2)", + "timedmedia-subtitle-language": "$1 ($2) subtítols", + "timedmedia-videos": "{{PLURAL:$1|$1 vídeo|$1 vídeos}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 vídeo Ogg|$1 vídeos Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 vídeo WebM|$1 vídeosWebM}}", + "timedmedia-file": "Fitxer" +} diff --git a/extensions/TimedMediaHandler/i18n/ce.json b/extensions/TimedMediaHandler/i18n/ce.json new file mode 100644 index 00000000..e6af995d --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ce.json @@ -0,0 +1,85 @@ +{ + "@metadata": { + "authors": [ + "Sasan700", + "Умар" + ] + }, + "timedmedia-desc": "WebM, Ogg Theora, Vorbis, srt форматаш ловш аудио, видео а, субтитраш а кечерг", + "timedmedia-ogg-short-audio": "Аьзнийн файл Ogg $1, $2", + "timedmedia-ogg-short-video": "Видео-файл Ogg $1, $2", + "timedmedia-ogg-short-general": "Медиа-файл Ogg $1, $2", + "timedmedia-ogg-long-audio": "аьзнийн файл Ogg $1, дохалла (хан) $2, $3", + "timedmedia-ogg-long-video": "видео-файл Ogg $1, йохалла $2, $4 × $5 {{PLURAL:$5|пиксель}}, $3", + "timedmedia-ogg-long-multiplexed": "мультиплекан аудио/видео-файл Ogg, $1, йохалла $2, $4×$5 пиксель, $3 ерриг", + "timedmedia-ogg-long-general": "медиа-файл Ogg, йохалла $2, $3", + "timedmedia-ogg-long-error": "нийса йоцу Ogg-файл: $1", + "timedmedia-ogg-long-no-streams": "Мультимедиа-файл ogg. ДӀахьедар: ХӀокху файлас лелош йолу цхьа кодек билгалйина яц.", + "timedmedia-webm-short-video": "WebM $1 видео-файл, $2", + "timedmedia-webm-long-video": "WebM аудио/видео файл, $1, йохалла $2, $4 × $5 {{PLURAL:$5|пиксель}}, ерриг $3", + "timedmedia-flac-short-audio": "Аудиофайл FLAC, $1", + "timedmedia-flac-long-audio": "Аудиофайл FLAC, йохалла — $1, битрейт — $2", + "timedmedia-wav-short-audio": "Аудиофайл FLAC, $1", + "timedmedia-wav-long-audio": "Аудиофайл WAV, йохалла — $1, битрейт — $2", + "timedmedia-wav-pcm-required": "Ахьа чуяккха мега PCM (Pulse Code Modulation) WAV.", + "timedmedia-mp4-short-video": "WebM $1 видео-файл, $2", + "timedmedia-mp4-long-video": "Аудио/видеофайл MP4 $1, йохалла — $2, $4 × $5 пиксель, битрейт — $3", + "timedmedia-more": "Дукха…", + "timedmedia-dismiss": "Къайлаяккха", + "timedmedia-download": "Файл чуяккхар", + "timedmedia-play-media": "Йолаялийта медиа-файл", + "timedmedia-desc-link": "ХӀокху файлах лаьцна хаам", + "timedmedia-oggThumb-failed": "oggThumb тар цаделира миниатюра кхолла.", + "timedmedia-status-header": "Юху кодяран хьал", + "timedmedia-update-status": "Юху кодяран статус карлаяккха", + "timedmedia-status": "Хьал", + "timedmedia-status-unknown": "Ца довза хьал", + "timedmedia-transcodeinfo": "Юху кодярах лаьцна", + "timedmedia-actions": "Дийраш", + "timedmedia-direct-link": "Чуяккха модифицироват йина вариант", + "timedmedia-not-ready": "Кийча яц", + "timedmedia-completed-on": "$1 юху кодяр чекхдели", + "timedmedia-error-on": "$1 юху кодъеш гӀалат", + "timedmedia-started-transcode": "Юху кодяр дола далийтина $1 хьалха. $2", + "timedmedia-percent-done": "Герггара хьесапехь $1% кхочушдина", + "timedmedia-in-job-queue": "Да дезарг $1 хьалхо рогӀе диллина", + "timedmedia-days": "{{PLURAL:$1|$1 де}}", + "timedmedia-hours": "{{PLURAL:$1|1 сахьт}}", + "timedmedia-minutes": "{{PLURAL:$1|$1 минот}}", + "timedmedia-seconds": "{{PLURAL:$1|$1 секунд}}", + "timedmedia-reset": "Юху кодяр тӀехдалийтар", + "timedmedia-reset-error": "Юху кодъеш гӀалат.", + "timedmedia-source-file": "Хьост $1", + "timedmedia-source-file-desc": "Оригинал $1 файлан, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Оригиналан $1-файл ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg-видео дика йоцу (160p)", + "timedmedia-derivative-desc-360p.ogv": "МогӀаман Ogg-видео (360p)", + "timedmedia-derivative-desc-480p.ogv": "МогӀаман Ogg-видео (480p)", + "timedmedia-derivative-desc-720p.ogv": "Ogg-видео дика (720p)", + "timedmedia-derivative-desc-160p.webm": "МогӀаман WebM-видео (160p)", + "timedmedia-derivative-desc-360p.webm": "МогӀаман WebM-видео (360p)", + "timedmedia-derivative-desc-480p.webm": "МогӀаман WebM-видео (480p)", + "timedmedia-derivative-desc-720p.webm": "WebM-видео дика (720p)", + "timedmedia-derivative-desc-320p.mp4": "MP4 (320P) кечяр", + "timedmedia-derivative-desc-480p.mp4": "МогӀаман MP4-видео (480p)", + "timedmedia-derivative-desc-720p.mp4": "MP4 HD-дикаллица (720P)", + "timedmedia-subtitle-new-go": "Кхочушдé", + "timedmedia-subtitle-language": "$1 ($2) субтитраш", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 видео-файл|$1 видео-файлаш}}", + "timedmedia-ogg-videos": "$1 Ogg видео{{PLURAL:$1|файл}}", + "timedmedia-webm-videos": "$1 WebM видео{{PLURAL:$1|файл}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 юху кодяр}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 юху кодяр доладалийтар}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 юху кодяран рогӀехь ю}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 кхочуш ца хилла юху кодяр}}", + "timedmedia-file": "Файл", + "timedmedia-audios": "{{PLURAL:$1|$1 аудио-файл|$1 аудио-файлаш}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 аудио-файл Ogg|$1 аудио-файлаш Ogg}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 аудио-файл FLAC|$1 аудио-файлаш FLAC}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 аудио-файл WAV|$1 аудио-файлаш WAV}}", + "right-transcode-reset": "Кхосса дика йоцу видеош уьш рогӀехь хилийта.", + "right-transcode-status": "Хьажа [[Special:TimedMediaHandler|юху код хийцаран хааме]]", + "orphanedtimedtext": "Яйна агӀонаш TimedText", + "orphanedtimedtext-notimedtext": "TimedText хӀокху вики чохь латина дац." +} diff --git a/extensions/TimedMediaHandler/i18n/ckb.json b/extensions/TimedMediaHandler/i18n/ckb.json new file mode 100644 index 00000000..4a345dc0 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ckb.json @@ -0,0 +1,14 @@ +{ + "@metadata": { + "authors": [ + "Calak", + "Asoxor", + "Muhammed taha" + ] + }, + "timedmedia-no-player-js": "ببوورە، وێبگەرەکەت یان JavaScript لە کار خستووە یان playerی پێویستی نییە.
\nدەتوانیت کلیپەکە داگریت یان playerێک داگریت بۆ ئەوەی لە وێبگەڕەکەتدا بیخەیتە سەر.", + "timedmedia-source-file": "$1 سەرچاوە", + "timedmedia-source-audio-file-desc": "پەڕگەی $1 بنەڕەتی ($2)", + "right-transcode-reset": "ڕێکخستنەوەی ڤیدیۆ سەرنەکوتووەکان یان ترانسکۆدکراوەکان ھەتا دیسان بچنەوە ناو ڕیزی کارەوە.", + "right-transcode-status": "دیتنی [[تایبەت:TimedMediaHandler|زانیاری لەمەڕ چالاکیی ترانسکۆدی ھەنووکەیی]]" +} diff --git a/extensions/TimedMediaHandler/i18n/cs.json b/extensions/TimedMediaHandler/i18n/cs.json new file mode 100644 index 00000000..69a2c0ca --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/cs.json @@ -0,0 +1,75 @@ +{ + "@metadata": { + "authors": [ + "Chmee2", + "Li-sung", + "Matěj Grabovský", + "Mormegil", + "Paxt", + "Matěj Suchánek" + ] + }, + "timedmedia-desc": "Obsluha zvuků, videa a časovaného textu s podporou formátů WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Zvukový soubor ogg $1, $2", + "timedmedia-ogg-short-video": "Videosoubor ogg $1, $2", + "timedmedia-ogg-short-general": "Multimediální soubor ogg $1, $2", + "timedmedia-ogg-long-audio": "Zvukový soubor ogg $1, délka $2, $3", + "timedmedia-ogg-long-video": "Videosoubor $1, délka $2, $4×$5 pixelů, $3", + "timedmedia-ogg-long-multiplexed": "multiplexovaný audio/video soubor Ogg, $1, délka $2, $4×$5 pixelů, celkem $3", + "timedmedia-ogg-long-general": "Soubor média ogg, délka $2, $3", + "timedmedia-ogg-long-error": "Chybný soubor ogg: $1", + "timedmedia-webm-short-video": "Videosoubor WebM $1, $2", + "timedmedia-webm-long-video": "audio/video soubor WebM, $1, délka $2, $4×$5 pixelů, celkem $3", + "timedmedia-wav-pcm-required": "Můžete načítat jen WAV soubory ve formátu PCM (pulse code modulation).", + "timedmedia-mp4-short-video": "Videosoubor MP4 $1, $2", + "timedmedia-mp4-long-video": "audio/video soubor MP4, $1, délka $2, $4×$5 pixelů, celkem $3", + "timedmedia-no-player-js": "Je mi líto, ale váš prohlížeč má buď vypnutý JavaScript, nebo nemáte žádný podporovaný přehrávač.
\nMůžete si stáhnout klip nebo si stáhnout přehrávač, kterým si klip přehrajete v prohlížeči.", + "timedmedia-more": "Více...", + "timedmedia-dismiss": "Zavřít", + "timedmedia-download": "Stáhnout soubor", + "timedmedia-play-media": "Přehrát média", + "timedmedia-desc-link": "O tomto souboru", + "timedmedia-oggThumb-version": "OggHandler vyžaduje oggThumb verze $1 nebo novější.", + "timedmedia-oggThumb-failed": "oggThumb nedokázal vytvořit náhled.", + "timedmedia-status-header": "Stav transkódování", + "timedmedia-update-status": "Aktualizovat stav transkódování", + "timedmedia-status": "Stav", + "timedmedia-status-unknown": "Neznámý stav", + "timedmedia-actions": "Akce", + "timedmedia-not-ready": "Nepřipraveno", + "timedmedia-completed-on": "Transkódování dokončeno v $1", + "timedmedia-error-on": "Chyba transkódování v $1", + "timedmedia-started-transcode": "Transkódování začalo před $1. $2", + "timedmedia-percent-done": "Hotovo asi $1 %", + "timedmedia-in-job-queue": "Před $1 přidáno do fronty", + "timedmedia-unknown-target-size": "Neznámá výsledná velikost, zakódováno $1", + "timedmedia-days": "$1 {{PLURAL:$1|den|dny|dní}}", + "timedmedia-hours": "$1 {{PLURAL:$1|hodina|hodiny|hodin}}", + "timedmedia-minutes": "$1 {{PLURAL:$1|minuta|minuty|minut}}", + "timedmedia-seconds": "$1 {{PLURAL:$1|sekunda|sekundy|sekund}}", + "timedmedia-reset": "Obnovit transkódování", + "timedmedia-source-file": "Zdrojový soubor $1", + "timedmedia-source-audio-file-desc": "Původní soubor $1 ($2)", + "timedmedia-subtitle-new": "Vytvoření nového překladu nebo editace stávajícího", + "timedmedia-subtitle-new-desc": "Zvolte jazyk a stiskněte tlačítko '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Přejít", + "timedmedia-subtitle-language": "titulky v jazyce $1 ($2)", + "timedmedia-subtitle-no-video": "K aktuální stránce titulků nepatří žádné video.", + "timedmedia-subtitle-no-subtitles": "K tomuto videu momentálně neexistují titulky v jazyce $1, přidat je můžete [{{fullurl:{{FULLPAGENAME}}|action=edit}} editací této stránky]", + "timedmedia-subtitle-remote": "Synchronizovaný text k tomuto souboru je uložen na $1", + "timedmedia-subtitle-remote-link": "[$1 Stránku s popisem tohoto souboru] si můžete prohlédnout na $2", + "timedmedia-videos": "{{PLURAL:$1|$1 video|$1 videa|$1 videí}}", + "timedmedia-ogg-videos": "$1 {{PLURAL:$1|video|videa|videí}} Ogg", + "timedmedia-webm-videos": "$1 {{PLURAL:$1|video|videa|videí}} WebM", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transkódování}}", + "timedmedia-derivative-state-active": "$1 {{PLURAL:$1|probíhající|probíhající|probíhajících}} transkódování", + "timedmedia-derivative-state-queued": "$1 {{PLURAL:$1|naplánované|naplánovaná|naplánovaných}} transkódování", + "timedmedia-derivative-state-failed": "$1 {{PLURAL:$1|neúspěšné|neúspěšná|neúspěšných}} transkódování", + "timedmedia-file": "Soubor", + "timedmedia-audios": "{{PLURAL:$1|$1 zvukový soubor|$1 zvukové soubory|$1 zvukových souborů}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 zvukový soubor|$1 zvukové soubory|$1 zvukových souborů}} Ogg", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 zvukový soubor|$1 zvukové soubory|$1 zvukových souborů}} FLAC", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 zvukový soubor|$1 zvukové soubory|$1 zvukových souborů}} WAV", + "right-transcode-status": "Zobrazení [[Special:TimedMediaHandler|informací o probíhajícím transkódování]]", + "action-transcode-status": "vidět aktuální stav transkódování" +} diff --git a/extensions/TimedMediaHandler/i18n/cu.json b/extensions/TimedMediaHandler/i18n/cu.json new file mode 100644 index 00000000..5221b9ed --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/cu.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "ОйЛ" + ] + }, + "timedmedia-dismiss": "ꙁакрꙑи", + "timedmedia-source-file": "$1 кладѧꙃь", + "timedmedia-file": "дѣло" +} diff --git a/extensions/TimedMediaHandler/i18n/cv.json b/extensions/TimedMediaHandler/i18n/cv.json new file mode 100644 index 00000000..93c188e0 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/cv.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Chuvash2014" + ] + }, + "timedmedia-more": "Нумайрах…" +} diff --git a/extensions/TimedMediaHandler/i18n/cy.json b/extensions/TimedMediaHandler/i18n/cy.json new file mode 100644 index 00000000..5ffa296c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/cy.json @@ -0,0 +1,50 @@ +{ + "@metadata": { + "authors": [ + "Lloffiwr", + "Ham II", + "Xxglennxx" + ] + }, + "timedmedia-ogg-short-audio": "Ffeil sain Ogg $1, $2", + "timedmedia-ogg-short-video": "Ffeil fideo Ogg $1, $2", + "timedmedia-ogg-short-general": "Ffeil gyfrwng Ogg $1, $2", + "timedmedia-ogg-long-audio": "Ffeil sain Ogg $1, yn para $2, $3", + "timedmedia-ogg-long-video": "Ffeil fideo Ogg $1, yn para $2, $4 × $5 picsel, $3", + "timedmedia-ogg-long-error": "Ffeil Ogg annilys: $1", + "timedmedia-more": "Mwy…", + "timedmedia-dismiss": "Caeer", + "timedmedia-download": "Lawrlwytho ffeil", + "timedmedia-play-media": "Chwarae'r ffeil gyfrwng", + "timedmedia-desc-link": "Ynglŷn â'r ffeil hon", + "timedmedia-status-header": "Statws trawsgodio", + "timedmedia-update-status": "Diweddaru'r statws trawsgodio", + "timedmedia-status": "Statws", + "timedmedia-status-unknown": "Statws anhysbys", + "timedmedia-actions": "Gweithredoedd", + "timedmedia-not-ready": "Ddim yn barod", + "timedmedia-started-transcode": "Dechreuodd y trawsgodio $1 yn ôl. $2", + "timedmedia-percent-done": "Tua $1% wedi ei wneud", + "timedmedia-in-job-queue": "Ychwanegwyd i'r ciw tasgiau $1 yn ôl", + "timedmedia-unknown-target-size": "Maint anhysbys i'r gwrthrych, $1 wedi ei amgodio", + "timedmedia-days": "{{PLURAL:$1|$1 diwrnod|diwrnod|deuddydd|tridiau|$1 diwrnod}}", + "timedmedia-hours": "{{PLURAL:$1|$1 awr}}", + "timedmedia-minutes": "{{PLURAL:$1|$1 munud|$1 munud|$1 funud|$1 munud}}", + "timedmedia-seconds": "{{PLURAL:$1|$1 eiliad}}", + "timedmedia-reset": "Ailosod y trawsgodio", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "ffynhonnell $1", + "timedmedia-source-file-desc": "Y ffeil $1 gwreiddiol, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Y ffeil $1 gwreiddiol ($2)", + "timedmedia-derivative-desc-160p.ogv": "Fideo Ogg (160P) o led band isel", + "timedmedia-subtitle-new-desc": "Dewiswch iaith a gwasgu'r botwm '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Gwneler", + "timedmedia-subtitle-language": "Isdeitlau $1 ($2)", + "timedmedia-subtitle-remote-link": "Gallwch [$1 weld tudalen ddisgrifio'r] ffeil hon ar $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 fideo}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 fideo Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 fideo WebM}}", + "timedmedia-file": "Ffeil", + "action-transcode-status": "gweld statws presennol y trawsgodio" +} diff --git a/extensions/TimedMediaHandler/i18n/da.json b/extensions/TimedMediaHandler/i18n/da.json new file mode 100644 index 00000000..c9864945 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/da.json @@ -0,0 +1,100 @@ +{ + "@metadata": { + "authors": [ + "Byrial", + "Christian List", + "Fnielsen", + "HenrikKbh", + "Jon Harald Søby", + "Peter Alberti", + "Simeondahl", + "Steenth" + ] + }, + "timedmedia-desc": "Understøttelse for lyd, video og tidsbestemt txt med formaterne WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1 lydfil, $2", + "timedmedia-ogg-short-video": "Ogg $1 videofil, $2", + "timedmedia-ogg-short-general": "Ogg $1 mediafil, $2", + "timedmedia-ogg-long-audio": "Ogg $1 lydfil, længde $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 videofil, længde $2, $4×$5 pixel, $3", + "timedmedia-ogg-long-multiplexed": "Sammensat Ogg-lyd- og -videofil, $1, længde $2, $4×$5 pixel, $3 samlet", + "timedmedia-ogg-long-general": "Ogg mediafil, længde $2, $3", + "timedmedia-ogg-long-error": "Ugyldig Ogg-fil: $1", + "timedmedia-webm-short-video": "WebM $1 videofil, $2", + "timedmedia-webm-long-video": "WebM audio/video fil, $1, længde $2, $4 × $5 pixel, $3 samlet", + "timedmedia-flac-short-audio": "FLAC lydfil, $1", + "timedmedia-flac-long-audio": "FLAC lydfil, varighed $1, $2 samlet", + "timedmedia-wav-short-audio": "WAV lydfil, $1", + "timedmedia-wav-long-audio": "WAV lydfil, varighed $1, $2 samlet", + "timedmedia-wav-pcm-required": "Du kan kun uploade PCM (Pulse Code Modulation) WAV.", + "timedmedia-mp4-short-video": "Mp4 $1 videofil, $2", + "timedmedia-mp4-long-video": "Mp4-fil for audio/video, $1, længde $2, $4 × $5 pixel, $3 samlet", + "timedmedia-no-player-js": "Beklager, men din browser har enten deaktiveret JavaScript eller har ikke en afspiller, som vi understøtter.
\nDu kan enten hente klippet eller hente en afspiller til at afspille klippet i din browser.", + "timedmedia-more": "Mere...", + "timedmedia-dismiss": "Luk", + "timedmedia-download": "Download fil", + "timedmedia-play-media": "Afspil medier", + "timedmedia-desc-link": "Om denne fil", + "timedmedia-oggThumb-version": "OggHandler kræver oggThumb version $1 eller nyere.", + "timedmedia-oggThumb-failed": "oggThumb kunne ikke oprette et miniaturebillede.", + "timedmedia-status-header": "Transcode status", + "timedmedia-update-status": "Opdater transcode status", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Ukendt status", + "timedmedia-transcodeinfo": "Afledt transcode beskrivelse", + "timedmedia-actions": "Handlinger", + "timedmedia-direct-link": "Hent derivat", + "timedmedia-not-ready": "Ikke klar", + "timedmedia-completed-on": "Gennemført transcode $1", + "timedmedia-error-on": "Fejl i omkodning ved $1", + "timedmedia-started-transcode": "Kode startet $1 siden. $2", + "timedmedia-percent-done": "Cirka $1 % udført", + "timedmedia-in-job-queue": "Føjet til jobkøen $1 siden", + "timedmedia-unknown-target-size": "Ukendt mål størrelse, $1 kodet", + "timedmedia-days": "{{PLURAL:$1|$1 dag|$1 dage}}", + "timedmedia-hours": "{{PLURAL:$1|1 time|$1 timer}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minut|$1 minutter}}", + "timedmedia-seconds": "{{PLURAL:$1|$1 sekund|$1 sekunder}}", + "timedmedia-reset": "Nulstil transkode", + "timedmedia-reset-confirm": "Nulstilling af denne omkodning vil fjerne alle eksisterende filer (hvis der er nogen), og vil genindsætte omkodningen i jobkøen. Det vil tage nogen tid at omkode forfra.

\nEr du sikker på, du vil fortsætte?", + "timedmedia-reset-error": "Fejl ved nulstilling af kode job.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 kilde", + "timedmedia-source-file-desc": "Original $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Original $1 file ($2)", + "timedmedia-derivative-desc-160p.ogv": "Lav båndbredde Ogg video (160 P)", + "timedmedia-derivative-desc-360p.ogv": "Web streambar Ogg video (360 P)", + "timedmedia-derivative-desc-480p.ogv": "Web streambar Ogg video (480 P)", + "timedmedia-derivative-desc-720p.ogv": "Højkvalitets hentbar Ogg video (720P)", + "timedmedia-derivative-desc-160p.webm": "Web streambar WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "Web streambar WebM (360 P)", + "timedmedia-derivative-desc-480p.webm": "Web streambar WebM (480 P)", + "timedmedia-derivative-desc-720p.webm": "Højkvalitets hentbar WebM (720 P)", + "timedmedia-derivative-desc-320p.mp4": "enhedsvenlig MP4 (320 P)", + "timedmedia-derivative-desc-480p.mp4": "Web streambar MP4 (480 P)", + "timedmedia-derivative-desc-720p.mp4": "HD-kvalitet MP4 (720 P)", + "timedmedia-subtitle-new": "Opret nye oversættelse eller rediger eksisterende", + "timedmedia-subtitle-new-desc": "Vælg sprog og tryk på '''{{int:Timedmedia-undertitlen-ny-gå}}'' ' knappen", + "timedmedia-subtitle-new-go": "Kør", + "timedmedia-subtitle-language": "$1( $2 ) undertekster", + "timedmedia-subtitle-no-video": "Der er ingen video tilknyttet den aktuelle undertitel side", + "timedmedia-subtitle-no-subtitles": "Der er i øjeblikket ingen undertekster i $1 denne video du kan [{{fullurl: {{FULLPAGENAME}} |action = Rediger}} redigere denne side] for at tilføje dem", + "timedmedia-subtitle-remote": "Tidsbestemte tekst for denne fil er placeret på $1", + "timedmedia-subtitle-remote-link": "Du kan [$1 se beskrivelses siden] til denne fil på $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 video|$1 videoer}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg video|$1 Ogg videoer}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM video|$1 WebM videoer}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transkode|$1 transkoder}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 kørende transcode|$1 kørende transcodes}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transkode i kø|$1 transkoder i kø}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 transkode mislykket|$1 transkoder mislykkedes}}", + "timedmedia-file": "Fil", + "timedmedia-audios": "{{PLURAL:$1|$1 lydfil|$1 lydfiler}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg-lydfil|$1 Ogg-lydfiler}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC-lydfil|$1 FLAC-lydfiler}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV-lydfil|$1 WAV-lydfiler}}", + "right-transcode-reset": "Nulstil mislykkedes eller transkodede videoer så de er sat ind i jobkøen igen.", + "right-transcode-status": "Vis [[Special:TimedMediaHandler|information om den aktuelle omkodningsaktivitet]]", + "action-transcode-status": "få vist den aktuelle transkode status" +} diff --git a/extensions/TimedMediaHandler/i18n/de-formal.json b/extensions/TimedMediaHandler/i18n/de-formal.json new file mode 100644 index 00000000..be6d0c9d --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/de-formal.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Kghbln" + ] + }, + "timedmedia-no-player-js": "Entschuldige, aber für Ihren Browser ist entweder die Nutzung von JavaScript deaktiviert oder er verfügt über keine unterstützte Abspielsoftware.
\nSie können den Clip herunterladen oder eine Abspielsoftware herunterladen, um den Clip im Browser abspielen zu können." +} diff --git a/extensions/TimedMediaHandler/i18n/de.json b/extensions/TimedMediaHandler/i18n/de.json new file mode 100644 index 00000000..cffaf6e2 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/de.json @@ -0,0 +1,130 @@ +{ + "@metadata": { + "authors": [ + "Filzstift", + "G.Hagedorn", + "Geitost", + "Kghbln", + "Leithian", + "Les Meloures", + "Metalhead64", + "MichaelFrey", + "Raimond Spekking", + "The Evil IP address", + "Umherirrender" + ] + }, + "extensionname-timedmedia": "TimedMediaHandler", + "timedmedia-desc": "Stellt ein Steuerungsprogramm für zeitgesteuerte Medien (Video, Audio, timedText) bereit, welches die Formate WebM, Ogg Theora, Ogg Vorbis und SubRip unterstützt", + "timedmedia-ogg-short-audio": "Ogg-$1-Audiodatei, $2", + "timedmedia-ogg-short-video": "Ogg-$1-Videodatei, $2", + "timedmedia-ogg-short-general": "Ogg-$1-Mediadatei, $2", + "timedmedia-ogg-long-audio": "Ogg-$1-Audiodatei, Länge: $2, $3", + "timedmedia-ogg-long-video": "Ogg-$1-Videodatei, Länge: $2, $4×$5 Pixel, $3 insgesamt", + "timedmedia-ogg-long-multiplexed": "Ogg-Audio-/Video-Datei, $1, Länge: $2, $4×$5 Pixel, $3 insgesamt", + "timedmedia-ogg-long-general": "Ogg-Mediadatei, Länge: $2, $3", + "timedmedia-ogg-long-error": "Ungültige Ogg-Datei: $1", + "timedmedia-ogg-long-no-streams": "OGG-Mediendatei. Warnung: Keine der in dieser Datei verwendeten Codecs wurden erkannt.", + "timedmedia-webm-short-video": "WebM-$1-Videodatei, $2", + "timedmedia-webm-long-video": "WebM-Audio-/Video-Datei, $1, Länge: $2, $4×$5 Pixel, $3 insgesamt", + "timedmedia-flac-short-audio": "FLAC-Audiodatei, $1", + "timedmedia-flac-long-audio": "FLAC-Audiodatei, Länge: $1, $2 insgesamt", + "timedmedia-wav-short-audio": "WAV-Audiodatei, $1", + "timedmedia-wav-long-audio": "WAV-Audiodatei, Länge: $1, $2 insgesamt", + "timedmedia-wav-pcm-required": "Du kannst nur PCM-WAV-Dateien hochladen (Pulse Code Modulation).", + "timedmedia-mp4-short-video": "MP4-$1-Videodatei, $2", + "timedmedia-mp4-long-video": "MP4-Audio-/Video-Datei, $1, Länge $2, $4 × $5 Pixel, $3 insgesamt", + "timedmedia-no-player-js": "Entschuldige, aber für deinen Browser ist entweder die Nutzung von JavaScript deaktiviert oder er verfügt über keine unterstützte Abspielsoftware.
\nDu kannst den Clip herunterladen oder eine Abspielsoftware herunterladen, um den Clip im Browser abspielen zu können.", + "timedmedia-more": "Optionen …", + "timedmedia-dismiss": "Schließen", + "timedmedia-download": "Datei herunterladen", + "timedmedia-play-media": "Mediendatei abspielen", + "timedmedia-desc-link": "Über diese Datei", + "timedmedia-oggThumb-version": "OggHandler erfordert oggThumb in der Version $1 oder höher.", + "timedmedia-oggThumb-failed": "oggThumb konnte kein Miniaturbild erstellen.", + "timedmedia-status-header": "Transkodierungstatus", + "timedmedia-update-status": "Transkodierungstatus aktualisieren", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Unbekannter Status", + "timedmedia-transcodebitrate": "Bitrate", + "timedmedia-transcodeduration": "Kodierungszeit", + "timedmedia-transcodeinfo": "Format", + "timedmedia-actions": "Aktionen", + "timedmedia-direct-link": "Herunterladen", + "timedmedia-not-ready": "Nicht bereit", + "timedmedia-completed-on": "Abgeschlossen um $1", + "timedmedia-error-on": "Fehler um $1", + "timedmedia-started-transcode": "Gestartet vor $1. $2", + "timedmedia-percent-done": "Ungefähr $1 % sind erledigt", + "timedmedia-in-job-queue": "Vor $1 der Auftragswarteschlange hinzugefügt", + "timedmedia-unknown-target-size": "Unbekannte Zielgröße, $1 codiert", + "timedmedia-days": "{{PLURAL:$1|1 Tag|$1 Tagen}}", + "timedmedia-hours": "{{PLURAL:$1|1 Stunde|$1 Stunden}}", + "timedmedia-minutes": "{{PLURAL:$1|1 Minute|$1 Minuten}}", + "timedmedia-seconds": "{{PLURAL:$1|1 Sekunde|$1 Sekunden}}", + "timedmedia-reset": "Umschlüsselung zurücksetzen", + "timedmedia-reset-confirm": "Das Zurücksetzen dieser Umschlüsselung wird, sofern vorhanden, die bestehende Datei entfernen und die Umschlüsselung erneut der Auftragswarteschlange hinzufügen. Die erneute Umschlüsselung wird einige Zeit dauern.

Soll dies tatsächlich gemacht werden?", + "timedmedia-reset-error": "Fehler beim Zurücksetzen der Umschlüsselung", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Quelle ($1)", + "timedmedia-source-file-desc": "Original $1-Datei, $2 x $3 ($4)", + "timedmedia-source-audio-file-desc": "Ursprüngliche $1-Datei ($2)", + "timedmedia-derivative-160p.ogv": "Ogg (160p)", + "timedmedia-derivative-desc-160p.ogv": "Ogg-Videodatei mit niedriger Datenübertragungsrate (160p)", + "timedmedia-derivative-desc-240p.ogv": "Webstreamfähiges OGG-Video (240p)", + "timedmedia-derivative-desc-360p.ogv": "Webstreamingfähige Ogg-Videodatei (360p)", + "timedmedia-derivative-desc-480p.ogv": "Webstreamingfähige Ogg-Videodatei (480p)", + "timedmedia-derivative-desc-720p.ogv": "Qualitativ hochwertige Ogg-Videodatei (720p)", + "timedmedia-derivative-desc-1080p.ogv": "Herunterladbares Full-HD-OGG-Video (1080p)", + "timedmedia-derivative-desc-160p.webm": "Webstreamingfähige WebM-Videodatei (160p)", + "timedmedia-derivative-desc-360p.webm": "Webstreamingfähige WebM-Videodatei (360p)", + "timedmedia-derivative-desc-480p.webm": "Webstreamingfähige WebM-Videodatei (480p)", + "timedmedia-derivative-desc-720p.webm": "Qualitativ hochwertige WebM-Videodatei (720p)", + "timedmedia-derivative-desc-1080p.webm": "Herunterladbares Full-HD-WebM-Video (1080p)", + "timedmedia-derivative-desc-2160p.webm": "Herunterladbares Full-4K-WebM-Video (2160P)", + "timedmedia-derivative-360p.vp9.webm": "VP9 360P", + "timedmedia-derivative-desc-360p.vp9.webm": "Webstreamfähiges WebM-VP9-Video (360P)", + "timedmedia-derivative-480p.vp9.webm": "VP9 480P", + "timedmedia-derivative-desc-480p.vp9.webm": "Webstreamfähiges WebM-VP9-Video (480P)", + "timedmedia-derivative-720p.vp9.webm": "VP9 720P", + "timedmedia-derivative-desc-720p.vp9.webm": "HD-streamfähiges WebM-VP9-Video (720P)", + "timedmedia-derivative-1080p.vp9.webm": "VP9 1080P", + "timedmedia-derivative-desc-1080p.vp9.webm": "Full-HD-streamfähiges WebM-VP9-Video (1080P)", + "timedmedia-derivative-desc-2160p.vp9.webm": "Streamfähiges Full-4K-WebM-VP9-Video (2160P)", + "timedmedia-derivative-desc-320p.mp4": "Gerätefreundliche MP4-Datei (320P)", + "timedmedia-derivative-desc-480p.mp4": "Webstreamingfähige MP4-Datei (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4-Datei in HD-Qualität (720P)", + "timedmedia-derivative-desc-1080p.mp4": "Full-HD-Qualität-MP4-Video (1080p)", + "timedmedia-derivative-desc-2160p.mp4": "Full-4K-Qualitäts-MP4-Video (2160P)", + "timedmedia-subtitle-new": "Neue Übersetzung erstellen oder vorhandene bearbeiten", + "timedmedia-subtitle-new-desc": "Wähle eine Sprache aus und klicke auf '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Los", + "timedmedia-subtitle-language": "$1 ($2) Untertitel", + "timedmedia-subtitle-no-video": "Der aktuellen Seite mit Untertiteln ist kein Video zugeordnet.", + "timedmedia-subtitle-no-subtitles": "Derzeit gibt es keine Untertitel auf $1 für dieses Video. Zum Hinzufügen von Untertiteln kann [{{fullurl:{{FULLPAGENAME}}|action=edit}} diese Seite] bearbeitet werden.", + "timedmedia-subtitle-remote": "timedText für diese Datei befindet sich auf $1", + "timedmedia-subtitle-remote-link": "Du kannst [$1 die Beschreibungsseite] für diese Datei auf $2 ansehen", + "timedmediahandler": "Steuerungsprogramm für zeitgesteuerte Medien", + "timedmedia-videos": "{{PLURAL:$1|Ein Video|$1 Videos}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|Ein Ogg-Video|$1 Ogg-Videos}}", + "timedmedia-webm-videos": "{{PLURAL:$1|Ein WebM-Video|$1 WebM-Videos}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|Eine Umschlüsselung|$1 Umschlüsselungen}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|Eine laufende Umschlüsselung|$1 laufende Umschlüsselungen}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|Eine Umschlüsselung|$1 Umschlüsselungen}} in der Warteschlange", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|Eine fehlgeschlagene Umschlüsselung|$1 fehlgeschlagene Umschlüsselungen}}", + "timedmedia-file": "Datei", + "timedmedia-audios": "{{PLURAL:$1|Eine Audiodatei|$1 Audiodateien}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|Eine OGG-Audiodatei|$1 OGG-Audiodateien}}", + "timedmedia-flac-audios": "{{PLURAL:$1|Eine FLAC-Audiodatei|$1 FLAC-Audiodateien}}", + "timedmedia-wav-audios": "{{PLURAL:$1|Eine WAV-Audiodatei|$1 WAV-Audiodateien}}", + "right-transcode-reset": "Fehlgeschlagene oder erfolgreiche Umschlüsselungen von Video-Dateien zurücksetzen, um sie erneut in die Auftragswarteschlange einzureihen", + "right-transcode-status": "[[Special:TimedMediaHandler|Informationen über aktuelle Umschlüsselungen]] betrachten", + "action-transcode-status": "den aktuellen Transkodierungstatus anzusehen", + "orphanedtimedtext": "Verwaiste TimedText-Seiten", + "orphanedtimedtext-summary": "Liste der [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]]-Seiten, die keine entsprechende Datei haben.", + "orphanedtimedtext-unsupported": "Diese Spezialseite wird nur auf MySQL-Datenbanken unterstützt.", + "orphanedtimedtext-notimedtext": "TimedText ist auf diesem Wiki nicht aktiviert.", + "apihelp-query+transcodestatus-example-1": "Ruft den Transkodierungsstatus für [[:File:Clip.webm]] ab", + "apihelp-transcodereset-param-title": "Der Titel der Mediendatei.", + "apihelp-transcodereset-example-1": "Setzt alle Umschlüsselungen für [[:File:Clip.webm]] zurück" +} diff --git a/extensions/TimedMediaHandler/i18n/din.json b/extensions/TimedMediaHandler/i18n/din.json new file mode 100644 index 00000000..194e1a5e --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/din.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Dinkawiki" + ] + }, + "timedmedia-source-file": "$1 tënëyök" +} diff --git a/extensions/TimedMediaHandler/i18n/diq.json b/extensions/TimedMediaHandler/i18n/diq.json new file mode 100644 index 00000000..1b1ba84c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/diq.json @@ -0,0 +1,41 @@ +{ + "@metadata": { + "authors": [ + "Aspar", + "Erdemaslancan", + "Gorizon", + "Marmase", + "Mirzali", + "Xoser" + ] + }, + "timedmedia-desc": "Qen dê dosyayan dê WebM, Ogg Theora, Vorbis, srt rêxo desti veng dekerdış u nuşte ronayış", + "timedmedia-ogg-short-audio": "Ogg $1 dosyaya vengi, $2", + "timedmedia-ogg-short-video": "Ogg $1 dosyaya filmi, $2", + "timedmedia-ogg-short-general": "Ogg $1 dosyaya medyayi, $2", + "timedmedia-ogg-long-audio": "Ogg $1 dosyaya medyayi, mudde $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 dosyaya filmi, mudde $2, $4×$5 piksel, $3", + "timedmedia-ogg-long-multiplexed": "Ogg dosyaya filmi/vengi yo multiexed, $1, mudde $2, $4×$5 piksel, $3 bıumumi", + "timedmedia-ogg-long-general": "Ogg dosyaya medyayi, mudde $2, $3", + "timedmedia-ogg-long-error": "dosyaya oggi yo nemeqbul: $1", + "timedmedia-more": "hema....", + "timedmedia-dismiss": "Racnê", + "timedmedia-download": "dosya biyar war", + "timedmedia-play-media": "Medya bıcıne", + "timedmedia-desc-link": "Heqa na dosya de", + "timedmedia-status": "Weziyet", + "timedmedia-status-unknown": "Weziyeto nêzanaye", + "timedmedia-transcodeinfo": "Şınasiya babetê transkodi", + "timedmedia-actions": "Kerdışi", + "timedmedia-not-ready": "Hazır niyo", + "timedmedia-days": "{{PLURAL:$1|1 roce|$1 roci}}", + "timedmedia-hours": "{{PLURAL:$1|1 seate|$1 seati}}", + "timedmedia-minutes": "{{PLURAL:$1|1 deqa|$1 deqey}}", + "timedmedia-seconds": "{{PLURAL:$1|1 saniya|$1 saniyey}}", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 çıme", + "timedmedia-source-audio-file-desc": "Dosyaya $1 oricinale ($2)", + "timedmedia-subtitle-new-go": "Şo", + "timedmediahandler": "Programê kontroliê zemanê kontrolbiyayey", + "timedmedia-file": "Dosya" +} diff --git a/extensions/TimedMediaHandler/i18n/dsb.json b/extensions/TimedMediaHandler/i18n/dsb.json new file mode 100644 index 00000000..6b65986b --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/dsb.json @@ -0,0 +1,30 @@ +{ + "@metadata": { + "authors": [ + "Michawiki" + ] + }, + "timedmedia-desc": "Wóźeński program za awdio, wideo a timedText, z formatoweju pódpěru za WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1 awdiodataja, $2", + "timedmedia-ogg-short-video": "Ogg $1 wideodataja, $2", + "timedmedia-ogg-short-general": "Ogg $1 medijowa dataja, $2", + "timedmedia-ogg-long-audio": "Ogg $1 awdiodataja, dłujkosć $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 wideodataja, dłujkosć $2, $4×$5 pikselow, $3", + "timedmedia-ogg-long-multiplexed": "ogg multipleksowa awdio-/wideodataja, $1, dłujkosć $2, $4×$5 pikselow, $3 dogromady", + "timedmedia-ogg-long-general": "Ogg medijowa dataja, dłujkosć $2, $3", + "timedmedia-ogg-long-error": "Njepłaśiwa Ogg-dataja: $1", + "timedmedia-webm-short-video": "WebM $1 wideodataja, $2", + "timedmedia-webm-long-video": "WebM dataja awdio/wideo, $1, dłujkosć $2, $4 x $5, $3 dogromady", + "timedmedia-no-player-js": "Twój wobglědowak jo bóžko pak JavaScript znjemóžnił abo njama njepódpěrany wótegrawak.
\nMóžoš klip ześěgnuś abo wótgrawak ześěgnuś, aby klip w swójom wobglědowaku wótegrał.", + "timedmedia-more": "Wěcej...", + "timedmedia-dismiss": "Zacyniś", + "timedmedia-download": "Dataju ześěgnuś", + "timedmedia-play-media": "Medijowu dataju wótegraś", + "timedmedia-desc-link": "Wó toś tej dataji", + "timedmedia-oggThumb-version": "OggHandler trjeba wersiju $1 oggThumb abo nowšu.", + "timedmedia-oggThumb-failed": "oggThumb njejo mógł wobrazk napóraś.", + "timedmedia-source-file": "Žrědło ($1)", + "timedmedia-source-file-desc": "Originalna $1-dataja, $2 × $3 ($4)", + "timedmedia-derivative-desc-160p.ogv": "Niska šyrokosć pasma Ogg-widea (200p)", + "timedmedia-subtitle-language": "$1 ($2) pódtitele" +} diff --git a/extensions/TimedMediaHandler/i18n/dty.json b/extensions/TimedMediaHandler/i18n/dty.json new file mode 100644 index 00000000..905f6b6d --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/dty.json @@ -0,0 +1,12 @@ +{ + "@metadata": { + "authors": [ + "जनक राज भट्ट", + "रमेश सिंह बोहरा", + "राम प्रसाद जोशी" + ] + }, + "timedmedia-no-player-js": "माफ गर्या, तमरो ब्राउजरमी या त जावास्क्रिप्ट अक्षम छ या समर्थित प्लेयर छैन ।
\nतमी क्लिपलाई डाउनलोड गद्द सक्द्याहौ अथवा क्लिपलाई आफ्नो ब्राउजरमी चलाउनको लागि एक प्लेयर डाउनलोड गद्द सक्द्या हौ ।", + "timedmedia-source-file": "$1 श्रोत", + "timedmedia-source-audio-file-desc": "खास $1 चित्र ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/ee.json b/extensions/TimedMediaHandler/i18n/ee.json new file mode 100644 index 00000000..993eb5df --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ee.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Aguve" + ] + }, + "timedmedia-no-player-js": "Tafltsɛ, YavaSkript mele sisi internet-dɔwɔnua dzi alo ƒonu aɖeke meli si ate ŋu aƒoe o.
\nÀte ŋu awɔ video kpui sia ƒe kɔpi le alo wɔ ƒonu aɖe ƒe kɔpi nàtsɔ aƒo video kpui sia le wò internet-dɔwɔnua me.", + "timedmedia-source-file": "Tsoƒe $1" +} diff --git a/extensions/TimedMediaHandler/i18n/el.json b/extensions/TimedMediaHandler/i18n/el.json new file mode 100644 index 00000000..f1791421 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/el.json @@ -0,0 +1,28 @@ +{ + "@metadata": { + "authors": [ + "Consta", + "Dead3y3", + "Omnipaedista", + "ZaDiak", + "Evropi", + "Geraki" + ] + }, + "timedmedia-desc": "Χειριστής για αρχεία Ogg Theora και Vorbis, με αναπαραγωγέα JavaScript", + "timedmedia-ogg-short-audio": "Αρχείο ήχου Ogg $1, $2", + "timedmedia-ogg-short-video": "Αρχείο βίντεο Ogg $1, $2", + "timedmedia-ogg-short-general": "Αρχείο μέσων Ogg $1, $2", + "timedmedia-ogg-long-audio": "Αρχείο ήχου Ogg $1, διάρκεια $2, $3", + "timedmedia-ogg-long-video": "Αρχείο βίντεο Ogg $1, διάρκεια $2, $4×$5 pixels, $3", + "timedmedia-ogg-long-multiplexed": "Αρχείο πολυπλεκτικού ήχου/βίντεο Ogg, $1, διάρκεια $2, $4×$5 pixels, $3 ολικά", + "timedmedia-ogg-long-general": "Αρχείο μέσων Ogg, διάρκεια $2, $3", + "timedmedia-ogg-long-error": "Άκυρο αρχείο ogg: $1", + "timedmedia-no-player-js": "Συγνώμη, το πρόγραμμα περιήγησής σας είτε έχει την JavaScript απενεργοποιημένη ή δεν έχει οποιοδήποτε υποστηριζόμενο player.
\nΜπορείτε να κατεβάστε το κλιπ ή να κάνετε λήψη ενός player για να αναπαράγετε το κλιπ στον browser σας.", + "timedmedia-more": "Περισσότερα...", + "timedmedia-dismiss": "Κλείσιμο", + "timedmedia-download": "Κατεβάστε το αρχείο", + "timedmedia-desc-link": "Σχετικά με αυτό το αρχείο", + "timedmedia-source-file": "Πηγή $1", + "timedmedia-source-audio-file-desc": "Πρωτότυπο αρχείο $1 ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/en-gb.json b/extensions/TimedMediaHandler/i18n/en-gb.json new file mode 100644 index 00000000..001bb5cc --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/en-gb.json @@ -0,0 +1,12 @@ +{ + "@metadata": { + "authors": [ + "Shirayuki", + "Captaindogfish" + ] + }, + "timedmedia-ogg-long-no-streams": "Ogg media file. Warning: None of the codecs used in this file are recognised.", + "timedmedia-derivative-desc-1080p.ogv": "Full HD downloadable Ogg video (1080P)", + "timedmedia-derivative-desc-1080p.webm": "Full HD downloadable WebM (1080P)", + "timedmedia-derivative-desc-1080p.mp4": "Full HD quality MP4 (1080P)" +} diff --git a/extensions/TimedMediaHandler/i18n/en.json b/extensions/TimedMediaHandler/i18n/en.json new file mode 100644 index 00000000..b17d0665 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/en.json @@ -0,0 +1,154 @@ +{ + "@metadata": { + "authors": [] + }, + "extensionname-timedmedia": "TimedMediaHandler", + "timedmedia-desc": "Handler for audio, video and timed text, with format support for WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1 sound file, $2", + "timedmedia-ogg-short-video": "Ogg $1 video file, $2", + "timedmedia-ogg-short-general": "Ogg $1 media file, $2", + "timedmedia-ogg-long-audio": "Ogg $1 sound file, length $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 video file, length $2, $4 × $5 pixels, $3", + "timedmedia-ogg-long-multiplexed": "Ogg multiplexed audio/video file, $1, length $2, $4 × $5 pixels, $3 overall", + "timedmedia-ogg-long-general": "Ogg media file, length $2, $3", + "timedmedia-ogg-long-error": "Invalid Ogg file: $1", + "timedmedia-ogg-long-no-streams": "Ogg media file. Warning: None of the codecs used in this file are recognized.", + "timedmedia-webm-short-video": "WebM $1 video file, $2", + "timedmedia-webm-long-video": "WebM audio/video file, $1, length $2, $4 × $5 pixels, $3 overall", + "timedmedia-flac-short-audio": "FLAC audio file, $1", + "timedmedia-flac-long-audio": "FLAC audio file, length $1, $2 overall", + "timedmedia-wav-short-audio": "WAV audio file, $1", + "timedmedia-wav-long-audio": "WAV audio file, length $1, $2 overall", + "timedmedia-wav-pcm-required": "You can only upload PCM (Pulse Code Modulation) WAV.", + "timedmedia-mp4-short-video": "MP4 $1 video file, $2", + "timedmedia-mp4-long-video": "MP4 audio/video file, $1, length $2, $4 × $5 pixels, $3 overall", + "timedmedia-no-player-js": "Sorry, your browser either has JavaScript disabled or does not have any supported player.
\nYou can download the clip or download a player to play the clip in your browser.", + "timedmedia-more": "More…", + "timedmedia-dismiss": "Close", + "timedmedia-download": "Download file", + "timedmedia-play-media": "Play media", + "timedmedia-desc-link": "About this file", + "timedmedia-oggThumb-version": "OggHandler requires oggThumb version $1 or later.", + "timedmedia-oggThumb-failed": "oggThumb failed to create the thumbnail.", + "timedmedia-status-header": "Transcode status", + "timedmedia-update-status": "Update transcode status", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Unknown status", + "timedmedia-transcodebitrate": "Bitrate", + "timedmedia-transcodeduration": "Encode time", + "timedmedia-transcodeinfo": "Format", + "timedmedia-actions": "Actions", + "timedmedia-direct-link": "Download", + "timedmedia-not-ready": "Not ready", + "timedmedia-completed-on": "Completed $1", + "timedmedia-error-on": "Error on $1", + "timedmedia-started-transcode": "Started $1 ago. $2", + "timedmedia-percent-done": "About $1% done", + "timedmedia-in-job-queue": "Added to Job queue $1 ago", + "timedmedia-unknown-target-size": "Unknown target size, $1 encoded", + "timedmedia-days": "{{PLURAL:$1|1 day|$1 days}}", + "timedmedia-hours": "{{PLURAL:$1|1 hour|$1 hours}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minute|$1 minutes}}", + "timedmedia-seconds": "{{PLURAL:$1|1 second|$1 seconds}}", + "timedmedia-reset": "Reset transcode", + "timedmedia-reset-confirm": "Resetting this transcode will remove any existing file (if present), and it will re-add the transcode to the job queue. It will take some time to re-transcode.

\nAre you sure you want to proceed?", + "timedmedia-reset-error": "Error in resetting transcode job.", + "timedmedia-ogg": "Ogg", + "timedmedia-webm": "WebM", + "timedmedia-mp4": "MP4", + "timedmedia-wav": "WAV", + "timedmedia-flac": "FLAC", + "timedmedia-source-file": "$1 source", + "timedmedia-source-file-desc": "Original $1 file, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Original $1 file ($2)", + "timedmedia-derivative-160p.ogv": "Ogg 160P", + "timedmedia-derivative-desc-160p.ogv": "Low bandwidth Ogg video (160P)", + "timedmedia-derivative-240p.ogv": "Ogg 240P", + "timedmedia-derivative-desc-240p.ogv": "Web streamable Ogg video (240P)", + "timedmedia-derivative-360p.ogv": "Ogg 360P", + "timedmedia-derivative-desc-360p.ogv": "Web streamable Ogg video (360P)", + "timedmedia-derivative-480p.ogv": "Ogg 480P", + "timedmedia-derivative-desc-480p.ogv": "Web streamable Ogg video (480P)", + "timedmedia-derivative-720p.ogv": "Ogg 720P", + "timedmedia-derivative-desc-720p.ogv": "High quality downloadable Ogg video (720P)", + "timedmedia-derivative-1080p.ogv": "Ogg 1080P", + "timedmedia-derivative-desc-1080p.ogv": "Full HD downloadable Ogg video (1080P)", + "timedmedia-derivative-160p.webm": "WebM 160P", + "timedmedia-derivative-desc-160p.webm": "Web streamable WebM (160P)", + "timedmedia-derivative-360p.webm": "WebM 360P", + "timedmedia-derivative-desc-360p.webm": "Web streamable WebM (360P)", + "timedmedia-derivative-480p.webm": "WebM 480P", + "timedmedia-derivative-desc-480p.webm": "Web streamable WebM (480P)", + "timedmedia-derivative-720p.webm": "WebM 720P", + "timedmedia-derivative-desc-720p.webm": "High quality downloadable WebM (720P)", + "timedmedia-derivative-1080p.webm": "WebM 1080P", + "timedmedia-derivative-desc-1080p.webm": "Full HD downloadable WebM (1080P)", + "timedmedia-derivative-2160p.webm": "WebM 2160P", + "timedmedia-derivative-desc-2160p.webm": "Full 4K downloadable WebM (2160P)", + "timedmedia-derivative-360p.vp9.webm": "VP9 360P", + "timedmedia-derivative-desc-360p.vp9.webm": "Web streamable WebM VP9 (360P)", + "timedmedia-derivative-480p.vp9.webm": "VP9 480P", + "timedmedia-derivative-desc-480p.vp9.webm": "Web streamable WebM VP9 (480P)", + "timedmedia-derivative-720p.vp9.webm": "VP9 720P", + "timedmedia-derivative-desc-720p.vp9.webm": "HD streamable WebM VP9 (720P)", + "timedmedia-derivative-1080p.vp9.webm": "VP9 1080P", + "timedmedia-derivative-desc-1080p.vp9.webm": "Full HD streamable WebM VP9 (1080P)", + "timedmedia-derivative-2160p.vp9.webm": "VP9 2160P", + "timedmedia-derivative-desc-2160p.vp9.webm": "Full 4K streamable WebM VP9 (2160P)", + "timedmedia-derivative-320p.mp4": "H264 320P", + "timedmedia-derivative-desc-320p.mp4": "Device-friendly MP4 (320P)", + "timedmedia-derivative-480p.mp4": "H264 480P", + "timedmedia-derivative-desc-480p.mp4": "Web streamable MP4 (480P)", + "timedmedia-derivative-720p.mp4": "H264 720P", + "timedmedia-derivative-desc-720p.mp4": "HD quality MP4 (720P)", + "timedmedia-derivative-1080p.mp4": "H264 1080P", + "timedmedia-derivative-desc-1080p.mp4": "Full HD quality MP4 (1080P)", + "timedmedia-derivative-2160p.mp4": "H264 2160P", + "timedmedia-derivative-desc-2160p.mp4": "Full 4K quality MP4 (2160P)", + "timedmedia-derivative-ogg": "Ogg Vorbis", + "timedmedia-derivative-desc-ogg": "Ogg Vorbis", + "timedmedia-derivative-opus": "Opus", + "timedmedia-derivative-desc-opus": "Opus", + "timedmedia-derivative-mp3": "MP3", + "timedmedia-derivative-desc-mp3": "MP3", + "timedmedia-derivative-m4a": "AAC", + "timedmedia-derivative-desc-m4a": "AAC", + "timedmedia-subtitle-new": "Create new translation or edit existing", + "timedmedia-subtitle-new-desc": "Select language and press the '''{{int:Timedmedia-subtitle-new-go}}''' button", + "timedmedia-subtitle-new-go": "Go", + "timedmedia-subtitle-language": "$1 ($2) subtitles", + "timedmedia-subtitle-no-video": "There is no video associated with the current subtitle page.", + "timedmedia-subtitle-no-subtitles": "There are currently no subtitles in $1 for this video, you can [{{fullurl:{{FULLPAGENAME}}|action=edit}} edit this page] to add them.", + "timedmedia-subtitle-remote": "Timed text for this file is hosted on $1", + "timedmedia-subtitle-remote-link": "You can [$1 view the description page] for this file on $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 video|$1 videos}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg video|$1 Ogg videos}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM video|$1 WebM videos}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transcode|$1 transcodes}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 running transcode|$1 running transcodes}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 queued transcode|$1 queued transcodes}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 failed transcode|$1 failed transcodes}}", + "timedmedia-file": "File", + "timedmedia-audios": "{{PLURAL:$1|$1 audio file|$1 audio files}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg audio file|$1 Ogg audio files}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC audio file|$1 FLAC audio files}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV audio file|$1 WAV audio files}}", + "right-transcode-reset": "Reset failed or transcoded videos so they are inserted into the job queue again", + "right-transcode-status": "View [[Special:TimedMediaHandler|information about the current transcode activity]]", + "action-transcode-status": "view the current transcoding status", + "orphanedtimedtext": "Orphaned TimedText pages", + "orphanedtimedtext-summary": "List of [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] pages which do not have a corresponding file.", + "orphanedtimedtext-unsupported": "This special page is only supported on MySQL databases.", + "orphanedtimedtext-notimedtext": "TimedText is not enabled on this wiki.", + "apihelp-query+transcodestatus-description": "Get transcode status for a given file page.", + "apihelp-query+transcodestatus-example-1": "Get transcode status for [[:File:Clip.webm]]", + "apihelp-query+videoinfo-description": "Extends imageinfo to include video source (derivatives) information", + "apihelp-query+videoinfo-param-prop": "What video information to get:\n;timestamp:Adds timestamp for the uploaded version.\n;user:Adds the user who uploaded the video version.\n;userid:Add the user ID that uploaded the video version.\n;comment:Comment on the version.\n;parsedcomment:Parse the comment on the version.\n;canonicaltitle:Adds the canonical title of the video file.\n;url:Gives URL to the video and the description page.\n;size:Adds the size of the video in bytes, its height and its width. Page count and duration are added if applicable.\n;dimensions:Alias for size.\n;sha1:Adds SHA-1 hash for the video.\n;mime:Adds MIME type of the video.\n;thumbmime:Adds MIME type of the video thumbnail (requires url and param $1urlwidth).\n;mediatype:Adds the media type of the video.\n;metadata:Lists Exif metadata for the version of the video.\n;commonmetadata:Lists file format generic metadata for the version of the video.\n;extmetadata:Lists formatted metadata combined from multiple sources. Results are HTML formatted.\n;archivename:Adds the filename of the archive version for non-latest versions.\n;bitdepth:Adds the bit depth of the version.\n;uploadwarning:Used by the [[Special:Upload]] page to get information about an existing file. Not intended for use outside MediaWiki core.\n;derivatives:Adds an array of the different format and quality versions of an audio or video file that are available.", + "apihelp-query+videoinfo-example-1": "Fetch information about [[:File:Folgers.ogv]]", + "apihelp-transcodereset-description": "Users with the 'transcode-reset' right can reset and re-run a transcode job.", + "apihelp-transcodereset-param-title": "The media file title.", + "apihelp-transcodereset-param-transcodekey": "The transcode key you wish to reset. Fetch from [[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "Reset all transcodes for [[:File:Clip.webm]]", + "apihelp-transcodereset-example-2": "Reset the '360_560kbs.webm' transcode key for [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/eo.json b/extensions/TimedMediaHandler/i18n/eo.json new file mode 100644 index 00000000..436317ea --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/eo.json @@ -0,0 +1,26 @@ +{ + "@metadata": { + "authors": [ + "Amikeco", + "ArnoLagrange", + "Yekrats", + "KuboF" + ] + }, + "timedmedia-desc": "Traktilo por sondosieroj, videoj, kaj horloĝigita teksto, kun formatsubteno por WebM kaj Ogg Theora kaj Vobis kaj srt.", + "timedmedia-ogg-short-audio": "Ogg $1 sondosiero, $2", + "timedmedia-ogg-short-video": "Ogg $1 videodosiero, $2", + "timedmedia-ogg-short-general": "Media Ogg-dosiero $1, $2", + "timedmedia-ogg-long-audio": "Aŭda Ogg-dosiero $1, longeco $2, $3 entute", + "timedmedia-ogg-long-video": "Video Ogg-dosiero $1, longeco $2, $4×$5 pikseloj, $3 entute", + "timedmedia-ogg-long-multiplexed": "Kunigita aŭdio/video Ogg-dosiero, $1, longeco $2, $4×$5 pikseloj, $3 entute", + "timedmedia-ogg-long-general": "Ogg-mediodosiero, longeco $2, $3", + "timedmedia-ogg-long-error": "Malvalida Ogg-dosiero: $1", + "timedmedia-no-player-js": "Pardonu, estas malŝaltita Ĝavoskripto en via foliumilo aŭ ĝi enhavas neniun subtenatan ludilon
\nVi povas elŝuti la videaĵonelŝuti ludilon por povi ludi la videaĵon en via foliumilo.", + "timedmedia-more": "Pli...", + "timedmedia-dismiss": "Fermi", + "timedmedia-download": "Alŝuti dosieron", + "timedmedia-desc-link": "Pri ĉi tiu dosiero", + "timedmedia-source-file": "Fonta $1-dosiero", + "timedmedia-source-audio-file-desc": "Origina $1-dosiero ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/es.json b/extensions/TimedMediaHandler/i18n/es.json new file mode 100644 index 00000000..962436fb --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/es.json @@ -0,0 +1,117 @@ +{ + "@metadata": { + "authors": [ + "Aleator", + "Armando-Martin", + "Ciencia Al Poder", + "Crazymadlover", + "Jewbask", + "Maor X", + "Muro de Aguas", + "Remember the dot", + "Rodrigo Molinero", + "Rzuwig", + "Sanbec", + "Spacebirdy", + "Translationista", + "Fitoschido", + "Macofe" + ] + }, + "timedmedia-desc": "Manipulador de sonido, vídeo y texto sincronizado, con soporte para los formatos WebM, Ogg Theora, Vorbis y srt", + "timedmedia-ogg-short-audio": "Archivo de sonido Ogg $1, $2", + "timedmedia-ogg-short-video": "Archivo de video Ogg $1, $2", + "timedmedia-ogg-short-general": "Archivo Ogg $1, $2", + "timedmedia-ogg-long-audio": "Archivo de sonido Ogg $1, tamaño $2, $3", + "timedmedia-ogg-long-video": "Archivo de video Ogg $1, tamaño $2, $4×$5 píxeles, $3", + "timedmedia-ogg-long-multiplexed": "Archivo Ogg de audio/video multiplexado, $1, tamaño $2, $4×$5 píxeles, $3 en todo", + "timedmedia-ogg-long-general": "Archivo Ogg. tamaño $2, $3", + "timedmedia-ogg-long-error": "Archivo Ogg no válido: $1", + "timedmedia-webm-short-video": "Archivo de vídeo WebM $1, $2", + "timedmedia-webm-long-video": "Archivo de audio/vídeo WebM, $1 , longitud $2 , $4 × $5 píxeles, $3 total", + "timedmedia-flac-short-audio": "Archivo de audio FLAC, $1", + "timedmedia-flac-long-audio": "Archivo de audio FLAC, duración: $1, $2 en general", + "timedmedia-wav-short-audio": "Archivo de audio WAV, $1", + "timedmedia-wav-long-audio": "Archivo de audio WAV, duración: $1, $2 en general", + "timedmedia-wav-pcm-required": "Solo puedes cargar WAV del tipo PCM (''Pulse Code Modulation'', por sus siglas en inglés).", + "timedmedia-mp4-short-video": "Archivo de vídeo MP4 $1, $2", + "timedmedia-mp4-long-video": "Archivo de audio/vídeo MP4, $1 , longitud $2 , $4 × $5 píxeles, $3 total", + "timedmedia-no-player-js": "Tu navegador tiene JavaScript desactivado o no tiene un reproductor compatible.
\nPuedes descargar el vídeo o descargar un reproductor para ver el vídeo en tu navegador.", + "timedmedia-more": "Opciones...", + "timedmedia-dismiss": "Cerrar", + "timedmedia-download": "Descargar archivo", + "timedmedia-play-media": "Reproducir contenido multimedia", + "timedmedia-desc-link": "Sobre este archivo", + "timedmedia-oggThumb-version": "OggHandler requiere una versión oggThumb $1 o posterior.", + "timedmedia-oggThumb-failed": "oggThumb no pudo crear la imagen miniatura.", + "timedmedia-status-header": "Estado de transcodificación", + "timedmedia-update-status": "Actualizar el estado de transcodificación", + "timedmedia-status": "Estado", + "timedmedia-status-unknown": "Estado desconocido", + "timedmedia-transcodeduration": "Tiempo de codificación", + "timedmedia-transcodeinfo": "Formato", + "timedmedia-actions": "Acciones", + "timedmedia-direct-link": "Descarga", + "timedmedia-not-ready": "No está listo", + "timedmedia-completed-on": "Completada $1", + "timedmedia-error-on": "Error a las $1", + "timedmedia-started-transcode": "Comenzada hace $1. $2", + "timedmedia-percent-done": "Aproximadamente $1 % completado", + "timedmedia-in-job-queue": "Añadido a la cola de trabajos hace $1", + "timedmedia-unknown-target-size": "Tamaño de archivo de destino desconocido, $1 codificado", + "timedmedia-days": "{{PLURAL:$1|1 día|$1 días}}", + "timedmedia-hours": "{{PLURAL:$1|1 hora|$1 horas}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuto|$1 minutos}}", + "timedmedia-seconds": "{{PLURAL:$1|1 segundo|$1 segundos}}", + "timedmedia-reset": "Reiniciar la transcodificación", + "timedmedia-reset-confirm": "Restablecer esta transcodificación eliminará cualquier archivo existente (si existe), y volverá a añadir la transcodificación a la cola de trabajos. Llevará algún tiempo repetir la transcodificación.

\n¿Estás seguro de que quieres continuar?", + "timedmedia-reset-error": "Error al restablecer el trabajo de transcodificación.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Fuente $1", + "timedmedia-source-file-desc": "Archivo $1 original, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Archivo $1 original ($2)", + "timedmedia-derivative-desc-160p.ogv": "Video en formato Ogg de bajo ancho de banda (160 P)", + "timedmedia-derivative-desc-360p.ogv": "Vídeo Ogg para la web (360P)", + "timedmedia-derivative-desc-480p.ogv": "Vídeo Ogg para la web (480P)", + "timedmedia-derivative-desc-720p.ogv": "Vídeo Ogg de alta calidad que se puede descargar (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Video ogg descargable Full HD (1080P)", + "timedmedia-derivative-desc-160p.webm": "WebM para la web (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM para la web (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM para la web (480P)", + "timedmedia-derivative-desc-720p.webm": "WebM de alta calidad que se puede descargar (720P)", + "timedmedia-derivative-desc-1080p.webm": "WebM descargable Full HD (1080P)", + "timedmedia-derivative-desc-320p.mp4": "MP4 para dispositivos móviles (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 para la web (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 de calidad HD (720P)", + "timedmedia-derivative-desc-1080p.mp4": "MP4 calidad Full HD (1080P)", + "timedmedia-subtitle-new": "Crear una nueva traducción o editar una existente", + "timedmedia-subtitle-new-desc": "Selecciona el idioma y pulsa el botón '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Ir", + "timedmedia-subtitle-language": "Subtítulos en $1 ($2)", + "timedmedia-subtitle-no-video": "No hay ningún video asociado con la página actual de subtítulos", + "timedmedia-subtitle-no-subtitles": "Actualmente no hay subtítulos en $1 para este video, puede [{{fullurl: {{FULLPAGENAME}} |action = edit}} editar esta página] para agregarlos", + "timedmedia-subtitle-remote": "El texto programado para este archivo está alojado en $1", + "timedmedia-subtitle-remote-link": "Puedes [$1 ver la página de descripción] de este archivo en $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 vídeo|$1 vídeos}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 vídeo Ogg|$1 vídeos Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 vídeo WebM|$1 vídeos WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transcodificación|$1 transcodificaciones}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 transcodificación activa|$1 transcodificaciones activas}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transcodificación|$1 transcodificaciones}} en la cola", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 transcodificación fallida|$1 transcodificaciones fallidas}}", + "timedmedia-file": "Archivo", + "timedmedia-audios": "{{PLURAL:$1|$1 archivo|$1 archivos}} de audio", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 archivo|$1 archivos}} de audio Ogg", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 archivo|$1 archivos}} de audio FLAC", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 archivo|$1 archivos}} de audio WAV", + "right-transcode-reset": "Reiniciar los vídeos erróneos o transcodificados por lo que se vuelven a colocar en la cola de trabajo", + "right-transcode-status": "Ver [[Special:TimedMediaHandler|información sobre la actividad de transcodificación actual]]", + "action-transcode-status": "ver el estado de transcodificación actual", + "orphanedtimedtext": "Páginas TimedText huérfanas", + "orphanedtimedtext-summary": "Lista de páginas [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] que no tienen un archivo correspondiente.", + "orphanedtimedtext-unsupported": "Esta página especial sólo está soportada en base de datos MySQL.", + "orphanedtimedtext-notimedtext": "TimedText no se activó en este wiki.", + "apihelp-query+videoinfo-example-1": "Obtener información sobre [[:File:Folgers.ogv]]", + "apihelp-transcodereset-param-title": "El título del archivo multimedia." +} diff --git a/extensions/TimedMediaHandler/i18n/et.json b/extensions/TimedMediaHandler/i18n/et.json new file mode 100644 index 00000000..ce910777 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/et.json @@ -0,0 +1,122 @@ +{ + "@metadata": { + "authors": [ + "Avjoska", + "Pikne", + "Silvar" + ] + }, + "timedmedia-desc": "WebM-, srt- ning Theora- ja Vorbis-tüüpi Ogg-vormingu toega heli-, video- ja ajastatud teksti käsitseja", + "timedmedia-ogg-short-audio": "$1-tüüpi Ogg-helifail, $2", + "timedmedia-ogg-short-video": "$1-tüüpi Ogg-videofail, $2", + "timedmedia-ogg-short-general": "$1-tüüpi Ogg-fail, $2", + "timedmedia-ogg-long-audio": "$1-tüüpi Ogg-helifail, kestus: $2, $3", + "timedmedia-ogg-long-video": "$1-tüüpi Ogg-videofail, kestus: $2, $4×$5 pikslit, $3", + "timedmedia-ogg-long-multiplexed": "Ogg-liitfail (heli ja video), $1, kestus: $2, $4×$5 pikslit, üldbitikiirus: $3", + "timedmedia-ogg-long-general": "Ogg-fail, kestus: $2, $3", + "timedmedia-ogg-long-error": "Vigane Ogg-fail: $1", + "timedmedia-ogg-long-no-streams": "Ogg-meediafail. Hoiatus: Selles failis ei kasutata ühtegi tuvastatud kodekit.", + "timedmedia-webm-short-video": "$1-tüüpi WebM-videofail, $2", + "timedmedia-webm-long-video": "WebM-fail (heli ja video), $1, kestus: $2, $4×$5 pikslit, üldbitikiirus: $3", + "timedmedia-flac-short-audio": "FLAC-helifail, $1", + "timedmedia-flac-long-audio": "FLAC-helifail, kestus: $1, üldbitikiirus: $2", + "timedmedia-wav-short-audio": "WAV-helifail, $1", + "timedmedia-wav-long-audio": "WAV-helifail, kestus: $1, üldbitikiirus: $2", + "timedmedia-wav-pcm-required": "Saad üles laadida ainult impulssmodulatsiooniga (PCM) WAV-faile.", + "timedmedia-mp4-short-video": "$1-tüüpi MP4-videofail, $2", + "timedmedia-mp4-long-video": "MP4-fail (heli ja video), $1, kestus: $2, $4×$5 pikslit, üldbitikiirus: $3", + "timedmedia-no-player-js": "Kahjuks on sinu veebilehitsejas JavaScript keelatud või puudub sul sobiv esitustarkvara.
\nSaad lõigu alla laadida või laadida alla tarkvara, millega lõik veebilehitsejas esitada.", + "timedmedia-more": "Lisa...", + "timedmedia-dismiss": "Sule", + "timedmedia-download": "Laadi fail alla", + "timedmedia-play-media": "Esita meediafail", + "timedmedia-desc-link": "Info faili kohta", + "timedmedia-oggThumb-version": "OggHandleri jaoks on vaja oggThumbi versiooni $1 või hilisemat.", + "timedmedia-oggThumb-failed": "oggThumb ei saanud pisipildi loomisega hakkama.", + "timedmedia-status-header": "Ümberkodeeringu olek", + "timedmedia-update-status": "Värskenda ümberkodeeringu olekut", + "timedmedia-status": "Olek", + "timedmedia-status-unknown": "Tundmatu olek", + "timedmedia-transcodebitrate": "Bitikiirus", + "timedmedia-transcodeduration": "Kodeerimisaeg", + "timedmedia-transcodeinfo": "Vorming", + "timedmedia-actions": "Toimingud", + "timedmedia-direct-link": "Allalaadimine", + "timedmedia-not-ready": "Ei ole valmis", + "timedmedia-completed-on": "Valmimisaeg: $1", + "timedmedia-error-on": "Tõrkeaeg: $1", + "timedmedia-started-transcode": "Alustatud $1 tagasi. $2", + "timedmedia-percent-done": "Umbes $1% valmis", + "timedmedia-in-job-queue": "Lisatud tööjärge $1 tagasi", + "timedmedia-unknown-target-size": "Sihtsuurus teadmata, $1 kodeeritud", + "timedmedia-days": "{{PLURAL:$1|1 päev|$1 päeva}}", + "timedmedia-hours": "{{PLURAL:$1|1 tund|$1 tundi}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minut|$1 minutit}}", + "timedmedia-seconds": "{{PLURAL:$1|1 sekund|$1 sekundit}}", + "timedmedia-reset": "Lähtesta ümberkodeering", + "timedmedia-reset-confirm": "Ümberkodeeringu lähtestamisel eemaldatakse kõik olemasolevad failid (kui neid on) ja see lisatakse uuesti tööjärge. Uus ümberkodeering kestab mõnda aega.

\nKas oled kindel, et tahad jätkata?", + "timedmedia-reset-error": "Ümberkodeeringu lähtestamisel ilmnes tõrge.", + "timedmedia-source-file": "Allikas ($1)", + "timedmedia-source-file-desc": "Algne $1-fail, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Algne $1-fail ($2)", + "timedmedia-derivative-desc-160p.ogv": "Väikse ribalaiusega Ogg-video (160P)", + "timedmedia-derivative-desc-240p.ogv": "Võrgus voogedastatav Ogg-video (240P)", + "timedmedia-derivative-desc-360p.ogv": "Võrgus voogedastatav Ogg-video (360P)", + "timedmedia-derivative-desc-480p.ogv": "Võrgus voogedastatav Ogg-video (480P)", + "timedmedia-derivative-desc-720p.ogv": "Kõrge kvaliteediga allalaaditav Ogg-video (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Täiskõrglahutusega allalaaditav Ogg-video (1080P)", + "timedmedia-derivative-desc-160p.webm": "Võrgus voogedastatav WebM-video (160P)", + "timedmedia-derivative-desc-360p.webm": "Võrgus voogedastatav WebM-video (360P)", + "timedmedia-derivative-desc-480p.webm": "Võrgus voogedastatav WebM-video (480P)", + "timedmedia-derivative-desc-720p.webm": "Kõrge kvaliteediga allalaaditav WebM-video (720P)", + "timedmedia-derivative-desc-1080p.webm": "Täiskõrglahutusega allalaaditav WebM-video (1080P)", + "timedmedia-derivative-desc-2160p.webm": "Allalaaditav 4K-täislahutusega WebM-video (2160P)", + "timedmedia-derivative-desc-360p.vp9.webm": "Võrgus voogedastatav VP9-tüüpi WebM-video (360P)", + "timedmedia-derivative-desc-480p.vp9.webm": "Võrgus voogedastatav VP9-tüüpi WebM-video (480P)", + "timedmedia-derivative-desc-720p.vp9.webm": "Kõrglahutusega voogedastatav VP9-tüüpi WebM-video (720P)", + "timedmedia-derivative-desc-1080p.vp9.webm": "Täiskõrglahutusega voogedastatav VP9-tüüpi WebM-video (1080P)", + "timedmedia-derivative-desc-2160p.vp9.webm": "Voogedastatav 4K-täislahutusega VP9-tüüpi WebM-video (2160P)", + "timedmedia-derivative-desc-320p.mp4": "Seadmetele sobiv MP4-video (320P)", + "timedmedia-derivative-desc-480p.mp4": "Võrgus voogedastatav MP4-video (480P)", + "timedmedia-derivative-desc-720p.mp4": "Kõrglahutusega MP4-video (720P)", + "timedmedia-derivative-desc-1080p.mp4": "Täiskõrglahutusega MP4-kvaliteetvideo (1080P)", + "timedmedia-derivative-desc-2160p.mp4": "4K-täislahutusega MP4-kvaliteetvideo (2160P)", + "timedmedia-subtitle-new": "Uue tõlkega alustamine või olemasoleva muutmine", + "timedmedia-subtitle-new-desc": "Vali keel ja klõpsa nuppu '''{{int:Timedmedia-subtitle-new-go}}'''.", + "timedmedia-subtitle-new-go": "Mine", + "timedmedia-subtitle-language": "$1 ($2) alltiitrid", + "timedmedia-subtitle-no-video": "Puudub selle alltiitrite leheküljega seotud video.", + "timedmedia-subtitle-no-subtitles": "Selle video alltiitrid puuduvad praegu selles keeles ($1). Saad seda lehekülge [{{fullurl:{{FULLPAGENAME}}|action=edit}} redigeerida], et need lisada.", + "timedmedia-subtitle-remote": "Selle faili ajastatud tekst on hoidlas $1", + "timedmedia-subtitle-remote-link": "Selle faili [$1 kirjelduslehekülge] saad vaadata hoidlas $2.", + "timedmediahandler": "Ajastatud meediafailide töötleja", + "timedmedia-videos": "{{PLURAL:$1|$1 video|$1 videot}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg-video|$1 Ogg-videot}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM-video|$1 WebM-videot}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|Üks ümberkodeering|$1 ümberkodeeringut}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|Üks|$1}} täitmisel {{PLURAL:$1|ümberkodeering|ümberkodeeringut}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|Üks|$1}} ootel {{PLURAL:$1|ümberkodeering|ümberkodeeringut}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|Üks|$1}} ebaõnnestunud {{PLURAL:$1|ümberkodeering|ümberkodeeringut}}", + "timedmedia-file": "Fail", + "timedmedia-audios": "{{PLURAL:$1|$1 helifail|$1 helifaili}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg-helifail|$1 Ogg-helifaili}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC-helifail|$1 FLAC-helifaili}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV-helifail|$1 WAV-helifaili}}", + "right-transcode-reset": "Lähtestada ebaõnnestunud või ümberkodeeritud videoid, nii et need lisatakse uuesti tööjärge", + "right-transcode-status": "Vaadata [[Special:TimedMediaHandler|teavet praegu aktiivsete ümberkodeeringute kohta]]", + "action-transcode-status": "vaadata praegust ümberkodeeringute olekut", + "orphanedtimedtext": "Ajastatud teksti orbleheküljed", + "orphanedtimedtext-summary": "Loend nimeruumi [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] lehekülgedest, millele vastav fail puudub.", + "orphanedtimedtext-unsupported": "Seda erilehekülge toetatakse ainult MySQL-andmebaasides.", + "orphanedtimedtext-notimedtext": "Ajastatud tekst pole selles vikis lubatud.", + "apihelp-query+transcodestatus-description": "Antud faili ümberkodeerimise oleku hankimine.", + "apihelp-query+transcodestatus-example-1": "Hangi faili [[:File:Clip.webm|Clip.webm]] ümberkodeerimise olek", + "apihelp-query+videoinfo-description": "Laiendab moodulit \"imageinfo\", kaasa arvatakse teave video allika (tuletiste) kohta.", + "apihelp-query+videoinfo-param-prop": "Millist teavet video kohta hankida:\n;timestamp: Lisab üles laaditud versiooni ajatempli.\n;user: Lisab kasutaja, kes videoversiooni üles laadis.\n;userid: Lisab videoversiooni üles laadinud kasutaja identifikaatori.\n;comment: Lisab versioonikommentaari.\n;parsedcomment: Parsib versioonikommentaari.\n;canonicaltitle: Lisab videofaili kanoonilise pealkirja.\n;url: Tagastab videofaili ja kirjelduslehekülje internetiaadressi.\n;size: Lisab video suuruse baitides ning selle kõrguse ja laiuse. Lehekülgede arv ja kestus lisatakse, kui see on kohane.\n;dimensions: Elemendi \"size\" rööpnimi.\n;sha1: Lisab video SHA-1 räsiväärtuse.\n;mime: Lisab video MIME tüübi.\n;thumbmime: Lisab video pisipildi MIME tüübi (vaja elementi \"url\" ja parameetrit \"$1urlwidth\").\n;mediatype: Lisab video meediatüübi.\n;metadata: Loetleb videoversiooni Exif-metaandmed.\n;commonmetadata: Loetleb videoversiooni failivormingu üldised metaandmed.\n;extmetadata: Loetleb mitme allika vormindatud ühendmetaandmed. Tulemused on HTML-vormingus.\n;archivename: Lisab praegusest versioonist vanemate arhiiviversioonide failinimed.\n;bitdepth: Lisab versiooni bitisügavuse.\n;uploadwarning: Kasutab lehekülg [[Special:Upload]], et saada teavet olemasoleva faili kohta. Pole mõeldud kasutamiseks väljaspool MediaWiki keskosa.\n;derivatives: Lisab heli- või videofaili erinevaid saadaolevaid vorminguid ja kvaliteediversioone sisaldava massiivi.", + "apihelp-query+videoinfo-example-1": "Faili [[:File:Folgers.ogv|Folgers.ogv]] teabe väljavõtt", + "apihelp-transcodereset-description": "Kasutajad, kellel on õigus \"transcode-reset\", saavad ümberkodeerimistööd lähtestada ja uuesti käitada.", + "apihelp-transcodereset-param-title": "Meediafaili pealkiri.", + "apihelp-transcodereset-param-transcodekey": "Lähtestatava ümberkodeerimise võti. Leitav päringuga [[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "Lähtesta kõik faili [[:File:Clip.webm|Clip.webm]] ümberkodeeringud", + "apihelp-transcodereset-example-2": "Lähtesta faili [[:File:Clip.webm|Clip.webm]] ümberkodeerimisvõti '360_560kbs.webm'" +} diff --git a/extensions/TimedMediaHandler/i18n/eu.json b/extensions/TimedMediaHandler/i18n/eu.json new file mode 100644 index 00000000..fb5ff526 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/eu.json @@ -0,0 +1,29 @@ +{ + "@metadata": { + "authors": [ + "An13sa", + "Joxemai", + "Theklan", + "පසිඳු කාවින්ද", + "Subi", + "Sator" + ] + }, + "timedmedia-desc": "Ogg Theora eta Vorbis fitxategientzako edukiontzia, JavaScript playerrarekin", + "timedmedia-ogg-short-audio": "Ogg $1 soinu fitxategia, $2", + "timedmedia-ogg-short-video": "Ogg $1 bideo fitxategia, $2", + "timedmedia-ogg-short-general": "Ogg $1 media fitxategia, $2", + "timedmedia-ogg-long-audio": "Ogg $1 soinu fitxategia, $2 iraupea, $3", + "timedmedia-ogg-long-error": "ogg fitxategi okerra: $1", + "timedmedia-more": "Gehiago...", + "timedmedia-dismiss": "Itxi", + "timedmedia-download": "Fitxategia jaitsi", + "timedmedia-desc-link": "Fitxategi honen inguruan", + "timedmedia-status": "Egoera", + "timedmedia-transcodeinfo": "Formatua", + "timedmedia-direct-link": "Jaitsi", + "timedmedia-source-file": "$1 iturri", + "timedmedia-source-audio-file-desc": "Jatorrizko $1 fitxategia ($2)", + "timedmedia-subtitle-new-go": "Joan", + "timedmedia-file": "Fitxategia" +} diff --git a/extensions/TimedMediaHandler/i18n/fa.json b/extensions/TimedMediaHandler/i18n/fa.json new file mode 100644 index 00000000..2a3a549c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/fa.json @@ -0,0 +1,123 @@ +{ + "@metadata": { + "authors": [ + "Alireza", + "Armin1392", + "Calak", + "Ebraminio", + "Huji", + "Mahdiz", + "Mjbmr", + "Reza1615", + "جواد", + "پاناروما", + "Mehran" + ] + }, + "extensionname-timedmedia": "هدایت‌کننده رسانهٔ زمان‌دار", + "timedmedia-desc": "به دست گیرندهٔ پرونده‌های صوتی، تصویری و متن‌های زمان‌بندی شده با پشتیبانی از Ogg Theora ،Vorbis و srt", + "timedmedia-ogg-short-audio": "پرونده صوتی Ogg $1، $2", + "timedmedia-ogg-short-video": "پرونده تصویری Ogg $1، $2", + "timedmedia-ogg-short-general": "پرونده Ogg $1، $2", + "timedmedia-ogg-long-audio": "پرونده صوتی Ogg $1، مدت $2، $3", + "timedmedia-ogg-long-video": "پرونده تصویری Ogg $1، مدت $2 ، $4×$5 پیکسل، $3", + "timedmedia-ogg-long-multiplexed": "پرونده صوتی/تصویری پیچیده Ogg، $1، مدت $2، $4×$5 پیکسل، $3 در مجموع", + "timedmedia-ogg-long-general": "پرونده Ogg، مدت $2، $3", + "timedmedia-ogg-long-error": "پروندهٔ Ogg نامجاز: $1", + "timedmedia-ogg-long-no-streams": "پروندهٔ رسانه‌ای Ogg. هشدار: هیچ‌یک از این کدک‌های استفاده‌شده در این پرونده‌ها شناخته‌شده نیستند.", + "timedmedia-webm-short-video": "رسانهٔ وب $1 پروندهٔ ویدئویی، $2", + "timedmedia-webm-long-video": "رسانهٔ وب پروندهٔ صوتی/تصویری، $1، مدت $2، $4 × $5 پیکسل، $3 کلی", + "timedmedia-flac-short-audio": "پروندهٔ صوتی اف‌ال‌ای‌سی، $1", + "timedmedia-flac-long-audio": "پروندهٔ صوتی اف‌ال‌ای‌سی، مدت $1، $2 کلی", + "timedmedia-wav-short-audio": "پروندهٔ صوتی WAV، $1", + "timedmedia-wav-long-audio": "پروندهٔ صوتی WAV، مدت $1، $2 کلی", + "timedmedia-wav-pcm-required": "شما فقط می توانید PCM و WAV را بار کنید.", + "timedmedia-mp4-short-video": "MP4 $1 پروندهٔ ویدئویی، $2", + "timedmedia-mp4-long-video": "پرونده‌های صوتی یا تصویری با قالب $1، زمان $2، $4 × $5 پیکسل و $3 به طورکلی", + "timedmedia-no-player-js": "با عرض پوزش، نرم افزار جاوا اسکریپت برروی مرورگر شما فعال نیست، و یا مرورگر شما از این نرم افزار پشتیبانی نمی کند.
\nشما میتوانید این ویدیو را دانلود و یا یک پخش کننده برای اجرای این ویدیو دانلود نمایید.", + "timedmedia-more": "بیشتر...", + "timedmedia-dismiss": "بستن", + "timedmedia-download": "دریافت پرونده", + "timedmedia-play-media": "پخش رسانه", + "timedmedia-desc-link": "دربارهٔ این پرونده", + "timedmedia-oggThumb-version": "نگهدارندهٔ Ogg به بند‌انگشتی Ogg نسخهٔ $1 یا بالاتر نیاز دارد.", + "timedmedia-oggThumb-failed": "بندانگشتی Ogg در ساخت بندانگشتی با خطا مواجه شد.", + "timedmedia-status-header": "وضعیت فراکد", + "timedmedia-update-status": "وضعیت به‌روزرسانی فراکد", + "timedmedia-status": "وضعیت", + "timedmedia-status-unknown": "وضعیت ناشناخته", + "timedmedia-transcodeinfo": "توضیحات اشتقاقی فراکد", + "timedmedia-actions": "فعالیت‌ها", + "timedmedia-direct-link": "دریافت اشتقاقی", + "timedmedia-not-ready": "آماده نیست", + "timedmedia-completed-on": "تکمیل فراکد $1", + "timedmedia-error-on": "خطا در فراکد در $1", + "timedmedia-started-transcode": "فراکد $1 پیش آغاز شد. $2", + "timedmedia-percent-done": "حدود $1% انجام شده", + "timedmedia-in-job-queue": "اضافه‌شد به صف کار $1 پیش", + "timedmedia-unknown-target-size": "اندازهٔ هدف ناشناخته، $1 رمزی", + "timedmedia-days": "{{PLURAL:$1|1 روز|$1 روز}}", + "timedmedia-hours": "{{PLURAL:$1|1 ساعت|$1 ساعت}}", + "timedmedia-minutes": "{{PLURAL:$1|1 دقیقه|$1 دقیقه}}", + "timedmedia-seconds": "{{PLURAL:$1|1 ثانیه|$1 ثانیه}}", + "timedmedia-reset": "تنظیم مجدد فراکد", + "timedmedia-reset-confirm": "تنظیم مجدد فراکد پرونده‌های موجود را حذف خواهد کرد و فراکد را در صف کار خواهد افزود. این کار مقداری زمان لازم دارد.

\nآیا می‌خواهید ادامه دهید؟", + "timedmedia-reset-error": "خطا در بازنشانی کار فراکد.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 مبدأ", + "timedmedia-source-file-desc": "اصلی $1 پرونده، $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "اصلی $1 پرونده ( $2 )", + "timedmedia-derivative-desc-160p.ogv": "ویدئو Ogg با پهنای باند کم (160P)", + "timedmedia-derivative-desc-240p.ogv": "ویدیوی آگ قابل بخش در وب (۲۴۰ نقطه)", + "timedmedia-derivative-desc-360p.ogv": "ویدئو Ogg با پهنای وب (360P)", + "timedmedia-derivative-desc-480p.ogv": "ویدئو Ogg با پهنای وب (480P)", + "timedmedia-derivative-desc-720p.ogv": "ویدئو Ogg قابل بارگیری با کیفیت بالا (720P)", + "timedmedia-derivative-desc-1080p.ogv": "ویدیوی آگ کاملاً اچ‌دی (۱۰۸۰ پیکسل)", + "timedmedia-derivative-desc-160p.webm": "رسانه وب با پهنای وب (160P)", + "timedmedia-derivative-desc-360p.webm": "رسانه وب با پهنای وب (360P)", + "timedmedia-derivative-desc-480p.webm": "رسانه وب با پهنای وب (480P)", + "timedmedia-derivative-desc-720p.webm": "رسانه وب قابل بارگیری با کیفیت بالا (720P)", + "timedmedia-derivative-desc-1080p.webm": "ویدیوی وب‌ام کاملاً اچ‌دی (۱۰۸۰ پیکسل)", + "timedmedia-derivative-desc-320p.mp4": "ابزار سازگار با MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "پهنای وب MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "کیفیت بالا MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "ویدیوی ام‌پی۴ کاملاً اچ‌دی (۱۰۸۰ پیکسل)", + "timedmedia-subtitle-new": "ترجمه جدیدی را ایجاد کنید یا در صورت وجود آن را ویرایش کنید.", + "timedmedia-subtitle-new-desc": " زبان را انتخاب کنید و دکمهٔ '''{{int:Timedmedia-subtitle-new-go}}''' را بفشارید", + "timedmedia-subtitle-new-go": "برو", + "timedmedia-subtitle-language": "$1 ($2) زیرنویس‌ها", + "timedmedia-subtitle-no-video": "هیچ ویدئویی مرتبط با زیرعنوان این صفحه موجود نیست", + "timedmedia-subtitle-no-subtitles": "در حال حاضر زیرنویسی برای $1 در این ویدیو وجود ندارد، می‌توانید این صفحه را برای افزودن آن‌ها [{{fullurl:{{FULLPAGENAME}}|action=edit}} ویرایش کنید].", + "timedmedia-subtitle-remote": "متن زمان‌دار برای پرونده ذخیره شده در $1", + "timedmedia-subtitle-remote-link": "شما می توانید [$1 توضیحات صفحه مشاهده کنید] برای این پرونده در $2", + "timedmediahandler": "هدایت‌کننده رسانهٔ زمان‌دار", + "timedmedia-videos": "{{PLURAL:$1|$1 ویدئو|$1 ویدئو}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 ویدئو Ogg|$1 ویدئو Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 ویدئو WebM|$1 ویدئو WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 فراکدها|$1 فراکدها}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 درحال اجرای فراکدها|$1 در حال اجرای فراکدها}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 صف فراکدها|$1 صف فراکدها}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 فراکدهای اشتباه|$1 فراکدهای اشتباه}}", + "timedmedia-file": "پرونده", + "timedmedia-audios": "{{PLURAL:$1|$1 پوشهٔ صوتی|$1 پوشه‌های صوتی}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 پوشهٔ صوتی آگ|$1 پوشه‌های صوتی آگ}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1پوشه‌ٔ صوتی اف‌ال‌ای‌سی|$1پوشه‌های صوتی اف‌ال‌ای‌سی}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 پوشه‌ٔ صوتی دبلیو‌ای‌وی|$1 پوشه‌های صوتی دبلیو‌ای‌وی}}", + "right-transcode-reset": "تنظیم مجدد انجام‌نشده‌ها یا ویدئوهای فراکدشده اعمال شد. در نتیجه مجدداً آنها در صف کار قرار گرفتند.", + "right-transcode-status": "مشاهدهٔ [[Special:TimedMediaHandler|اطلاعات در مورد فعالیت فراکد]]", + "action-transcode-status": "نمایش وضعیت فراکدگذاری کنونی", + "orphanedtimedtext": "صفحه‌های TimedText یتیم", + "orphanedtimedtext-summary": "فهرست صفحه‌های [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] که دارای پروندهٔ متناظر نیستند.", + "orphanedtimedtext-unsupported": "این صفحهٔ خاص فقط در پایگاه داده‌های مای‌اس‌کیوال حمایت شده‌است.", + "orphanedtimedtext-notimedtext": "TimedText در این ویکی فعال نشده است.", + "apihelp-query+transcodestatus-description": "گرفتن وضعیت کذگذاری برای یک صفحه مشخص شده.", + "apihelp-query+transcodestatus-example-1": "گرفتن وضعیت کدگذاری [[:File:Clip.webm]]", + "apihelp-query+videoinfo-description": "گسترش اطلاعات تصویری به شامل شدن اطلاعات منبع تصویر (اشتقاق‌شده‌ها)", + "apihelp-query+videoinfo-param-prop": "چه اطلاعاتی دریافت می‌شود:\n;timestamp:مهرزمانی به نسخهٔ بارگذاری شده می‌افزاید.\n;user:کاربری که تصویر را بارگذاری کرده را می‌افزاید.\n;userid:شناسهٔ کاربری که ویدیو را بارگذاری کرده است که نسخهٔ ویدیویی را بارگذاری کرده است می‌افزاید.\n;comment:توضیحات نسخه.\n;parsedcomment:تجزیه توضیحات نسخه.\n;canonicaltitle:عنوان متمرکز پرونده ویدیویی.\n;url:نشانی اینترنتی از ویدیوی و صفحهٔ توضیحات.\n;size:حجم ویدیو به بایت، طول و عرض آن را می‌افزاید. تعداد صفحه و طول اگر قابل اطلاق باشد.\n;dimensions:نام مستعار برای اندازه.\n;sha1:افزود SHA-1 hash برای ویدئو.\n;mime:افزود نوع MIME برای ویدئو.\n;thumbmime:افزود نوع MIME برای بندانگشتی ویدئو (نیازمند نشانی و متغییر $1urlwidth).\n;mediatype:نوع رسانهٔ ویدئو را افزود.\n;metadata:فراداده‌های Exif برای نسخهٔ ویدیو فهرست می‌کند.\n;commonmetadata:فراداده‌های عمومی برای نسخهٔ قالب پرونده ویدیو فهرست می‌کند.\n;extmetadata:فراداده‌های ترکیبی قالب‌دهی شده از چندین منبع را فهرست می‌کند. نتیجه اج‌تی‌ام‌ال قالب‌دهی شده است.\n;archivename:نام پرونده نسخهٔ بایگانی‌شده را برای نسخه‌های غیر آخرین می‌افزاید.\n;bitdepth:عمق بیتی نسخه را می‌افزاید.\n;uploadwarning:استفاده شده توسط صفحه [[Special:Upload]] برای دریافت اطلاعاتی از یک پرونده موجود. برای استفاده بیرون از هستهٔ‌ مدیاویکی نیست.\n;derivatives:آرایه‌ای از قالب‌ها و کیفیت نسخه‌های یک پرونده صدا یا تصویری که پرونده‌ای که موجود است می‌افزاید.", + "apihelp-query+videoinfo-example-1": "واکشی اطلاعاتی درباره [[:File:Folgers.ogv]]", + "apihelp-transcodereset-description": "کاربرهیی که دسترسی 'transcode-reset' می‌توانند یک کار کدگذاری را از نو کنند.", + "apihelp-transcodereset-param-title": "عنوان پرونده.", + "apihelp-transcodereset-param-transcodekey": "کلید کدگذاری که می‌خواهید از نو شود. گرفتن از [[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "از نو کردن کدگذاری‌ها برای [[:File:Clip.webm]]", + "apihelp-transcodereset-example-2": "از نو کردن کلید کدگذاری '360_560kbs.webm' برای [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/fi.json b/extensions/TimedMediaHandler/i18n/fi.json new file mode 100644 index 00000000..ef19cca9 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/fi.json @@ -0,0 +1,78 @@ +{ + "@metadata": { + "authors": [ + "Agony", + "Beluga", + "Crt", + "Nike", + "Pxos", + "Str4nd", + "VezonThunder", + "Pitke" + ] + }, + "timedmedia-desc": "Käsittelijä äänelle, videolle ja tekstitykselle. Tukee muotoja WebM, Ogg Theora, Vorbis ja srt.", + "timedmedia-ogg-short-audio": "Ogg $1 -äänitiedosto, $2", + "timedmedia-ogg-short-video": "Ogg $1 -videotiedosto, $2", + "timedmedia-ogg-short-general": "Ogg $1 -mediatiedosto, $2", + "timedmedia-ogg-long-audio": "Ogg $1 -äänitiedosto, $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 -videotiedosto, $2, $4×$5, $3", + "timedmedia-ogg-long-multiplexed": "Ogg-tiedosto limitetty kuva ja ääni, $1, $2, $4×$5, $3", + "timedmedia-ogg-long-general": "Ogg-tiedosto, $2, $3", + "timedmedia-ogg-long-error": "Kelvoton Ogg-tiedosto: $1", + "timedmedia-webm-short-video": "WebM $1 -videotiedosto, $2", + "timedmedia-webm-long-video": "WebM-ääni-/videotiedosto, $1, pituus $2, $4×$5 pikseliä, yhteensä $3", + "timedmedia-mp4-short-video": "MP4 $1 -videotiedosto, $2", + "timedmedia-mp4-long-video": "MP4-ääni-/videotiedosto, $1, pituus $2, $4×$5 pikseliä, yhteensä $3", + "timedmedia-no-player-js": "Valitettavasti selaimessasi on joko JavaScript pois päältä tai sille ei ole tuettua soitinta.
\nVoit ladata leikkeen tai ladata soittimen toistaaksesi sen selaimessasi.", + "timedmedia-more": "Lisää…", + "timedmedia-dismiss": "Sulje", + "timedmedia-download": "Lataa", + "timedmedia-play-media": "Toista mediaa", + "timedmedia-desc-link": "Tiedoston tiedot", + "timedmedia-oggThumb-version": "OggHandler vaatii oggThumbin version $1 tai uudemman.", + "timedmedia-oggThumb-failed": "oggThumb epäonnistui pikkukuvan luomisessa.", + "timedmedia-status": "Tila", + "timedmedia-status-unknown": "Tuntematon tila", + "timedmedia-transcodeinfo": "Muoto", + "timedmedia-actions": "Toiminnot", + "timedmedia-direct-link": "Lataa", + "timedmedia-not-ready": "Ei valmis", + "timedmedia-completed-on": "Valmistui $1", + "timedmedia-started-transcode": "Alkoi $1 sitten. $2", + "timedmedia-percent-done": "Noin $1% valmiina", + "timedmedia-in-job-queue": "Lisättiin työjonoon $1 sitten", + "timedmedia-days": "{{PLURAL:$1|1 päivä|$1 päivää}}", + "timedmedia-hours": "{{PLURAL:$1|1 tunti|$1 tuntia}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuutti|$1 minuuttia}}", + "timedmedia-seconds": "{{PLURAL:$1|1 sekunti|$1 sekuntia}}", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Lähde $1", + "timedmedia-source-file-desc": "Alkuperäinen $1-tiedosto, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Alkuperäinen $1-tiedosto ($2)", + "timedmedia-derivative-desc-160p.ogv": "Pienen kaistanleveyden Ogg-video (160p)", + "timedmedia-derivative-desc-240p.ogv": "Streamautuva Ogg-video (240p)", + "timedmedia-derivative-desc-360p.ogv": "verkossa suoratoistettavaa Ogg-videota (360p)", + "timedmedia-derivative-desc-480p.ogv": "Verkossa suoratoistettava Ogg-video (480p)", + "timedmedia-derivative-desc-720p.ogv": "Korkealaatuinen ladattava Ogg-video (720p)", + "timedmedia-derivative-desc-160p.webm": "Verkossa suoratoistettava WebM-video (160p)", + "timedmedia-derivative-desc-360p.webm": "Verkossa suoratoistettava WebM-video (360p)", + "timedmedia-derivative-desc-480p.webm": "Verkossa suoratoistettava WebM-video (480p)", + "timedmedia-derivative-desc-720p.webm": "Korkealaatuinen ladattava WebM-video (720p)", + "timedmedia-derivative-360p.vp9.webm": "VP9 360P", + "timedmedia-derivative-480p.vp9.webm": "VP9 480P", + "timedmedia-derivative-desc-320p.mp4": "Laiteystävällinen MP4 (320p)", + "timedmedia-derivative-desc-480p.mp4": "Verkossa suoratoistettava MP4 (480p)", + "timedmedia-derivative-desc-720p.mp4": "HD-laatuinen MP4 (720p)", + "timedmedia-subtitle-new": "Luo uusi käännös tai muokkaa aiempaa", + "timedmedia-subtitle-new-desc": "Valitse kieli ja napsauta '''{{int:Timedmedia-subtitle-new-go}}'''.", + "timedmedia-subtitle-new-go": "Siirry", + "timedmedia-subtitle-language": "$1 ($2) kuvatekstit", + "timedmedia-subtitle-no-video": "Tähän tekstityssivuun ei liity videota", + "timedmedia-subtitle-no-subtitles": "Tälle videolle ei tällä hetkellä ole tekstityksiä kielellä $1. Voit [{{fullurl:{{FULLPAGENAME}}|action=edit}} muokata tätä sivua] lisätäksesi ne.", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 video|$1 videota}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg-video|$1 Ogg-videota}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM-video|$1 WebM-videota}}", + "timedmedia-file": "Tiedosto" +} diff --git a/extensions/TimedMediaHandler/i18n/fo.json b/extensions/TimedMediaHandler/i18n/fo.json new file mode 100644 index 00000000..a96f3de3 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/fo.json @@ -0,0 +1,12 @@ +{ + "@metadata": { + "authors": [ + "Spacebirdy", + "EileenSanda" + ] + }, + "timedmedia-no-player-js": "Halt okkum til góðar, tín brovsari hevur antin gjørt JavaScript óvirkið ella hevur ongan avspælara ið virkar her.
\nTú kanst taka klippið niður ella taka niður ein avspælara sum kann spæla klippið í tínum internetkaga.", + "timedmedia-more": "Meira...", + "timedmedia-source-file": "$1 kelda", + "timedmedia-source-audio-file-desc": "Uppruna $1 fíla ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/fr.json b/extensions/TimedMediaHandler/i18n/fr.json new file mode 100644 index 00000000..e252d5c4 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/fr.json @@ -0,0 +1,150 @@ +{ + "@metadata": { + "authors": [ + "Boniface", + "Brunoperel", + "Crochet.david", + "Erkethan", + "Gomoko", + "Grondin", + "Hashar", + "Hello71", + "IAlex", + "Jean-Frédéric", + "McDutchie", + "Od1n", + "Peter17", + "Rzuwig", + "Seb35", + "Sherbrooke", + "Shirayuki", + "Urhixidur", + "Verdy p", + "Framafan", + "Wladek92" + ] + }, + "timedmedia-desc": "Gestionnaire pour les vidéos, enregistrements sonores et textes synchronisés, avec le support des formats WebM, Ogg Theora, Vorbis et srt", + "timedmedia-ogg-short-audio": "Fichier son Ogg $1, $2", + "timedmedia-ogg-short-video": "Fichier vidéo Ogg $1, $2", + "timedmedia-ogg-short-general": "Fichier média Ogg $1, $2", + "timedmedia-ogg-long-audio": "Fichier son Ogg $1, durée $2, $3", + "timedmedia-ogg-long-video": "Fichier vidéo Ogg $1, durée $2, $4 × $5 pixels, $3", + "timedmedia-ogg-long-multiplexed": "Fichier multiplexé audio/vidéo Ogg, $1, durée $2, $4 × $5 pixels, $3 au total", + "timedmedia-ogg-long-general": "Fichier média Ogg, durée $2, $3", + "timedmedia-ogg-long-error": "Fichier Ogg invalide : $1", + "timedmedia-ogg-long-no-streams": "Fichier de média ogg. Attention : Aucun des codecs utilisés dans ce fichier n’est reconnu.", + "timedmedia-webm-short-video": "Fichier vidéo WebM $1, $2", + "timedmedia-webm-long-video": "Fichier audio/vidéo WebM, $1, longueur $2, $4 x $5 pixels, $3 l'ensemble", + "timedmedia-flac-short-audio": "Fichier audio FLAC, $1", + "timedmedia-flac-long-audio": "Fichier audio FLAC, durée $1, débit $2 sur l’ensemble", + "timedmedia-wav-short-audio": "Fichier audio WAV, $1", + "timedmedia-wav-long-audio": "Fichier audio WAV, durée $1, $2 sur l’ensemble", + "timedmedia-wav-pcm-required": "Vous ne pouvez télécharger que des WAV PCM (Modulation d’impulsion codée).", + "timedmedia-mp4-short-video": "Fichier vidéo MP4 $1, $2", + "timedmedia-mp4-long-video": "Fichier audio/vidéo MP4, $1, taille $2, $4 x $5 pixels, $3 au total", + "timedmedia-no-player-js": "Désolé, soit votre navigateur a JavaScript désactivé, soit il ne dispose d’aucun lecteur pris en charge.
\nVous pouvez télécharger le clip ou télécharger un lecteur pour lire le clip dans votre navigateur.", + "timedmedia-more": "Plus…", + "timedmedia-dismiss": "Fermer", + "timedmedia-download": "Télécharger le fichier", + "timedmedia-play-media": "Lire le média", + "timedmedia-desc-link": "À propos de ce fichier", + "timedmedia-oggThumb-version": "OggHandler nécessite oggThumb, version $1 ou supérieure.", + "timedmedia-oggThumb-failed": "oggThumb n’a pas réussi à créer la miniature.", + "timedmedia-status-header": "Statut de transcodage", + "timedmedia-update-status": "Mettre à jour le statut de transcodage", + "timedmedia-status": "Statut", + "timedmedia-status-unknown": "État inconnu", + "timedmedia-transcodebitrate": "Débit en bits", + "timedmedia-transcodeduration": "Durée de l'encodage", + "timedmedia-transcodeinfo": "Transcode la description dérivée", + "timedmedia-actions": "Actions", + "timedmedia-direct-link": "Télécharger", + "timedmedia-not-ready": "Pas prêt", + "timedmedia-completed-on": "$1 terminé", + "timedmedia-error-on": "Erreur sur $1", + "timedmedia-started-transcode": "Démarré depuis $1. $2", + "timedmedia-percent-done": "Environ $1 % complété", + "timedmedia-in-job-queue": "Ajouté à la file d’attente des travaux il y a $1", + "timedmedia-unknown-target-size": "Taille de la cible inconnue, $1 encodé", + "timedmedia-days": "{{PLURAL:$1|1 jour|$1 jours}}", + "timedmedia-hours": "{{PLURAL:$1|1 heure|$1 heures}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minute|$1 minutes}}", + "timedmedia-seconds": "{{PLURAL:$1|1 seconde|$1 secondes}}", + "timedmedia-reset": "Réinitialiser le transcodage", + "timedmedia-reset-confirm": "Remettre à zéro ce transcodage supprimera tout fichier existant (s’il y en a) et ajoutera de nouveau le transcodage à la file de travaux. Cela prendra un certain temps pour être transcodé de nouveau.

\nÊtes-vous sûr de vouloir continuer ?", + "timedmedia-reset-error": "Erreur en réinitialisant le travail de transcodage.", + "timedmedia-ogg": "Ogg", + "timedmedia-webm": "WebM", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Source $1", + "timedmedia-source-file-desc": "Original fichier $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Fichier original $1 ($2)", + "timedmedia-derivative-160p.ogv": "Ogg 160p", + "timedmedia-derivative-desc-160p.ogv": "Vidéo Ogg bas débit (160P)", + "timedmedia-derivative-desc-240p.ogv": "Vidéo Ogg en flux continu sur le web (240P)", + "timedmedia-derivative-360p.ogv": "Ogg 360p", + "timedmedia-derivative-desc-360p.ogv": "Vidéo Ogg lisible en continu sur le Web (360p)", + "timedmedia-derivative-480p.ogv": "Ogg 480p", + "timedmedia-derivative-desc-480p.ogv": "Vidéo Ogg lisible en continu sur le web (480p)", + "timedmedia-derivative-720p.ogv": "Ogg 720p", + "timedmedia-derivative-desc-720p.ogv": "Vidéo Ogg téléchargeable de grande qualité (720p)", + "timedmedia-derivative-desc-1080p.ogv": "Vidéo Ogg téléchargeable en HD total (1080P)", + "timedmedia-derivative-desc-160p.webm": "WebM lisible en continu depuis le web (160p)", + "timedmedia-derivative-360p.webm": "WebM 360p", + "timedmedia-derivative-desc-360p.webm": "WebM lisible en continu depuis le web (360p)", + "timedmedia-derivative-480p.webm": "WebM 480p", + "timedmedia-derivative-desc-480p.webm": "WebM lisible en continu depuis le web (480p)", + "timedmedia-derivative-720p.webm": "WebM 720p", + "timedmedia-derivative-desc-720p.webm": "Vidéo WebM téléchargeable de grande qualité (720p)", + "timedmedia-derivative-desc-1080p.webm": "WebM téléchargeable en HD total (1080P)", + "timedmedia-derivative-desc-2160p.webm": "WebM télécharheable en 4K total (2160P)", + "timedmedia-derivative-desc-360p.vp9.webm": "WebM VP9 (360P) lisible en continu sur le web", + "timedmedia-derivative-desc-480p.vp9.webm": "WebM VP9 (480P) lisible en continu sur le web", + "timedmedia-derivative-desc-720p.vp9.webm": "WebM VP9 haute définition (720P) lisible en continu sur le web", + "timedmedia-derivative-desc-1080p.vp9.webm": "WebM VP9 en haute définition totale (1080P) lisible en continu sur le web", + "timedmedia-derivative-desc-2160p.vp9.webm": "WebM VP9 4K complet (2160P) lisible en continu sur le web", + "timedmedia-derivative-desc-320p.mp4": "MP4 compatible matériel (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 pour flux web (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 qualité HD (720P)", + "timedmedia-derivative-desc-1080p.mp4": "MP4 de qualité HD total (1080P)", + "timedmedia-derivative-desc-2160p.mp4": "MP4 de qualité haute définition 4K", + "timedmedia-subtitle-new": "Créer une nouvelle traduction ou modifier une traduction existante", + "timedmedia-subtitle-new-desc": "Sélectionnez la langue et appuyez sur le bouton '''{{int:Timedmedia-subtitle-new-go}}'''.", + "timedmedia-subtitle-new-go": "Aller", + "timedmedia-subtitle-language": "sous-titres en $1 ($2)", + "timedmedia-subtitle-no-video": "Il n’y a aucune vidéo associée à la page actuelle de sous-titre", + "timedmedia-subtitle-no-subtitles": "Il n’y a actuellement aucun sous-titres en $1 pour cette vidéo, vous pouvez [{{fullurl: {{FULLPAGENAME}} | action=edit}} modifier cette page] pour les ajouter", + "timedmedia-subtitle-remote": "Le texte prévu pour ce fichier est hébergé sur $1", + "timedmedia-subtitle-remote-link": "Vous pouvez [$1 voir la page de description] de ce fichier sur $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 vidéo|$1 vidéos}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 vidéo Ogg|$1 vidéos Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 vidéo WebM|$1 vidéos WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transcodage|$1 transcodages}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 transcodage en cours|$1 transcodages en cours}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transcodage en file d'attente|$1 transcodages en file d'attente}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 transcodage échoué|$1 transcodages échoués}}", + "timedmedia-file": "Fichier", + "timedmedia-audios": "{{PLURAL:$1|$1 fichier audio|$1 fichiers audio}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 fichier audio Ogg|$1 fichiers audio Ogg}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 fichier audio FLAC|$1 fichiers audio FLAC}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 fichier audio WAV|$1 fichiers audio WAV}}", + "right-transcode-reset": "Réinitialiser les vidéos en échec ou transcodées pour qu’elles soient de nouveau insérées dans la file des travaux", + "right-transcode-status": "Afficher [[Special:TimedMediaHandler|l'information sur l'activité de transcodage actuelle]]", + "action-transcode-status": "afficher l’état de transcodage actuel", + "orphanedtimedtext": "Pages orphelines de TimedText", + "orphanedtimedtext-summary": "Liste des pages [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] qui n’ont pas de fichier correspondant.", + "orphanedtimedtext-unsupported": "Cette page spéciale n’est supportée que sur les bases de données MySQL.", + "orphanedtimedtext-notimedtext": "TimedText n’est pas activé sur ce wiki.", + "apihelp-query+transcodestatus-description": "Obtenir l’état de transcodage pour une page de fichier donné.", + "apihelp-query+transcodestatus-example-1": "Obtenir l’état de transcodage pour [[:File:Clip.webm]]", + "apihelp-query+videoinfo-description": "Étends imageinfo pour inclure les informations sur la source (et dérivées) vidéo", + "apihelp-query+videoinfo-param-prop": "Quelles informations obtenir sur la vidéo :\n;timestamp:Ajoute l’horodatage pour la version téléchargée.\n;user:Ajoute l’utilisateur qui a téléchargé la version de la vidéo.\n;userid:Ajouter l’ID de l’utilisateur qui a téléchargé la version de la vidéo.\n;comment:Commentaire sur la version.\n;parsedcomment:Analyser le commentaire sur la version.\n;canonicaltitle:Ajoute le titre canonique du fichier vidéo.\n;url:Fournit l’URL de la vidéo et la page de description.\n;size:Ajoute la taille de la vidéo en octets, sa hauteur et sa largeur. Le compteur de page et la durée sont ajoutés le cas échéant.\n;dimensions:Alias pour la taille.\n;sha1:Ajoute le hachage SHA-1 pour la vidéo.\n;mime:Ajoute le type MIME de la vidéo.\n;thumbmime:Ajoute le type MIME de la vignette de la vidéo (nécessite l’URL et le paramètre $1urlwidth).\n;mediatype:Ajoute le type de média de la vidéo.\n;metadata:Liste les métadonnées Exif pour la version de la vidéo.\n;commonmetadata:Liste les métadonnées génériques du format de fichier pour la version de la vidéo.\n;extmetadata:Liste les métadonnées mises en forme combinées depuis plusieurs sources. Les résultats sont au format HTML.\n;archivename:Ajoute le nom de fichier de la version d’archive pour les versions hors la dernière.\n;bitdepth:Ajoute la profondeur en bits de la version.\n;uploadwarning:Utilisé par la page [[Special:Upload]] pour obtenir des informations sur un fichier existant. Non prévu pour être utilisé hors du cœur de MédiaWiki.\n;derivatives:Ajoute un tableau des différentes versions de format et de qualité d’un fichier audio ou vidéo disponibles.", + "apihelp-query+videoinfo-example-1": "Récupérer des informations sur [[:File:Folgers.ogv]]", + "apihelp-transcodereset-description": "Les utilisateurs avec le droit 'transcode-reset' peuvent réinitialiser et relancer une tâche de transcodage.", + "apihelp-transcodereset-param-title": "Le titre du fichier média.", + "apihelp-transcodereset-param-transcodekey": "La clé du transcodage que vous voulez réinitialiser. Récupéré depuis [[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "Réinitialiser tous les codes de transcodage de [[:File:Clip.webm]]", + "apihelp-transcodereset-example-2": "Réinitialiser la clé de transcodage '360_560kbs.webm' pour [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/frp.json b/extensions/TimedMediaHandler/i18n/frp.json new file mode 100644 index 00000000..ecf9cbc8 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/frp.json @@ -0,0 +1,38 @@ +{ + "@metadata": { + "authors": [ + "ChrisPtDe" + ] + }, + "timedmedia-desc": "Assistance por los fichiérs mèdia que dèfilont (ôdiô, vidèô et tèxto sincronisâ) avouéc transcodâjo en WebM, Ogg Theora, Vorbis et srt.", + "timedmedia-ogg-short-audio": "Fichiér son Ogg $1, $2", + "timedmedia-ogg-short-video": "Fichiér vidèô Ogg $1, $2", + "timedmedia-ogg-short-general": "Fichiér mèdia Ogg $1, $2", + "timedmedia-ogg-long-audio": "Fichiér son Ogg $1, temps $2, $3", + "timedmedia-ogg-long-video": "Fichiér vidèô Ogg $1, temps $2, $4×$5 pixèls, $3", + "timedmedia-ogg-long-multiplexed": "Fichiér multiplèxo ôdiô / vidèô Ogg, $1, temps $2, $4×$5 pixèls, $3", + "timedmedia-ogg-long-general": "Fichiér mèdia Ogg, temps $2, $3", + "timedmedia-ogg-long-error": "Fichiér Ogg envalido : $1", + "timedmedia-webm-short-video": "Fichiér vidèô WebM $1, $2", + "timedmedia-webm-long-video": "Fichiér ôdiô / vidèô WebM, $1, longior $2, $4 x $5 pixèls, en tot $3", + "timedmedia-more": "De ples...", + "timedmedia-dismiss": "Cllôre", + "timedmedia-download": "Tèlèchargiér lo fichiér", + "timedmedia-play-media": "Liére lo mèdia", + "timedmedia-desc-link": "Sur ceti fichiér", + "timedmedia-oggThumb-version": "OggHandler at fôta d’oggThumb, vèrsion $1 ou ben ples novèla.", + "timedmedia-oggThumb-failed": "oggThumb at pas reussi a fâre la figura.", + "timedmedia-status": "statut", + "timedmedia-status-unknown": "statut encognu", + "timedmedia-actions": "Accions", + "timedmedia-not-ready": "Pas prèst", + "timedmedia-percent-done": "A pou prés $1 % complètâ", + "timedmedia-days": "$1 jorn{{PLURAL:$1||s}}", + "timedmedia-hours": "$1 hor{{PLURAL:$1|a|es}}", + "timedmedia-minutes": "$1 menut{{PLURAL:$1|a|es}}", + "timedmedia-seconds": "$1 second{{PLURAL:$1|a|es}}", + "timedmedia-source-file": "Sôrsa $1", + "timedmedia-source-file-desc": "Originâl $1, $2 × $3 ($4)", + "timedmedia-subtitle-language": "sot-titros en $1 ($2)", + "timedmedia-file": "Fichiér" +} diff --git a/extensions/TimedMediaHandler/i18n/frr.json b/extensions/TimedMediaHandler/i18n/frr.json new file mode 100644 index 00000000..8a1f77a8 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/frr.json @@ -0,0 +1,11 @@ +{ + "@metadata": { + "authors": [ + "Murma174" + ] + }, + "timedmedia-no-player-js": "Bi dan browser as det brüken faan JavaScript deaktiwiaret wurden of hi koon det ianfach ei ufspele.
\nDü könst det video deelloose of en software deelloose, am det video uun dan browser uftuspelin.", + "timedmedia-source-file": "$1-kwel", + "timedmedia-source-audio-file-desc": "Originaal $1-datei ($2)", + "timedmediahandler": "Timed Media Handler" +} diff --git a/extensions/TimedMediaHandler/i18n/fur.json b/extensions/TimedMediaHandler/i18n/fur.json new file mode 100644 index 00000000..b80405f4 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/fur.json @@ -0,0 +1,23 @@ +{ + "@metadata": { + "authors": [ + "Klenje" + ] + }, + "timedmedia-desc": "Gjestôr pai files audio, video e di test cul timp, cun supuart pai formâts WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "File audio Ogg $1, $2", + "timedmedia-ogg-short-video": "File video Ogg $1, $2", + "timedmedia-ogg-short-general": "File multimediâl Ogg $1, $2", + "timedmedia-ogg-long-audio": "File audio Ogg $1, durade $2, $3", + "timedmedia-ogg-long-video": "File video Ogg $1, durade $2, dimensions $4×$5 pixels, $3", + "timedmedia-ogg-long-multiplexed": "File audio/video multiplexed Ogg $1, lungjece $2, dimensions $4×$5 pixels, in dut $3", + "timedmedia-ogg-long-general": "File multimediâl Ogg, durade $2, $3", + "timedmedia-ogg-long-error": "File ogg no valit: $1", + "timedmedia-webm-short-video": "File video WebM $1, $2", + "timedmedia-more": "Altri...", + "timedmedia-dismiss": "Siere", + "timedmedia-download": "Discjame il file", + "timedmedia-desc-link": "Informazions su chest file", + "timedmedia-status": "Stât", + "timedmedia-actions": "Azions" +} diff --git a/extensions/TimedMediaHandler/i18n/fy.json b/extensions/TimedMediaHandler/i18n/fy.json new file mode 100644 index 00000000..747f3b0b --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/fy.json @@ -0,0 +1,16 @@ +{ + "@metadata": { + "authors": [ + "Robin0van0der0vliet" + ] + }, + "timedmedia-more": "Mear…", + "timedmedia-dismiss": "Slute", + "timedmedia-days": "{{PLURAL:$1|1 dei|$1 dagen}}", + "timedmedia-hours": "{{PLURAL:$1|1 oere|$1 oere}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minút|$1 minuten}}", + "timedmedia-seconds": "{{PLURAL:$1|1 sekonde|$1 sekonden}}", + "timedmedia-source-file": "$1 boarne", + "timedmedia-source-audio-file-desc": "Orizjineel $1-triem ($2)", + "timedmedia-file": "Triem" +} diff --git a/extensions/TimedMediaHandler/i18n/ga.json b/extensions/TimedMediaHandler/i18n/ga.json new file mode 100644 index 00000000..d59b6164 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ga.json @@ -0,0 +1,13 @@ +{ + "@metadata": { + "authors": [ + "Spacebirdy", + "පසිඳු කාවින්ද" + ] + }, + "timedmedia-more": "Níos mó...", + "timedmedia-dismiss": "Dún", + "timedmedia-actions": "Gníomhartha", + "timedmedia-subtitle-new-go": "Gabh", + "timedmedia-file": "Comhad" +} diff --git a/extensions/TimedMediaHandler/i18n/gl.json b/extensions/TimedMediaHandler/i18n/gl.json new file mode 100644 index 00000000..8c40d72d --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/gl.json @@ -0,0 +1,125 @@ +{ + "@metadata": { + "authors": [ + "Toliño", + "Xosé", + "Banjo", + "Elisardojm" + ] + }, + "timedmedia-desc": "Manipulador de son, vídeo e texto sincronizado, con soporte para os formatos WebM, Ogg Theora, Vorbis e srt", + "timedmedia-ogg-short-audio": "Ficheiro de son Ogg $1, $2", + "timedmedia-ogg-short-video": "Ficheiro de vídeo Ogg $1, $2", + "timedmedia-ogg-short-general": "Ficheiro multimedia Ogg $1, $2", + "timedmedia-ogg-long-audio": "Ficheiro de son Ogg $1, duración $2, $3", + "timedmedia-ogg-long-video": "Ficheiro de vídeo Ogg $1, duración $2, $4×$5 píxeles, $3", + "timedmedia-ogg-long-multiplexed": "Ficheiro de son/vídeo Ogg multiplex, $1, duración $2, $4×$5 píxeles, $3 total", + "timedmedia-ogg-long-general": "Ficheiro multimedia Ogg, duración $2, $3", + "timedmedia-ogg-long-error": "Ficheiro Ogg non válido: $1", + "timedmedia-ogg-long-no-streams": "Ficheiro multimedia Ogg. Atención: Non se recoñece ningún dos codecs usados neste ficheiro.", + "timedmedia-webm-short-video": "Ficheiro de vídeo WebM $1, $2", + "timedmedia-webm-long-video": "Ficheiro WebM de son/vídeo, $1, duración $2, $4×$5 píxeles, $3 total", + "timedmedia-flac-short-audio": "Ficheiro de son FLAC, $1", + "timedmedia-flac-long-audio": "Ficheiro de son FLAC, duración $1, $2 total", + "timedmedia-wav-short-audio": "Ficheiro de son WAV, $1", + "timedmedia-wav-long-audio": "Ficheiro de son WAV, duración $1, $2 total", + "timedmedia-wav-pcm-required": "Unicamente pode cargar WAV PCM (modulación por código de pulsos).", + "timedmedia-mp4-short-video": "Ficheiro de vídeo MP4 $1, $2", + "timedmedia-mp4-long-video": "Ficheiro MP4 de son/vídeo, $1, duración $2, $4×$5 píxeles, $3 total", + "timedmedia-no-player-js": "Sentímolo, o seu navegador ten o JavaScript desactivado ou non conta con ningún reprodutor dos soportados.
\nPode descargar o vídeo ou descargar un reprodutor para reproducir o vídeo no seu navegador.", + "timedmedia-more": "Máis...", + "timedmedia-dismiss": "Pechar", + "timedmedia-download": "Descargar o ficheiro", + "timedmedia-play-media": "Reproducir o contido", + "timedmedia-desc-link": "Acerca deste ficheiro", + "timedmedia-oggThumb-version": "O OggHandler necesita a versión $1 ou unha posterior do oggThumb.", + "timedmedia-oggThumb-failed": "Houbo un erro por parte do oggThumb ao crear a miniatura.", + "timedmedia-status-header": "Estado da transcodificación", + "timedmedia-update-status": "Actualizar o estado de transcodificación", + "timedmedia-status": "Estado", + "timedmedia-status-unknown": "Estado descoñecido", + "timedmedia-transcodebitrate": "Taxa de transferencia", + "timedmedia-transcodeduration": "Tempo de codificación", + "timedmedia-transcodeinfo": "Formato", + "timedmedia-actions": "Accións", + "timedmedia-direct-link": "Descargar", + "timedmedia-not-ready": "Aínda non está preparado", + "timedmedia-completed-on": "Completado o $1", + "timedmedia-error-on": "Erro en $1", + "timedmedia-started-transcode": "Comezou hai $1. $2", + "timedmedia-percent-done": "Preto do $1% feito", + "timedmedia-in-job-queue": "Engadido á cola de traballos hai $1", + "timedmedia-unknown-target-size": "Tamaño de destino descoñecido; $1 codificados", + "timedmedia-days": "{{PLURAL:$1|1 día|$1 días}}", + "timedmedia-hours": "{{PLURAL:$1|1 hora|$1 horas}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuto|$1 minutos}}", + "timedmedia-seconds": "{{PLURAL:$1|1 segundo|$1 segundos}}", + "timedmedia-reset": "Restablecer a transcodificación", + "timedmedia-reset-confirm": "Ao restablecer a transcodificación eliminarase calquera ficheiro existente e volverá engadir a transcodificación á cola de traballos. A nova transcodificación levará bastante tempo.

\nEstá seguro de querer continuar?", + "timedmedia-reset-error": "Erro no restablecemento do traballo de transcodificación.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Fonte $1", + "timedmedia-source-file-desc": "Ficheiro $1 orixinal, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Ficheiro $1 orixinal ($2)", + "timedmedia-derivative-160p.ogv": "Ogg 160P", + "timedmedia-derivative-desc-160p.ogv": "Vídeo Ogg de baixo ancho de banda (160P)", + "timedmedia-derivative-desc-240p.ogv": "Vídeo Ogg para a web (240P)", + "timedmedia-derivative-desc-360p.ogv": "Vídeo Ogg para a web (360P)", + "timedmedia-derivative-desc-480p.ogv": "Vídeo Ogg para a web (480P)", + "timedmedia-derivative-desc-720p.ogv": "Vídeo Ogg de alta calidade que se pode descargar (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Vídeo Ogg descargable en Full HD (1080P)", + "timedmedia-derivative-desc-160p.webm": "WebM para a web (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM para a web (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM para a web (480P)", + "timedmedia-derivative-desc-720p.webm": "WebM de alta calidade que se pode descargar (720P)", + "timedmedia-derivative-desc-1080p.webm": "WebM descargable en Full HD (1080P)", + "timedmedia-derivative-desc-2160p.webm": "WebM descargable en 4K (2160P)", + "timedmedia-derivative-desc-360p.vp9.webm": "WebM VP9 (360P) visualizable na rede", + "timedmedia-derivative-desc-480p.vp9.webm": "WebM VP9 (480P) visualizable na rede", + "timedmedia-derivative-desc-720p.vp9.webm": "WebM VP9 de alta definición (720P) visualizable na rede", + "timedmedia-derivative-desc-1080p.vp9.webm": "WebM VP9 de alta definición total (1080P) visualizable na rede", + "timedmedia-derivative-desc-2160p.vp9.webm": "WebM VP9 4K completa (2160P) visualizable na rede", + "timedmedia-derivative-desc-320p.mp4": "MP4 compatible con dispositivos móbiles (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 para a web (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 de calidade HD (720P)", + "timedmedia-derivative-desc-1080p.mp4": "MP4 de calidade Full HD (1080P)", + "timedmedia-derivative-desc-2160p.mp4": "MP4 de calidade de alta definición 4K", + "timedmedia-subtitle-new": "Crear unha nova tradución ou editar a actual", + "timedmedia-subtitle-new-desc": "Seleccione a lingua e prema no botón \"'''{{int:Timedmedia-subtitle-new-go}}'''\"", + "timedmedia-subtitle-new-go": "Continuar", + "timedmedia-subtitle-language": "Subtítulos en $1 ($2)", + "timedmedia-subtitle-no-video": "Non hai ningún vídeo asociado coa páxina de subtítulos actual", + "timedmedia-subtitle-no-subtitles": "Actualmente non hai subtítulos en $1 para este vídeo; pode [{{fullurl:{{FULLPAGENAME}}|action=edit}} editar esta páxina] para engadilos", + "timedmedia-subtitle-remote": "Os subtítulos deste ficheiro están aloxados en $1", + "timedmedia-subtitle-remote-link": "Pode [$1 ollar a páxina de descrición] deste ficheiro en $2", + "timedmediahandler": "Extensión TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 vídeo|$1 vídeos}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 vídeo Ogg|$1 vídeos Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 vídeo WebM|$1 vídeos WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transcodificación|$1 transcodificacións}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 transcodificación activa|$1 transcodificacións activas}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transcodificación|$1 transcodificacións}} na cola", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 transcodificación fallida|$1 transcodificacións fallidas}}", + "timedmedia-file": "Ficheiro", + "timedmedia-audios": "{{PLURAL:$1|$1 ficheiro|$1 ficheiros}} de son", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 ficheiro|$1 ficheiros}} de son Ogg", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 ficheiro|$1 ficheiros}} de son FLAC", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 ficheiro|$1 ficheiros}} de son WAV", + "right-transcode-reset": "Restablecer os vídeos transcodificados erroneamente para incluílos de novo na cola de traballo.", + "right-transcode-status": "Ollar a [[Special:TimedMediaHandler|información sobre a actividade de transcodificación actual]]", + "action-transcode-status": "ollar o estado da transcodificación actual", + "orphanedtimedtext": "Páxinas orfas de TimedText", + "orphanedtimedtext-summary": "Lista de páxinas [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] que non teñen un ficheiro correspondente.", + "orphanedtimedtext-unsupported": "Esta páxina especial só está soportada en bases de datos MySQL.", + "orphanedtimedtext-notimedtext": "TimedText non se activou nesta wiki.", + "apihelp-query+transcodestatus-description": "Obter o estado de transcode para unha páxina de ficheiro dado.", + "apihelp-query+transcodestatus-example-1": "Obter o estado transcode para [[:File:Clip.webm]]", + "apihelp-query+videoinfo-description": "Estende a información da imaxe para incluír a información da fonte de vídeo (derivado)", + "apihelp-query+videoinfo-param-prop": "Que información de vídeo obter:\n;timestamp:Engade o selo de tempo da versión subida.\n;user:Engade o usuario que subiu a versión de vídeo.\n;userid:Engade o identificador de usuario que subiu a versión de vídeo.\n;comment:Comentario da versión.\n;parsedcomment:Comentario analizado na versión.\n;canonicaltitle:Engade o título canónico do ficheiro de vídeo.\n;url:Devolve a URL ó vídeo e a páxina de descrición.\n;size:Engade o tamaño do vídeo en bytes, a súa altura e anchura. O contador de páxina e duración tamén se engaden se é aplicable.\n;dimensions:Alias para \"size\".\n;sha1:Engade a función hash SHA-1 do vídeo.\n;mime:Engade o tipo MIME do vídeo.\n;thumbmime:Engade o tipo MIME da miniatura do vídeo (requie url e parámetro $1urlwidth).\n;mediatype:Engade o tipo de medio multimedia do vídeo.\n;metadata:Lista os metadatos Exif da versión do vídeo.\n;commonmetadata:Lista os metadatos formateados combinados desde varias fontes. Os resultados están formateados en HTML.\n;archivename:Engade o nome de ficheiro da versión para versións anteriores á última.\n;bitdepth:Engade a profundidade de bit da versión.\n;uploadwarning:Usado pola páxina [[Special:Upload]] para obter información sobre un ficheiro xa existente. Non previsto para usar fóra do núcleo MediaWiki.\n;derivatives:Engade unha matriz dos diferentes formatos e calidades dispoñibles dun audio ou vídeo.", + "apihelp-query+videoinfo-example-1": "Buscar información sobre [[:File:Folgers.ogv]]", + "apihelp-transcodereset-description": "Usuarios co permiso 'transcode-reset' poden reiniciar e reexecutar un traballo transcode.", + "apihelp-transcodereset-param-title": "Título do ficheiro multimedia.", + "apihelp-transcodereset-param-transcodekey": "A chave transcode que quere resetear. Collida de [[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "Resetear todos os transcodes para [[:File:Clip.webm]]", + "apihelp-transcodereset-example-2": "Resetear a clave transcode '360_560kbs.webm' de [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/gom-deva.json b/extensions/TimedMediaHandler/i18n/gom-deva.json new file mode 100644 index 00000000..f72dab88 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/gom-deva.json @@ -0,0 +1,12 @@ +{ + "@metadata": { + "authors": [ + "Darshan kandolkar", + "Supriya kankumbikar", + "Vaishali Parab" + ] + }, + "timedmedia-no-player-js": "माफ करचें, तुमच्या ब्रावजरांत एक तर जावास्क्रिप्ट दुबळें आसा वा समर्थीत प्लेयर ना।
\nतुमी क्लीप डाउनलोड करुं शकतात वा क्लीप तुमच्या ब्रावजरान चलोवपाक एक प्लेयर डाउनलोड करुं शकतात.", + "timedmedia-source-file": "$1 उगम", + "timedmedia-source-audio-file-desc": "मूळ $1 फायल ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/gom-latn.json b/extensions/TimedMediaHandler/i18n/gom-latn.json new file mode 100644 index 00000000..1bffb01a --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/gom-latn.json @@ -0,0 +1,11 @@ +{ + "@metadata": { + "authors": [ + "The Discoverer" + ] + }, + "timedmedia-no-player-js": "Maf korchem, tujea browserant ek tor JavaScript dublem asa vo somorthit player nam.
\nTum clipik download korunk shoktai vo clip tujea browseran cholovpak ek playerak download korunk shoktai.", + "timedmedia-source-file": "$1 strot", + "timedmedia-source-file-desc": "Mul $1 fail, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Mul $1 fail ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/grc.json b/extensions/TimedMediaHandler/i18n/grc.json new file mode 100644 index 00000000..ce201686 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/grc.json @@ -0,0 +1,13 @@ +{ + "@metadata": { + "authors": [ + "Crazymadlover", + "Flyax", + "Omnipaedista" + ] + }, + "timedmedia-ogg-long-error": "Ἄκυρα ἀρχεῖα ogg: $1", + "timedmedia-more": "πλέον...", + "timedmedia-dismiss": "Κλῄειν", + "timedmedia-source-file": "$1 Πηγή" +} diff --git a/extensions/TimedMediaHandler/i18n/gsw.json b/extensions/TimedMediaHandler/i18n/gsw.json new file mode 100644 index 00000000..fb38ce89 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/gsw.json @@ -0,0 +1,62 @@ +{ + "@metadata": { + "authors": [ + "Als-Chlämens", + "Als-Holder", + "Melancholie" + ] + }, + "timedmedia-desc": "Stellt e Styyrigsprogramm fir zytgstyyrti Medie (Video, Audio, timedText) zur Verfiegig, wo d Format WebM, Ogg Theora, Ogg Vorbis un SubRip unterstützt", + "timedmedia-ogg-short-audio": "Ogg-$1-Audiodatei, $2", + "timedmedia-ogg-short-video": "Ogg-$1-Videodatei, $2", + "timedmedia-ogg-short-general": "Ogg-$1-Mediadatei, $2", + "timedmedia-ogg-long-audio": "Ogg-$1-Audiodatei, Längi: $2, $3", + "timedmedia-ogg-long-video": "Ogg-$1-Videodatei, Längi: $2, $4×$5 Pixel, $3", + "timedmedia-ogg-long-multiplexed": "Ogg-Audio-/Video-Datei, $1, Längi: $2, $4×$5 Pixel, $3", + "timedmedia-ogg-long-general": "Ogg-Mediadatei, Längi: $2, $3", + "timedmedia-ogg-long-error": "Uugiltigi Ogg-Datei: $1", + "timedmedia-webm-short-video": "WebM-$1-Videodatei, $2", + "timedmedia-webm-long-video": "WebM-Audio-/Video-Datei, $1, Längei: $2, $4×$5 Pixel, $3 insgsamt", + "timedmedia-no-player-js": "Excusez, aber Dyy Browser het entwäder JavaScript deaktiviert oder kei unterstitzti Abspilsoftware.
\nDu chasch dr Clip abelade oder e Abspielsoftware abelade go dr Clip im Browser abspile.", + "timedmedia-more": "Meh …", + "timedmedia-dismiss": "Zuemache", + "timedmedia-download": "Datei spychere", + "timedmedia-play-media": "Mediedatei abspiile", + "timedmedia-desc-link": "Iber die Datei", + "timedmedia-oggThumb-version": "OggHandler brucht oggThumb in dr Version $1 oder hecher.", + "timedmedia-oggThumb-failed": "oggThumb het kei Miniaturbild chenne aalege.", + "timedmedia-status-header": "Umschlüsseligsstatus", + "timedmedia-update-status": "Umschlüsseligsstatus aktualisiere", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Status nit bekannt", + "timedmedia-transcodeinfo": "Abgleiteti Beschryybig umschlüssle", + "timedmedia-actions": "Aktione", + "timedmedia-direct-link": "Ableitig abelade", + "timedmedia-not-ready": "Nit parat", + "timedmedia-completed-on": "Umschlüsselig vo $1 abgschlosse", + "timedmedia-error-on": "Fääler bim Umschlüssle in $1.", + "timedmedia-started-transcode": "Umschlüsselig het vor $1 aagfange. $2", + "timedmedia-percent-done": "Öbe $1 % fertig", + "timedmedia-in-job-queue": "Vor $1 zur Ufftragswarteschlange dezuegfiegt worde", + "timedmedia-unknown-target-size": "Unbekannti Ziilgrößi, $1 codiert", + "timedmedia-days": "{{PLURAL:$1|1 Tag|$1 Täg}}", + "timedmedia-hours": "{{PLURAL:$1|1 Stund|$1 Stunde}}", + "timedmedia-minutes": "{{PLURAL:$1|1 Minut|$1 Minute}}", + "timedmedia-seconds": "{{PLURAL:$1|1 Sekund|$1 Sekunde}}", + "timedmedia-reset": "Umschlüsselig zruggsetze", + "timedmedia-reset-confirm": "S Zruggsetze vo dere Umschlüsselig wird (wänn's vorhande isch), die aktuelli Datei ussenee un d Umschlüsselig no emool uff d Ufftragswarteschlange dezuefiege. E nöii Umschlüsselig wird e Wyyl goo.

Söll des würkli gmacht werde?", + "timedmedia-reset-error": "Fääler bim Zruggsetze vo de Umschlüsselig.", + "timedmedia-source-file": "Quell ($1)", + "timedmedia-source-file-desc": "Original $1, $2 x $3 ($4)", + "timedmedia-source-audio-file-desc": "Urspringligi $1-Datei ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg-Videodatei mit niidriger Dateübertragigsrate (160p)", + "timedmedia-derivative-desc-360p.ogv": "Webstreamingfähigi Ogg-Videodatei (360p)", + "timedmedia-derivative-desc-480p.ogv": "Webstreamingfähigi Ogg-Videodatei (480p)", + "timedmedia-derivative-desc-720p.ogv": "Qualitativ hochwertigi Ogg-Videodatei (720p)", + "timedmedia-derivative-desc-360p.webm": "Webstreamingfähigi WebM-Videodatei (360p)", + "timedmedia-derivative-desc-480p.webm": "Webstreamingfähigi WebM-Videodatei (480p)", + "timedmedia-derivative-desc-720p.webm": "Qualitativ hochwertigi WebM-Videodatei (720p)", + "timedmedia-subtitle-language": "$1 ($2) Untertitel", + "timedmedia-subtitle-no-video": "Dere Untertitel-Syte isch kei Video zuegordnet.", + "timedmedia-subtitle-no-subtitles": "Es git uff $1 zurzit kei Untertitel für des Video. Bim dezuefiege vo Untertitel cha [{{fullurl:{{FULLPAGENAME}}|action=edit}} die Syte] bearbeitet werde." +} diff --git a/extensions/TimedMediaHandler/i18n/gu.json b/extensions/TimedMediaHandler/i18n/gu.json new file mode 100644 index 00000000..7c73dd97 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/gu.json @@ -0,0 +1,23 @@ +{ + "@metadata": { + "authors": [ + "KartikMistry", + "Arbhatt", + "Dsvyas", + "NehalDaveND", + "Ashok modhvadia" + ] + }, + "timedmedia-no-player-js": "માફ કરશો, તમારા બ્રાઉઝરમાં જાવાસ્ક્રિપ્ટ સક્રિય નથી અથવા તો સપોર્ટેડ પ્લેયર નથી.
\nતમે ક્લિપ ડાઉનલોડ કરી શકો છો અથવા આ ક્લિપને તમારા બ્રાઉઝરમાં ચલાવવા માટે એક પ્લેયર ડાઉનલોડ કરી શકો છો.", + "timedmedia-more": "વધુ...", + "timedmedia-dismiss": "બંધ કરો", + "timedmedia-download": "ફાઈલ ડાઉનલોડ કરો", + "timedmedia-status": "સ્થિતિ", + "timedmedia-status-unknown": "અજ્ઞાત સ્થિતિ", + "timedmedia-actions": "ક્રિયાઓ", + "timedmedia-not-ready": "તૈયાર નથી", + "timedmedia-source-file": "$1 સ્રોત", + "timedmedia-source-audio-file-desc": "મુળ આવૃત્તિ $1 ફાઇલ ($2)", + "timedmedia-subtitle-new-go": "જાઓ", + "timedmedia-file": "ફાઇલ" +} diff --git a/extensions/TimedMediaHandler/i18n/gv.json b/extensions/TimedMediaHandler/i18n/gv.json new file mode 100644 index 00000000..66353585 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/gv.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "MacTire02" + ] + }, + "timedmedia-desc-link": "Mychione y choadan shoh" +} diff --git a/extensions/TimedMediaHandler/i18n/he.json b/extensions/TimedMediaHandler/i18n/he.json new file mode 100644 index 00000000..29cddf15 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/he.json @@ -0,0 +1,125 @@ +{ + "@metadata": { + "authors": [ + "Amire80", + "Guycn2", + "Rotem Liss", + "Rotemliss", + "YaronSh" + ] + }, + "timedmedia-desc": "מטפל במדיה מתוזמנת – וידאו, שמע, טקסט מתוזמן – עם תמיכה בתסדירי WebM‏, Ogg Theora‏, Vorbis ו־srt", + "timedmedia-ogg-short-audio": "קובץ שמע $1 של Ogg, $2", + "timedmedia-ogg-short-video": "קובץ וידאו $1 של Ogg, $2", + "timedmedia-ogg-short-general": "קובץ מדיה $1 של Ogg, $2", + "timedmedia-ogg-long-audio": "קובץ שמע $1 של Ogg, באורך $2, $3", + "timedmedia-ogg-long-video": "קובץ וידאו $1 של Ogg, באורך $2, $4×$5 פיקסלים, $3", + "timedmedia-ogg-long-multiplexed": "קובץ Ogg מרובב של שמע ווידאו, $1, באורך $2, $4×$5 פיקסלים, $3 בסך הכול", + "timedmedia-ogg-long-general": "קובץ מדיה של Ogg, באורך $2, $3", + "timedmedia-ogg-long-error": "קובץ ogg בלתי תקין: $1", + "timedmedia-ogg-long-no-streams": "קובץ מדיה Ogg. אזהרה: אף אחד מהקודקים בקובץ הזה אינו מוּכּר.", + "timedmedia-webm-short-video": "קובץ וידאו WebM $1‏, $2", + "timedmedia-webm-long-video": "קובץ שמע ווידאו WebM‏, $1, באורך $2‏, $4 × $5 פיקסלים, $3 סך הכול", + "timedmedia-flac-short-audio": "קובץ שמע FLAC‏, $1", + "timedmedia-flac-long-audio": "קובץ שמע FLAC‏, אורך $1, סך הכול $2", + "timedmedia-wav-short-audio": "קובץ שמע WAV‏, $1", + "timedmedia-wav-long-audio": "קובץ שמע WAV, אורך $1, סך הכול $2", + "timedmedia-wav-pcm-required": "אפשר להעלות רק קובצי WAV בתסדיר PCM‏ (Pulse Code Modulation).", + "timedmedia-mp4-short-video": "קובץ וידאו MP4 $1‏, $2", + "timedmedia-mp4-long-video": "קובץ שמע ווידאו של MP4‏, $1, באורך $2, $4 × $5 פיסקלים, $3 בסך הכול", + "timedmedia-no-player-js": "מצטערים, בדפדפן שלך לא מופעלת תמיכה ב־JavaScript או שאין בו נגן נתמך.
\nבאפשרותך להוריד למחשב את הסרטון או להוריד נגן שינגן את הסרטון בדפדפן שלך.", + "timedmedia-more": "עוד…", + "timedmedia-dismiss": "סגירה", + "timedmedia-download": "הורדת הקובץ", + "timedmedia-play-media": "לנגן את המדיה", + "timedmedia-desc-link": "אודות הקובץ", + "timedmedia-oggThumb-version": "OggHandler דורש oggThumb מגרסה $1 או גרסה חדשה יותר.", + "timedmedia-oggThumb-failed": "oggThumb לא הצליח ליצור תמונה מוקטנת.", + "timedmedia-status-header": "מצב המרה משנית", + "timedmedia-update-status": "עדכון מצב המרה משנית", + "timedmedia-status": "מצב", + "timedmedia-status-unknown": "מצב לא ידוע", + "timedmedia-transcodebitrate": "קצב ביטים", + "timedmedia-transcodeduration": "זמן קידוד", + "timedmedia-transcodeinfo": "תסדיר", + "timedmedia-actions": "פעולות", + "timedmedia-direct-link": "הורדה", + "timedmedia-not-ready": "לא מוכן", + "timedmedia-completed-on": "הושלם ב־$1", + "timedmedia-error-on": "שגיאה ב־$1", + "timedmedia-started-transcode": "התחילה לפני $1‏. $2", + "timedmedia-percent-done": "בערך $1% נעשה", + "timedmedia-in-job-queue": "נוסף לתור משימות לפני $1", + "timedmedia-unknown-target-size": "גודל יעד אינו ידוע, קודדו $1", + "timedmedia-days": "{{PLURAL:$1|יום אחד|יומיים|$1 ימים}}", + "timedmedia-hours": "{{PLURAL:$1|שעה אחת|שעתיים|$1 שעות}}", + "timedmedia-minutes": "{{PLURAL:$1|דקה אחת|$1 דקות}}", + "timedmedia-seconds": "{{PLURAL:$1|שנייה אחת|$1 שניות}}", + "timedmedia-reset": "אתחול המרה משנית", + "timedmedia-reset-confirm": "אתחול ההמרה הזאת ימחק כל קובץ קיים (אם יש כזה) ויוסיף שוב את ההמרה המשנית לתור המשימות. ייקח זמן כדי לבצע את ההמרה המשנית שוב.

להמשיך?", + "timedmedia-reset-error": "שגיאה באתחול משימת המרה משנית", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "ממקור $1", + "timedmedia-source-file-desc": "קובץ $1 מקורי, $2 × $3‏ ($4)", + "timedmedia-source-audio-file-desc": "קובץ $1 מקורי ($2)", + "timedmedia-derivative-desc-160p.ogv": "וידאו Ogg (160P)‎ לרוחב פס נמוך", + "timedmedia-derivative-desc-240p.ogv": "וידאו Ogg (240P)‎ לנגינה שוטפת מהרשת", + "timedmedia-derivative-desc-360p.ogv": "וידאו Ogg (360P)‎ לנגינה שוטפת מהרשת", + "timedmedia-derivative-desc-480p.ogv": "וידאו Ogg (480P)‎ לנגינה שוטפת מהרשת", + "timedmedia-derivative-desc-720p.ogv": "וידאו Ogg (720P)‎ באיכות גבוהה שניתן להורדה", + "timedmedia-derivative-desc-1080p.ogv": "וידאו Ogg שאפשר להוריד באיכות HD מלאה (1080P)", + "timedmedia-derivative-desc-160p.webm": "WebM להזרמה ברשת (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM ניתן לשידור שטף ברשת (360P)", + "timedmedia-derivative-desc-480p.webm": "וידאו WebM (480P)‎ לנגינה שוטפת מהרשת", + "timedmedia-derivative-desc-720p.webm": "וידאו WebM (720P)‎ באיכות גבוהה שניתן להורדה", + "timedmedia-derivative-desc-1080p.webm": "קובץ WebM שאפשר להוריד באיכות HD מלאה (1080P)", + "timedmedia-derivative-desc-2160p.webm": "קובץ WebM שאפשר להוריד באיכות 4K מלאה (2160P)", + "timedmedia-derivative-desc-360p.vp9.webm": "WebM VP9 ניתן להזרמה ברשת (360P)", + "timedmedia-derivative-desc-480p.vp9.webm": "WebM VP9 ניתן להזרמה ברשת (480P)", + "timedmedia-derivative-desc-720p.vp9.webm": "WebM VP9 באיכות HD ניתן להזרמה ברשת (720P)", + "timedmedia-derivative-desc-1080p.vp9.webm": "WebM VP9 באיכות HD מלאה ניתן להזרמה ברשת (1080P)", + "timedmedia-derivative-desc-2160p.vp9.webm": "WebM VP9 באיכות 4K מלאה ניתן להזרמה ברשת (2160P)", + "timedmedia-derivative-desc-320p.mp4": "MP4 ידידותי למכשיר (320P)", + "timedmedia-derivative-desc-480p.mp4": "וידאו WebM (480P)‎ לנגינה שוטפת מהרשת", + "timedmedia-derivative-desc-720p.mp4": "MP4 באיכות גבוהה (720P)", + "timedmedia-derivative-desc-1080p.mp4": "קובץ MP4 שאפשר להוריד באיכות HD מלאה (1080P)", + "timedmedia-derivative-desc-2160p.mp4": "MP4 באיכות 4K מלאה (2160P)", + "timedmedia-subtitle-new": "יצירת תרגום חדש או עריכת תרגום קיים", + "timedmedia-subtitle-new-desc": "נא לבחור שפה וללחוץ על כפתור '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "מעבר", + "timedmedia-subtitle-language": "כתוביות ב{{GRAMMAR:תחילית|$1}} ($2)", + "timedmedia-subtitle-no-video": "אין וידאו שמשויך לדף הכתוביות הנוכחי", + "timedmedia-subtitle-no-subtitles": "אין כעת כתוביות עבור השפה הבאה: $1. אפשר [{{fullurl:{{FULLPAGENAME}}|action=edit}} לערוך את הדף הזה] ולהוסיף אותן", + "timedmedia-subtitle-remote": "טקסט מתוזמן עבור קובץ זה מאוחסן ב{{GRAMMAR:תחילית|$1}}", + "timedmedia-subtitle-remote-link": "אפשר [$1 להציג את דף התיאור] של הקובץ הזה בדף $2", + "timedmediahandler": "מדיה מתוזמנת", + "timedmedia-videos": "{{PLURAL:$1|סרט אחד|$1 סרטים}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|סרט Ogg אחד|$1 סרטי Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|סרט WebM אחד|$1 סרטי WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|המרה משנית אחת|$1 המרות משניות}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|המרה משנית אחת רצה|$1 המרות משניות רצות}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|המרה משנית אחת בתור|$1 המרות משניות בתור}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|המרות משנית אחת נכשלה|$1 המרות משניות נכשלו}}", + "timedmedia-file": "קובץ", + "timedmedia-audios": "{{PLURAL:$1|קובץ שמע אחד|$1 קובצי שמע}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|קובץ שמע Ogg אחד|$1 קובצי שמע Ogg}}", + "timedmedia-flac-audios": "{{PLURAL:$1|קובץ שמע FLAC אחד|$1 קובצי שמע FLAC}}", + "timedmedia-wav-audios": "{{PLURAL:$1|קובץ שמע WAV אחד|$1 קובצי שמע WAV}}", + "right-transcode-reset": "אתחול סרטים שקידוד המשנה שלהם הושלם או נכשל כך שהם מוכנסים מחדש לתור המשימות.", + "right-transcode-status": "הצגת [[Special:TimedMediaHandler|מידע על פעילות המרת־המשנה הנוכחית]]", + "action-transcode-status": "להציג את מצב המרת־המשנה", + "orphanedtimedtext": "דפי טקסט מתוזמן יתומים", + "orphanedtimedtext-summary": "רשימה של כל דפי [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] שלא משויך להם קובץ.", + "orphanedtimedtext-unsupported": "הדף המיוחד הזה נתמך רק במסדי נתונים MySQL.", + "orphanedtimedtext-notimedtext": "ההרחבה TimedText אינה מופעלת בוויקי הזה.", + "apihelp-query+transcodestatus-description": "קבלת מצב ההמרה המשנית עבור דף הקובץ הנתון.", + "apihelp-query+transcodestatus-example-1": "קבלת מצב ההמרה המשנית עבור [[:File:Clip.webm]]", + "apihelp-query+videoinfo-description": "הרחבת imageinfo להכללת מידע על מקור הווידאו (נגזרות)", + "apihelp-query+videoinfo-param-prop": "איזה מידע לקבל על הווידאו:\n;timestamp:הוספת חותם זמן לגרסה שהולתה.\n;user:הוספת המשתמש שהעלה את גרסת הווידאו.\n;userid:הוספת מזהה המשתמש שהעלה את גרסת הווידאו.\n;comment:הערה על הגרסה.\n;parsedcomment:פענוח ההערה על הגרסה.\n;canonicaltitle:הוספת הכותרת הקנונית של שם קובץ הווידאו.\n;url:הוספת URL לווידאו ולדף התיאור.\n;size:הוספת הגודל של הווידאו בבתים, הגובה שלו והרוחב שלו. ספירת דפים ומשך מוספים כשזה מתאים.\n;dimensions:אותו דבר כמו size.\n;sha1:הוספת גיבוב SHA-1 של הווידאו.\n;mime:הוספת סוג MIME של הווידאו.\n;thumbmime:הוספת סוג MIME של התמונה הממוזערת של הווידאו (נדרש url והפרמטר $1urlwidth).\n;mediatype:הוספת סוג המדיה של הווידאו.\n;metadata:רשימת מטא־נתוני EXIF לגרסה של הווידאו.\n;commonmetadata:רשימת מטא־נתונים כללים של תסדיר הקובץ עבור גרסת הווידאו.\n;extmetadata:רשימת מטא־נתונים מעוצבים משולבים ממספר מקורות התוצאות מעוצבות עם HTML.\n;archivename:הוספת שם הקובץ של גרסת הארכיון עבור הגרסאות שאינן הגרסה האחרונה.\n;bitdepth:עומק הביטים של הגרסה.\n;uploadwarning:משמש את הדף [[Special:Upload]] כדי לקבל מידע על קובץ קיים. לא מיועד לשימוש מחוץ לליבת MediaWiki.\n;derivatives:הוספת מערך של גרסאות זמינות שונות מבחינת תסדיר ואיכות של קובץ שמע או וידאו.", + "apihelp-query+videoinfo-example-1": "אחזור מידע על [[:File:Folgers.ogv]]", + "apihelp-transcodereset-description": "משתמשים עם ההרשאה 'transcode-reset' יכולים לאפס ולהריץ מלחדש משימת המרת־משנה.", + "apihelp-transcodereset-param-title": "כותרת קובץ המדיה.", + "apihelp-transcodereset-param-transcodekey": "מפתח המרת־המשנה שברצונך לאפס. יש לאחזר אותו מ־[[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "איפוס כל המרות־המשנה עבור [[:File:Clip.webm]]", + "apihelp-transcodereset-example-2": "איפוס מפתח המרת המשנה '360_560kbs.webm' עבור [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/hi.json b/extensions/TimedMediaHandler/i18n/hi.json new file mode 100644 index 00000000..08ef0a1d --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/hi.json @@ -0,0 +1,39 @@ +{ + "@metadata": { + "authors": [ + "Kaustubh", + "Shyam", + "Siddhartha Ghai", + "संजीव कुमार", + "NehalDaveND" + ] + }, + "timedmedia-desc": "ऑग थियोरा और वॉर्बिस फ़ाईल्सके लिये चालक, जावास्क्रीप्ट प्लेयर के साथ", + "timedmedia-ogg-short-audio": "ऑग $1 ध्वनी फ़ाईल, $2", + "timedmedia-ogg-short-video": "ऑग $1 चलतचित्र फ़ाईल, $2", + "timedmedia-ogg-short-general": "ऑग $1 मीडिया फ़ाईल, $2", + "timedmedia-ogg-long-audio": "ऑग $1 ध्वनी फ़ाईल, लंबाई $2, $3", + "timedmedia-ogg-long-video": "ऑग $1 चलतचित्र फ़ाईल, लंबाई $2, $4×$5 पीक्सेल्स, $3", + "timedmedia-ogg-long-multiplexed": "ऑग ध्वनी/चित्र फ़ाईल, $1, लंबाई $2, $4×$5 पिक्सेल्स, $3 कुल", + "timedmedia-ogg-long-general": "ऑग मीडिया फ़ाईल, लंबाई $2, $3", + "timedmedia-ogg-long-error": "गलत ऑग फ़ाईल: $1", + "timedmedia-no-player-js": "क्षमा करें, आपके ब्राउज़र में या तो जावास्क्रिप्ट अक्षम है या समर्थित प्लेयर नहीं है।
\nआप क्लिप को डाउनलोड कर सकते हैं अथवा क्लिप को अपने ब्राउज़र में चलाने के लिए एक प्लेयर डाउनलोड कर सकते हैं।", + "timedmedia-more": "और...", + "timedmedia-dismiss": "बंद करें", + "timedmedia-download": "फ़ाईल डाउनलोड करें", + "timedmedia-desc-link": "इस फ़ाईलके बारे में", + "timedmedia-status": "स्थिति", + "timedmedia-status-unknown": "अज्ञात स्थिति", + "timedmedia-actions": "क्रियाएँ", + "timedmedia-source-file": "$1 स्रोत", + "timedmedia-source-audio-file-desc": "मूल $1 फ़ाइल ($2)", + "timedmedia-derivative-desc-1080p.ogv": "पूर्ण उच्च परिभाषा डाउनलोड करने योग्य ओजीजी वीडियो (१०८०पी)", + "timedmedia-derivative-desc-1080p.webm": "पूर्ण उच्च परिभाषा के साथ डाउनलोड करने योग्य वेब-एम (१०८०पी)", + "timedmedia-derivative-desc-1080p.mp4": "पूर्ण उच्च परिभाषा गुणवता एमपी४ (१०८०पी)", + "timedmedia-subtitle-new-go": "जाएँ", + "timedmedia-file": "फ़ाइल", + "timedmedia-audios": "{{PLURAL:$1|$1 ऑडियो फ़ाइल|$1 ऑडियो फ़ाइलें}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg ऑडियो फ़ाइल|$1 Ogg ऑडियो फ़ाइलें}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC ऑडियो फ़ाइल|$1 FLAC ऑडियो फ़ाइलें}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV ऑडियो फ़ाइल|$1 WAV ऑडियो फ़ाइलें}}" +} diff --git a/extensions/TimedMediaHandler/i18n/hr.json b/extensions/TimedMediaHandler/i18n/hr.json new file mode 100644 index 00000000..b8d63e0f --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/hr.json @@ -0,0 +1,31 @@ +{ + "@metadata": { + "authors": [ + "CERminator", + "Dalibor Bosits", + "Ex13", + "SpeedyGonsales", + "Roberta F.", + "Fraxinus", + "Teoo3" + ] + }, + "timedmedia-desc": "Poslužitelj za Ogg Theora i Vorbis datoteke, s JavaScript preglednikom", + "timedmedia-ogg-short-audio": "Ogg $1 zvučna datoteka, $2", + "timedmedia-ogg-short-video": "Ogg $1 video datoteka, $2", + "timedmedia-ogg-short-general": "Ogg $1 medijska datoteka, $2", + "timedmedia-ogg-long-audio": "Ogg $1 zvučna datoteka, duljine $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 video datoteka, duljine $2, $4x$5 piksela, $3", + "timedmedia-ogg-long-multiplexed": "Ogg multipleksirana zvučna/video datoteka, $1, duljine $2, $4×$5 piksela, $3 ukupno", + "timedmedia-ogg-long-general": "Ogg medijska datoteka, duljine $2, $3", + "timedmedia-ogg-long-error": "nevaljana ogg datoteka: $1", + "timedmedia-no-player-js": "Nažalost, u Vašem pregledniku isključen je JavaScript, ili nije dostupan na ovom projektu.
\nMožete $1\">preuzeti videozapis ili učitati player za reproduciranje videozapisa u pregledniku.", + "timedmedia-more": "Više...", + "timedmedia-dismiss": "Zatvori", + "timedmedia-download": "Snimi datoteku", + "timedmedia-desc-link": "O ovoj datoteci", + "timedmedia-source-file": "Izvor $1", + "timedmedia-source-audio-file-desc": "Originalna $1 datoteka ($2)", + "timedmedia-derivative-desc-2160p.vp9.webm": "Full 4K streamable WebM VP9 (2160P)", + "timedmedia-derivative-desc-2160p.mp4": "MP4 pune 4K kvalitete (2160P)" +} diff --git a/extensions/TimedMediaHandler/i18n/hrx.json b/extensions/TimedMediaHandler/i18n/hrx.json new file mode 100644 index 00000000..80513f50 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/hrx.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Midnight Gambler" + ] + }, + "timedmedia-source-file": "Quell ($1)" +} diff --git a/extensions/TimedMediaHandler/i18n/hsb.json b/extensions/TimedMediaHandler/i18n/hsb.json new file mode 100644 index 00000000..094def43 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/hsb.json @@ -0,0 +1,83 @@ +{ + "@metadata": { + "authors": [ + "Dundak", + "Michawiki", + "Reedy" + ] + }, + "timedmedia-desc": "Wodźenski program za awdio, widejo a timedText z podpěru za formaty WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Awdiodataja Ogg $1, $2", + "timedmedia-ogg-short-video": "Widejodataja Ogg $1, $2", + "timedmedia-ogg-short-general": "Ogg medijowa dataja $1, $2", + "timedmedia-ogg-long-audio": "Ogg-awdiodataja $1, dołhosć: $2, $3", + "timedmedia-ogg-long-video": "Ogg-widejodataja $1, dołhosć: $2, $4×$5 pikselow, $3", + "timedmedia-ogg-long-multiplexed": "Ogg multipleksna awdio-/widejodataja, $1, dołhosć: $2, $4×$5 pikselow, $3", + "timedmedia-ogg-long-general": "Ogg medijowa dataja, dołhosć: $2, $3", + "timedmedia-ogg-long-error": "Njepłaćiwa Ogg-dataja: $1", + "timedmedia-webm-short-video": "Widejodataja WebM $1, $2", + "timedmedia-webm-long-video": "Awdio-/widejodataja WebM, $1, dołhosć $2, $4 x $5 pikselow, $3 dohromady", + "timedmedia-mp4-short-video": "MP4 $1 widejodataja, $2", + "timedmedia-mp4-long-video": "MP4 awdio-/widejodataja, $1, dołhosć $2, $4 × $5 pikselow, $3 dohromady", + "timedmedia-no-player-js": "Twój wobhladowak je pak JavaScript znjemóžnił pak nima podpěrowany wothrawak.
\nMóžeš klip sćahnyć abo wothrawak sćahnyć, zo by klip w swojim wobhladowaku wothrawał.", + "timedmedia-more": "Wjace ...", + "timedmedia-dismiss": "Začinić", + "timedmedia-download": "Dataju sćahnyć", + "timedmedia-play-media": "Medijowu dataju wothrać", + "timedmedia-desc-link": "Wo tutej dataji", + "timedmedia-oggThumb-version": "OggHandler trjeba wersiju $1 oggThumb abo nowšu.", + "timedmedia-oggThumb-failed": "oggThumb njemóžeše wobrazk wutworić.", + "timedmedia-status-header": "Překodowanski status", + "timedmedia-update-status": "Překodowanski status aktualizować", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Njeznaty status", + "timedmedia-transcodeinfo": "Wotwodźene wopisanje překodować", + "timedmedia-actions": "Akcije", + "timedmedia-direct-link": "Wotwodźenje sćahnyć", + "timedmedia-not-ready": "Nic hotowy", + "timedmedia-completed-on": "Překodowanje $1 wotzamknjene", + "timedmedia-error-on": "Zmylk při překodowanju $1", + "timedmedia-started-transcode": "Překodowanje před $1 startowane. $2", + "timedmedia-percent-done": "Na wšě $1% sčinjene", + "timedmedia-in-job-queue": "Před $1 čakanskemu rynčkej přidaty", + "timedmedia-unknown-target-size": "Njeznata cilowa wulkosć, $1 kodowane", + "timedmedia-days": "{{PLURAL:$1|1 dźeń|$1 dnjej|$1 dny|$1 dnjow}}", + "timedmedia-hours": "{{PLURAL:$1|1 hodźina|$1 hodźinje|$1 hodźiny|$1 hodźin}}", + "timedmedia-minutes": "{{PLURAL:$1|1 mjeńšina|$1 mjeńšinje|$1 mjeńšiny|$1 mjeńšin}}", + "timedmedia-seconds": "{{PLURAL:$1|1 sekunda|$1 sekundźe|$1 sekundy|$1 sekundow}}", + "timedmedia-reset": "Překodowanje wróćo stajić", + "timedmedia-reset-confirm": "Wróćostajenje tutoho překodowanja wotstroni eksistowacu dataju (jeli tajka eksistuje), a budźe překodowanje znowa čakanskemu rynčkej přidawać. Nowe překodowanje budźe někotry čas trać.

\nChceš woprawdźe pokročować?", + "timedmedia-reset-error": "Zmylk při wróćostajenju překodowanja.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Žórło ($1)", + "timedmedia-source-file-desc": "Originalna dataja $1, $2 x $3 ($4)", + "timedmedia-source-audio-file-desc": "Originalna $1-dataja ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg-widejo z niskej šěrokosću pasma (160p)", + "timedmedia-derivative-desc-360p.ogv": "Ogg-widejo (360p), kotrež da so přez Web přenjesć", + "timedmedia-derivative-desc-480p.ogv": "Ogg-widejo (480p), kotrež da so přez Web přenjesć", + "timedmedia-derivative-desc-720p.ogv": "Sćahujomne Ogg-widejo wysokeje kwality (720p)", + "timedmedia-derivative-desc-160p.webm": "WebM-widejo (160p), kotrež da so přez Web přenjesć", + "timedmedia-derivative-desc-360p.webm": "WebM-widejo (360p), kotrež da so přez Web přenjesć", + "timedmedia-derivative-desc-480p.webm": "WebM-widejo (480p), kotrež da so přez Web přenjesć", + "timedmedia-derivative-desc-720p.webm": "Sćahujomna WebM-dataja wysokeje kwality (720p)", + "timedmedia-derivative-desc-320p.mp4": "Gratapřećelny MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4-dataja, kotraž da so přez Web přenjesć", + "timedmedia-derivative-desc-720p.mp4": "MP4 w HD-kwaliće (720P)", + "timedmedia-subtitle-new": "Nowy přełožk wutworić abo eksistowacy wobdźěłać", + "timedmedia-subtitle-new-desc": "Wubjer rěč a klikń na tłóčatko '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "W porjadku", + "timedmedia-subtitle-language": "$1 ($2) podtitule", + "timedmedia-subtitle-no-video": "Widejo zwjazane z aktuelnej stronu podtitulow njeje", + "timedmedia-subtitle-no-subtitles": "Tuchwilu podtitule w $1 zu tute widejo njejsu, móžeš [{{fullurl:{{FULLPAGENAME}}|action=edit}} tutu stronu wobdźěłać], zo by je přidał", + "timedmedia-subtitle-remote": "timedText za tutu dataju je na $1", + "timedmedia-subtitle-remote-link": "Móžeš sej [$1 wopisansku stronu] za tutu dataja na $2 wobhladać", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 widejo|$1 wideji|$1 wideja|$1 widejow}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg-widejo|$1 Ogg-wideji|$1 Ogg-wideja|$1 Ogg-widejow}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM-widejo|$1 WebM-wideji|$1 WebM-wideja|$1 WebM-widejow}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 překodowanje|$1 překodowani|$1 překodowanja|$1 překodowanjow}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 běžne překodowanje|$1 běžnej překodowani|$1 běžne překodowanja|$1 běžnych překodowanjow}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 překodowanje|$1 překodowani|$1 překodowanja|$1 překodowanjow}} w čakanskim rynku", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 zwrěsćene překodowanje|$1 zwrěsćenej překodowani|$1 zwrěsćene překodowanja|$1 zwrěsćenych překodowanjow}}", + "timedmedia-file": "Dataja" +} diff --git a/extensions/TimedMediaHandler/i18n/ht.json b/extensions/TimedMediaHandler/i18n/ht.json new file mode 100644 index 00000000..54b45469 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ht.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Tisave" + ] + }, + "timedmedia-no-player-js": "Eskize, men swa JavaScript navigatè ou deyaktive oswa li pa gen okenn mwayen pou ouvri fichye sa a.
\nOu ka telechaje fichye a oswa chèche yon lojisyèl pou ouvri fichye a nan navigatè ou.", + "timedmedia-source-file": "Referans $1" +} diff --git a/extensions/TimedMediaHandler/i18n/hu.json b/extensions/TimedMediaHandler/i18n/hu.json new file mode 100644 index 00000000..dbe3df87 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/hu.json @@ -0,0 +1,37 @@ +{ + "@metadata": { + "authors": [ + "Dani", + "Glanthor Reviol", + "Tgr", + "Csega", + "Samat", + "Dj" + ] + }, + "timedmedia-desc": "JavaScript nyelven írt lejátszó Ogg Theora és Vorbis fájlokhoz", + "timedmedia-ogg-short-audio": "Ogg $1 hangfájl, $2", + "timedmedia-ogg-short-video": "Ogg $1 videofájl, $2", + "timedmedia-ogg-short-general": "Ogg $1 médiafájl, $2", + "timedmedia-ogg-long-audio": "Ogg $1 hangfájl, hossza: $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 videófájl, hossza $2, $4×$5 képpont, $3", + "timedmedia-ogg-long-multiplexed": "Ogg egyesített audió- és videófájl, $1, hossz: $2, $4×$5 képpont, $3 összesen", + "timedmedia-ogg-long-general": "Ogg médiafájl, hossza: $2, $3", + "timedmedia-ogg-long-error": "Érvénytelen ogg fájl: $1", + "timedmedia-no-player-js": "Sajnáljuk, a böngésződben vagy le van tiltva a JavaScript, vagy nincs egyetlen támogatott lejátszója sem.
\nLetöltheted a klipet, vagy letölthetsz egy lejátszót a böngészőben való megtekintéshez.", + "timedmedia-more": "Tovább...", + "timedmedia-dismiss": "Bezárás", + "timedmedia-download": "Fájl letöltése", + "timedmedia-play-media": "Média lejátszása", + "timedmedia-desc-link": "Fájlinformációk", + "timedmedia-oggThumb-version": "Az OggHandlerhez $1 vagy későbbi verziójú oggThumb szükséges.", + "timedmedia-oggThumb-failed": "Az oggThumb nem tudta elkészíteni a bélyegképet.", + "timedmedia-source-file": "Forrás ($1)", + "timedmedia-source-file-desc": "Eredeti $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Eredeti $1 fájl ($2)", + "timedmedia-derivative-desc-160p.ogv": "Alacsony sávszélességű Ogg videó (160P)", + "timedmedia-derivative-desc-720p.ogv": "Magas minőségű, letölthető Ogg videó (720P)", + "timedmedia-derivative-desc-720p.webm": "Magas minőségű, letölthető WebM (720P)", + "right-transcode-status": "[[Special:TimedMediaHandler|a legutóbbi átkódolási események információinak]] megtekintése", + "orphanedtimedtext": "Árva TimedText lapok" +} diff --git a/extensions/TimedMediaHandler/i18n/hy.json b/extensions/TimedMediaHandler/i18n/hy.json new file mode 100644 index 00000000..41aa6004 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/hy.json @@ -0,0 +1,11 @@ +{ + "@metadata": { + "authors": [ + "Xelgen", + "Vahe Gharakhanyan" + ] + }, + "timedmedia-no-player-js": "Ցավոք, ձեր վեբդիտարկիչում անջատված է JavaScript-ը կամ բացակայում է աջակցվող նվագարկիչ:
\nԴուք կարող եք $1\">ներբեռնել տեսահոլովակը կամ նվագարկիչ:", + "timedmedia-source-file": "$1 աղբյուր", + "timedmedia-source-audio-file-desc": "Սկզբնական $1 նիշք ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/ia.json b/extensions/TimedMediaHandler/i18n/ia.json new file mode 100644 index 00000000..a24d1b20 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ia.json @@ -0,0 +1,92 @@ +{ + "@metadata": { + "authors": [ + "McDutchie" + ] + }, + "timedmedia-desc": "Gestor pro audio, video e texto synchronisate, con supporto del formatos WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "File audio Ogg $1, $2", + "timedmedia-ogg-short-video": "File video Ogg $1, $2", + "timedmedia-ogg-short-general": "File media Ogg $1, $2", + "timedmedia-ogg-long-audio": "File audio Ogg $1, duration $2, $3", + "timedmedia-ogg-long-video": "File video Ogg $1, duration $2, $4×$5 pixel, $3", + "timedmedia-ogg-long-multiplexed": "File multiplexate audio/video Ogg, $1, duration $2, $4×$5 pixel, $3 in total", + "timedmedia-ogg-long-general": "File media Ogg, duration $2, $3", + "timedmedia-ogg-long-error": "File Ogg invalide: $1", + "timedmedia-webm-short-video": "File video WebM $1, $2", + "timedmedia-webm-long-video": "File audio/video WebM, $1, longitude $2, $4 × $5 pixels, $3 in total", + "timedmedia-flac-short-audio": "File audio FLAC, $1", + "timedmedia-flac-long-audio": "File audio FLAC, durata $1, $2 in total", + "timedmedia-wav-short-audio": "File audio WAV, $1", + "timedmedia-wav-long-audio": "File audio WAV, durata $1, $2 in total", + "timedmedia-wav-pcm-required": "Tu pote solmente incargar PCM (Pulse Code Modulation) WAV.", + "timedmedia-mp4-short-video": "File video MP4 $1, $2", + "timedmedia-mp4-long-video": "File audio/video MP4 $1, durata $2, dimensiones $4 × $5 pixels, $3 in total", + "timedmedia-no-player-js": "Pardono, tu systema o ha JavaScript disactivate o non ha un reproductor supportate.
\nTu pote discargar le clip o discargar un reproductor pro reproducer le clip in tu navigator.", + "timedmedia-more": "Plus…", + "timedmedia-dismiss": "Clauder", + "timedmedia-download": "Discargar file", + "timedmedia-play-media": "Reproducer multimedia", + "timedmedia-desc-link": "A proposito de iste file", + "timedmedia-oggThumb-version": "OggHandler require oggThumb version $1 o plus recente.", + "timedmedia-oggThumb-failed": "oggThumb ha fallite de crear le miniatura.", + "timedmedia-status-header": "Stato de transcodification", + "timedmedia-update-status": "Actualisar stato de transcodification", + "timedmedia-status": "stato", + "timedmedia-status-unknown": "stato incognite", + "timedmedia-transcodeinfo": "Description del file transcodificate", + "timedmedia-actions": "actiones", + "timedmedia-direct-link": "Discargar file transcodificate", + "timedmedia-not-ready": "Non preste", + "timedmedia-completed-on": "Transcodification de $1 complete", + "timedmedia-error-on": "Error in transcodification a $1", + "timedmedia-started-transcode": "Le transcodification comenciava $1 retro. $2", + "timedmedia-percent-done": "Circa $1% complete", + "timedmedia-in-job-queue": "Addite al cauda de actiones $1 retro", + "timedmedia-unknown-target-size": "Dimension del destination incognite, $1 codificate", + "timedmedia-days": "{{PLURAL:$1|1 die|$1 dies}}", + "timedmedia-hours": "{{PLURAL:$1|1 hora|$1 horas}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuta|$1 minutas}}", + "timedmedia-seconds": "{{PLURAL:$1|1 secunda|$1 secundas}}", + "timedmedia-reset": "Reinitialisar transcodification", + "timedmedia-reset-confirm": "Reinitialisar iste transcodification removera tote le files existente (si presente), e illo re-addera le transcodification al cauda de actiones. Le transcodification prendera un tempore.

Es tu secur de voler continuar?", + "timedmedia-reset-error": "Error durante le reinitialisation del action de transcodification", + "timedmedia-source-file": "original $1", + "timedmedia-source-file-desc": "File $1 original, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "File $1 original ($2)", + "timedmedia-derivative-desc-160p.ogv": "Video Ogg a basse largor de banda (160P)", + "timedmedia-derivative-desc-360p.ogv": "Video Ogg transmissibile per fluxo web (360P)", + "timedmedia-derivative-desc-480p.ogv": "Video Ogg transmissibile per fluxo web (480P)", + "timedmedia-derivative-desc-720p.ogv": "Video Ogg discargabile de alte qualitate (720P)", + "timedmedia-derivative-desc-160p.webm": "WebM transmissibile per fluxo web (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM transmissibile per fluxo web (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM transmissibile per fluxo web (480P)", + "timedmedia-derivative-desc-720p.webm": "WebM discargabile de alte qualitate (720P)", + "timedmedia-derivative-desc-320p.mp4": "MP4 pro dispositivos mobile (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 transmissibile per fluxo web (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 in qualitate HD (720P)", + "timedmedia-subtitle-new": "Crear un nove traduction o modificar le existente", + "timedmedia-subtitle-new-desc": "Selige un lingua e preme le button '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Va", + "timedmedia-subtitle-language": "$1 ($2) subtitulos", + "timedmedia-subtitle-no-video": "Il non ha video associate con le actual pagina de subtitulos", + "timedmedia-subtitle-no-subtitles": "Il ha actualmente nulle subtitulos in $1 pro iste video. Tu pote [{{fullurl:{{FULLPAGENAME}}|action=edit}} modificar iste pagina] pro adder los.", + "timedmedia-subtitle-remote": "Le subtitulos pro iste file es albergate sur $1", + "timedmedia-subtitle-remote-link": "Tu pote [$1 vider le pagina de description] pro iste file sur $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 video|$1 videos}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 video Ogg|$1 videos Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 video WebM|$1 videos WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transcodification|$1 transcodificationes}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 transcodification|$1 transcodificationes}} active", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transcodification|$1 transcodificationes}} in cauda", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 transcodification|$1 transcodificationes}} fallite", + "timedmedia-file": "File", + "timedmedia-audios": "{{PLURAL:$1|$1 file audio|$1 files audio}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 file audio Ogg|$1 files audio Ogg}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 file audio FLAC|$1 files audio FLAC}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 file audio WAV|$1 files audio WAV}}", + "right-transcode-reset": "Reinitialisar le videos fallite o transcodificate de sorta que illos es re-inserite in le cauda de actiones", + "right-transcode-status": "View [[Special:TimedMediaHandler|information sur le activitate currente de transcodification]]", + "action-transcode-status": "vider le stato actual de transcodification" +} diff --git a/extensions/TimedMediaHandler/i18n/id.json b/extensions/TimedMediaHandler/i18n/id.json new file mode 100644 index 00000000..48f6cc2a --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/id.json @@ -0,0 +1,97 @@ +{ + "@metadata": { + "authors": [ + "Bennylin", + "Farras", + "Irwangatot", + "IvanLanin", + "Iwan Novirion", + "Rex", + "පසිඳු කාවින්ද" + ] + }, + "timedmedia-desc": "Pemroses audio, video, dan teks berwaktu, dengan dukungan format WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Berkas suara $1 ogg, $2", + "timedmedia-ogg-short-video": "Berkas video $1 ogg, $2", + "timedmedia-ogg-short-general": "Berkas media $1 ogg, $2", + "timedmedia-ogg-long-audio": "Berkas suara $1 ogg, panjang $2, $3", + "timedmedia-ogg-long-video": "Berkas video $1 ogg, panjang $2, $4×$5 piksel, $3", + "timedmedia-ogg-long-multiplexed": "Berkas audio/video multiplexed ogg, $1, panjang $2, $4×$5 piksel, $3 keseluruhan", + "timedmedia-ogg-long-general": "Berkas media ogg, panjang $2, $3", + "timedmedia-ogg-long-error": "Berkas ogg tak valid: $1", + "timedmedia-webm-short-video": "Berkas video $1 WebM, $2", + "timedmedia-webm-long-video": "Berkas audio/video WebM, $1, lama $2, $4 × $5 piksel, $3 keseluruhan", + "timedmedia-no-player-js": "Maaf, peramban Anda memiliki JavaScript yang dinonaktifkan atau tidak memiliki pemutar media apapun.
\nAnda dapat mengunduh klip atau mengunduh pemutar untuk memutar klip di peramban Anda.", + "timedmedia-more": "Lainnya...", + "timedmedia-dismiss": "Tutup", + "timedmedia-download": "Unduh berkas", + "timedmedia-play-media": "Putar media", + "timedmedia-desc-link": "Mengenai berkas ini", + "timedmedia-oggThumb-version": "OggHandler membutuhkan oggThumb versi $1 atau terbaru.", + "timedmedia-oggThumb-failed": "oggThumb gagal membuat miniatur gambar.", + "timedmedia-status-header": "Status transkoder", + "timedmedia-update-status": "Memperbarui status transkoder", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Status tidak diketahui", + "timedmedia-transcodeinfo": "Deskripsi turunan transkoder", + "timedmedia-actions": "Tindakan", + "timedmedia-direct-link": "Unduh turunan", + "timedmedia-not-ready": "Belum siap", + "timedmedia-completed-on": "Transkoder komplit $1", + "timedmedia-error-on": "Kesalahan dalam transkoder pada $1 .", + "timedmedia-started-transcode": "Transkoder mulai $1 yang lalu. $2", + "timedmedia-percent-done": "Sekitar $1% selesai", + "timedmedia-days": "{{PLURAL:$1|$1 hari}}", + "timedmedia-hours": "{{PLURAL:$1|$1 jam}}", + "timedmedia-minutes": "{{PLURAL:$1|$1 menit}}", + "timedmedia-seconds": "{{PLURAL:$1|$1 detik}}", + "timedmedia-reset": "Reset transkoder", + "timedmedia-ogg": "Ogg", + "timedmedia-webm": "WebM", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Sumber $1", + "timedmedia-source-file-desc": "$1 asli, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Berkas ($2) asli $1", + "timedmedia-derivative-160p.ogv": "Ogg 160p", + "timedmedia-derivative-desc-160p.ogv": "Video Ogg lebar pita rendah (160P)", + "timedmedia-derivative-360p.ogv": "Ogg 360p", + "timedmedia-derivative-desc-360p.ogv": "Video Ogg aliran web (360P)", + "timedmedia-derivative-480p.ogv": "Ogg 480p", + "timedmedia-derivative-desc-480p.ogv": "Video Ogg aliran web (480P)", + "timedmedia-derivative-720p.ogv": "Ogg 720p", + "timedmedia-derivative-desc-720p.ogv": "Video Ogg unduhan berkualitas tinggi (720P)", + "timedmedia-derivative-160p.webm": "WebM 160p", + "timedmedia-derivative-360p.webm": "WebM 360p", + "timedmedia-derivative-desc-360p.webm": "WebM aliran web (360P)", + "timedmedia-derivative-480p.webm": "WebM 480p", + "timedmedia-derivative-desc-480p.webm": "WebM aliran web (480P)", + "timedmedia-derivative-720p.webm": "WebM 720p", + "timedmedia-derivative-desc-720p.webm": "WebM unduhan berkualitas tinggi (720P)", + "timedmedia-derivative-320p.mp4": "H264 320p", + "timedmedia-derivative-480p.mp4": "H264 480p", + "timedmedia-derivative-720p.mp4": "H264 720p", + "timedmedia-derivative-ogg": "Ogg Vorbis", + "timedmedia-derivative-desc-ogg": "Ogg Vorbis", + "timedmedia-derivative-opus": "Opus", + "timedmedia-derivative-desc-opus": "Opus", + "timedmedia-derivative-mp3": "MP3", + "timedmedia-derivative-desc-mp3": "MP3", + "timedmedia-derivative-m4a": "AAC", + "timedmedia-derivative-desc-m4a": "AAC", + "timedmedia-subtitle-new-go": "Lanjut", + "timedmedia-subtitle-language": "Teks film $1 ($2)", + "timedmedia-subtitle-no-video": "Tidak ada video yang terkait dengan halaman subjudul ini", + "timedmedia-subtitle-no-subtitles": "Saat ini tidak ada subjudul $1 untuk video ini. Anda dapat [{{fullurl: {{FULLPAGENAME}}|action=edit}} menyunting halaman ini] untuk menambahkannya", + "timedmediahandler": "Daftar berkas media", + "timedmedia-videos": "{{PLURAL:$1|$1 video}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Video ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 Video WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transkoder}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 transkoder aktif}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 antrian transkoder}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 transkoder rusak}}", + "timedmedia-file": "Berkas", + "right-transcode-reset": "Reset transkoder rusak ataupun yang tidak sehingga kembali dimasukkan dalam antrian.", + "right-transcode-status": "Lihat [[Special:TimedMediaHandler|informasi tentang aktifitas transkoder terkini]]", + "action-transcode-status": "lihat status transkoder terkini" +} diff --git a/extensions/TimedMediaHandler/i18n/ilo.json b/extensions/TimedMediaHandler/i18n/ilo.json new file mode 100644 index 00000000..11ce2be8 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ilo.json @@ -0,0 +1,89 @@ +{ + "@metadata": { + "authors": [ + "Lam-ang" + ] + }, + "timedmedia-desc": "Panagtengngel para iti audio, video ken naorasan a testo, nga adda ti porma a suporta para iti WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1 a mangeg a papeles, $2", + "timedmedia-ogg-short-video": "Ogg $1 a video a papeles, $2", + "timedmedia-ogg-short-general": "Ogg $1 a midia a papeles, $2", + "timedmedia-ogg-long-audio": "Ogg $1 a mangeg a papeles, kaatiddog $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 a video a papeles, kaatiddog $2, $4 × $5 a piksel, $3", + "timedmedia-ogg-long-multiplexed": "Ogg multiplexed nga audio/video a papeles, $1, kaatiddog $2, $4 × $5 a piksel, $3 amin-amin", + "timedmedia-ogg-long-general": "Ogg midia a papeles, kaatiddog $2, $3", + "timedmedia-ogg-long-error": "Imbalido nga Ogg a papeles: $1", + "timedmedia-webm-short-video": "WebM $1 a video a papeles, $2", + "timedmedia-webm-long-video": "WebM nga audio/video a papeles, $1, kaatiddog $2, $4 × $5 a piksel, $3 amin-amin", + "timedmedia-flac-short-audio": "Mangeg a papeles ti FLAC, $1", + "timedmedia-flac-long-audio": "Mangeg a papeles ti FLAC, kaatiddog $1, $2 ti amin a dagup", + "timedmedia-wav-short-audio": "Mangeg a papeles ti WAV, $1", + "timedmedia-wav-long-audio": "Mangeg a papeles ti WAV, kaatiddog $1, $2 ti amin a dagup", + "timedmedia-wav-pcm-required": "Mabalinmo laeng ti agikarga ti PCM (Pulse Code Modulation) WAV.", + "timedmedia-mp4-short-video": "MP4 $1 a video apapeles, $2", + "timedmedia-mp4-long-video": "MP4 nga audio/video a papeles, $1, kaatiddog $2, $4 × $5 a piksel, $3 amin-amin", + "timedmedia-no-player-js": "Pasensia, ti pagbasabasam ket mabalin a nabaldado ti JavaScript wenno awan ti nasuportaran a mangay-ayam.
\nMabalinmo ti agikarga ti sigpit wenno agikarga ti maysa a magay-ayam tapno maay-ayam ti sigpit iti pagbasabasam.", + "timedmedia-more": "Adu pay...", + "timedmedia-dismiss": "Irikep", + "timedmedia-download": "Ikarga ti papeles", + "timedmedia-play-media": "Ay-ayamen ti midia", + "timedmedia-desc-link": "Maipanggep ti daytoy a papeles", + "timedmedia-oggThumb-version": "Ti OggHandler ket masapulna ti oggThumb a bersion ti $1 wenno ti naududi.", + "timedmedia-oggThumb-failed": "Napaay ti oggThumb nga agaramid ti imahen.", + "timedmedia-status-header": "Kasasaad ti transkodigo", + "timedmedia-update-status": "Pabaruen ti kasasaad ti transkodigo", + "timedmedia-status": "Kasasaad", + "timedmedia-status-unknown": "Di ammo a kasasaad", + "timedmedia-transcodeinfo": "Deribatibo a deskripsion ti transkodigo", + "timedmedia-actions": "Dagiti aramid", + "timedmedia-direct-link": "Ikarga ti deribatibo", + "timedmedia-not-ready": "Saan pay a sisasagana", + "timedmedia-completed-on": "Nakompleto ti transkodigo $1", + "timedmedia-error-on": "Biddut iti transkodigo iti $1", + "timedmedia-started-transcode": "Ti transkodigo ket nairugi di $1. $2", + "timedmedia-percent-done": "Agarup a $1% ti nalapasen", + "timedmedia-in-job-queue": "Nagnayon idiay Pagurayan ti maaramid idi $1", + "timedmedia-unknown-target-size": "Di ammo a kadakkel ti puntaan, $1 ti nakodigo", + "timedmedia-days": "{{PLURAL:$1|1 nga aldaw|$1 nga al-aldaw}}", + "timedmedia-hours": "{{PLURAL:$1|1 nga oras|$1 nga or-oras}}", + "timedmedia-minutes": "{{PLURAL:$1|1 a minuto|$1 a minutos}}", + "timedmedia-seconds": "{{PLURAL:$1|1 a segundo|$1 a seg-segundo}}", + "timedmedia-reset": "Iyasentar manen ti transkodigo", + "timedmedia-reset-confirm": "Ti papangyasentar ti daytoy a transkodigo ket mangikkat ti aniaman nga adda a papeles (no adda), ket manginayonto manen ti transkodigo iti pagurayan ti maaramid.

\nSeguradoka kadi a kayatmo ti tumuloy?", + "timedmedia-reset-error": "Biddut ti panangiyasentar manen ti transkodigo a maaramid.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 taudan", + "timedmedia-source-file-desc": "Kasisigud a $1 a papeles, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Kasisigud a $1 a papeles ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ababa ti bandwidth a video ti Ogg (160P)", + "timedmedia-derivative-desc-360p.ogv": "Maiwaragawag ti web a video ti Ogg (360P)", + "timedmedia-derivative-desc-480p.ogv": "Maiwaragawag ti web a video ti Ogg (480P)", + "timedmedia-derivative-desc-720p.ogv": "Maikarga a nangato ti kalidadna a video ti Ogg (720P)", + "timedmedia-derivative-desc-160p.webm": "Maiwaragawag ti web a WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "Maiwaragawag ti web a WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "Maiwaragawag ti web a WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "Maikarga a nangato ti kalidadna a WebM (720)", + "timedmedia-derivative-desc-320p.mp4": "Mannakigayyem a ramit ti MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "Maiwaragawag ti web a Mp4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "HD ti kalidadna a MP4 (720P)", + "timedmedia-subtitle-new": "Agpartuat ti baro a panagipatarus wenno urnosen ti adda", + "timedmedia-subtitle-new-desc": "Agpili ti pagsasao ken pinduten ti '''{{int:Timedmedia-subtitle-new-go}}''' a buton", + "timedmedia-subtitle-new-go": "Inkan", + "timedmedia-subtitle-language": "Dagiti $1 ($2) a subtitulo", + "timedmedia-subtitle-no-video": "Awan ti video a mainanig iti agdama a subtitulo ti panid", + "timedmedia-subtitle-no-subtitles": "Awan dagiti agdama asubtitulo iti $1 para iti daytoy a video, mabalinmo ti [{{fullurl:{{FULLPAGENAME}}|action=edit}} agurnods ti daytoy a panid] tapno makanayonka kaniada", + "timedmedia-subtitle-remote": "Ti naorasan a testo para iti daytoy a papeles ket nanisangaili idiay $1", + "timedmedia-subtitle-remote-link": "Mabalinmo a [$1 kitaen ti deskripsion ti panid] para iti daytoy a papeles idiay $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 a video|dagiti $1 a video}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 nga Ogg a video|dagiti $1 nga Ogg a video}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 a WebM a video|dagiti $1 a WebM a video}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 a transkodigo|dagiti $1 a transkodigo}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 nga agtartaray a transkodigo|dagiti $1 nga agtartaray a transkodigo}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 a naiyur-uray a transkodigo|dagiti $1 a naiyur-uray a transkodigo}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 a napaay a transkodigo|dagiti $1 a napaay a transkodigo}}", + "timedmedia-file": "Papeles", + "right-transcode-reset": "Iyasentar manen ti napaay wenno dagiti naitranskodigo a video tapno maisengngatda manen ti pagurayan ti maaramid", + "right-transcode-status": "Kitaen ti [[Special:TimedMediaHandler|pakaammo a maipanggep ti agdama a tignay ti transkodigo]]", + "action-transcode-status": "kitaen ti agdama a kasasaad ti transkodigo" +} diff --git a/extensions/TimedMediaHandler/i18n/io.json b/extensions/TimedMediaHandler/i18n/io.json new file mode 100644 index 00000000..8977382c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/io.json @@ -0,0 +1,11 @@ +{ + "@metadata": { + "authors": [ + "Malafaya" + ] + }, + "timedmedia-ogg-long-error": "Ne-valida Ogg-arkivo: $1", + "timedmedia-more": "Plus…", + "timedmedia-dismiss": "Klozar", + "timedmedia-desc-link": "Pri ca arkivo" +} diff --git a/extensions/TimedMediaHandler/i18n/is.json b/extensions/TimedMediaHandler/i18n/is.json new file mode 100644 index 00000000..3188925c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/is.json @@ -0,0 +1,60 @@ +{ + "@metadata": { + "authors": [ + "S.Örvarr.S", + "Snævar", + "Spacebirdy" + ] + }, + "timedmedia-desc": "Rekill fyrir hljóð, myndbönd og texta myndbanda með stuðningi við skráarsniðin WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1 hljóðskrá, $2", + "timedmedia-ogg-short-video": "Ogg $1 myndbandskrá, $2", + "timedmedia-ogg-short-general": "Ogg $1 margmiðlunarskrá, $2", + "timedmedia-ogg-long-audio": "Ogg $1 hljóðskrá, lengd $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 myndbandskrá, lengd $2, $4×$5 dílar, $3", + "timedmedia-ogg-long-multiplexed": "Ogg myndbandaskrá, $1, lengd $2, $4 × $5 dílar, $3 samanlagt", + "timedmedia-ogg-long-general": "Ogg margmiðlunarskrá, lengd $2, $3", + "timedmedia-ogg-long-error": "Ógild Ogg skrá: $1", + "timedmedia-webm-short-video": "WebM $1 myndbandskrá, $2", + "timedmedia-webm-long-video": "WebM myndbandaskrá, $1, lengd $2, $4 × $5 dílar, $3 samanlagt", + "timedmedia-mp4-short-video": "MP4 $1 myndbandaskrá, $2", + "timedmedia-mp4-long-video": "MP4 myndbandaskrá, $1, lengd $2, $4 × $5 dílar, $3 samanlagt", + "timedmedia-no-player-js": "Því miður hefur vafrinn þinn annaðhvort óvirkan JavaScript eða engan studdann spilara.
\nÞú getur hlaðið niður skránni eða hlaðið niður spilara til þess að spila skránna í vafranum.", + "timedmedia-more": "Meira...", + "timedmedia-dismiss": "Loka", + "timedmedia-download": "Sækja skrá", + "timedmedia-play-media": "Spila margmiðlunarskrá", + "timedmedia-desc-link": "Um þessa skrá", + "timedmedia-oggThumb-version": "OggHandler þarfnast oggThumb útgáfu $1 eða nýrri.", + "timedmedia-oggThumb-failed": "oggThumb mistókst að búa til smámynd.", + "timedmedia-status": "Staða", + "timedmedia-actions": "Aðgerðir", + "timedmedia-direct-link": "Sækja eftirgerð", + "timedmedia-not-ready": "Ekki tilbúin", + "timedmedia-percent-done": "Um $1% lokið", + "timedmedia-unknown-target-size": "Óþekkt markstærð, $1 kóðað", + "timedmedia-days": "$1 {{PLURAL:$1|dagur|dagar}}", + "timedmedia-hours": "$1 {{PLURAL:$1|klukkutími|klukkutímar}}", + "timedmedia-minutes": "$1 {{PLURAL:$1|mínúta|mínútur}}", + "timedmedia-seconds": "$1 {{PLURAL:$1|sekúnda|sekúndur}}", + "timedmedia-source-file": "$1 uppruni", + "timedmedia-source-file-desc": "Upphafleg $1 skrá, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Upphafleg $1 skrá ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg myndband fyrir litla bandbreidd (160P)", + "timedmedia-derivative-desc-360p.ogv": "Ogg myndbands streymi (360P)", + "timedmedia-derivative-desc-480p.ogv": "Ogg myndbands streymi (480P)", + "timedmedia-derivative-desc-720p.ogv": "Hágæða niðurhalanlegt Ogg myndband (720P)", + "timedmedia-derivative-desc-160p.webm": "WebM myndbands streymi (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM myndbands streymi (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM myndbands streymi (480P)", + "timedmedia-derivative-desc-720p.webm": "Hágæða niðurhalanlegt WebM myndband (720P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 myndbands streymi (480P)", + "timedmedia-derivative-desc-720p.mp4": "Háskerpu MP4 (720P)", + "timedmedia-subtitle-new-desc": "Veldu tungumálið og smelltu á '''{{int:Timedmedia-subtitle-new-go}}''' takkann", + "timedmedia-subtitle-new-go": "Áfram", + "timedmedia-subtitle-no-video": "Ekkert myndband tengist þessum myndbanda texta", + "timedmedia-videos": "{{PLURAL:$1|$1 myndband|$1 myndbönd}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg myndband|$1 Ogg myndbönd}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM myndband|$1 WebM myndbönd}}", + "timedmedia-file": "Skrá" +} diff --git a/extensions/TimedMediaHandler/i18n/it.json b/extensions/TimedMediaHandler/i18n/it.json new file mode 100644 index 00000000..fe37c8b2 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/it.json @@ -0,0 +1,108 @@ +{ + "@metadata": { + "authors": [ + ".anaconda", + "Beta16", + "BrokenArrow", + "Darth Kule", + "F. Cosoleto", + "Gianfranco", + "Raoli", + "Peg60" + ] + }, + "timedmedia-desc": "Gestore per i file audio, video e con i sottotitoli; formati supportati: WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "File audio Ogg $1, $2", + "timedmedia-ogg-short-video": "File video Ogg $1, $2", + "timedmedia-ogg-short-general": "File multimediale Ogg $1, $2", + "timedmedia-ogg-long-audio": "File audio Ogg $1, durata $2, $3", + "timedmedia-ogg-long-video": "File video Ogg $1, durata $2, dimensioni $4×$5 pixel, $3", + "timedmedia-ogg-long-multiplexed": "File audio/video multiplexed Ogg $1, durata $2, dimensioni $4×$5 pixel, complessivamente $3", + "timedmedia-ogg-long-general": "File multimediale Ogg, durata $2, $3", + "timedmedia-ogg-long-error": "File ogg non valido: $1", + "timedmedia-ogg-long-no-streams": "File multimediale Ogg. Attenzione: non è stato riconosciuto alcuno dei codec usati in questo file.", + "timedmedia-webm-short-video": "File video WebM $1, $2", + "timedmedia-webm-long-video": "File audio/video WebM $1, durata $2, dimensioni $4×$5 pixel, complessivamente $3", + "timedmedia-flac-short-audio": "File audio FLAC, $1", + "timedmedia-flac-long-audio": "File audio FLAC, durata $1, complessivamente $2", + "timedmedia-wav-short-audio": "File audio WAV, $1", + "timedmedia-wav-long-audio": "File audio WAV, durata $1, complessivamente $2", + "timedmedia-wav-pcm-required": "Puoi caricare solo PCM (Pulse Code Modulation) WAV.", + "timedmedia-mp4-short-video": "File video MP4 $1, $2", + "timedmedia-mp4-long-video": "File audio/video MP4 $1, durata $2, dimensioni $4×$5 pixel, complessivamente $3", + "timedmedia-no-player-js": "Si sta utilizzando un browser che ha JavaScript disattivato oppure non ha alcun riproduttore supportato.
\nÈ possibile scaricare il clip multimediale o un riproduttore per riprodurre il clip nel proprio browser.", + "timedmedia-more": "Altro...", + "timedmedia-dismiss": "Chiudi", + "timedmedia-download": "Scarica il file", + "timedmedia-play-media": "Riproduci file multimediale", + "timedmedia-desc-link": "Informazioni su questo file", + "timedmedia-oggThumb-version": "OggHandler richiede la versione $1 o superiore di oggThumb.", + "timedmedia-oggThumb-failed": "oggThumb non è riuscito a creare la miniatura.", + "timedmedia-status-header": "Stato transcodifica", + "timedmedia-update-status": "Aggiorna stato transcodifica", + "timedmedia-status": "Stato", + "timedmedia-status-unknown": "Stato sconosciuto", + "timedmedia-transcodeinfo": "Formato", + "timedmedia-actions": "Azioni", + "timedmedia-direct-link": "Scarica", + "timedmedia-not-ready": "Non pronto", + "timedmedia-completed-on": "Completato $1", + "timedmedia-error-on": "Errore il $1", + "timedmedia-started-transcode": "Iniziato $1 fa. $2", + "timedmedia-percent-done": "$1% circa completato", + "timedmedia-in-job-queue": "Aggiunto alla coda di lavoro $1 fa", + "timedmedia-unknown-target-size": "Dimensioni di destinazione sconosciuta, $1 codificato", + "timedmedia-days": "{{PLURAL:$1|1 giorno|$1 giorni}}", + "timedmedia-hours": "{{PLURAL:$1|1 ora|$1 ore}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuto|$1 minuti}}", + "timedmedia-seconds": "{{PLURAL:$1|1 secondo|$1 secondi}}", + "timedmedia-reset": "Resetta transcodifica", + "timedmedia-reset-confirm": "Resettando questa transcodifica tutti i file esistenti (se presenti) saranno cancellati e la transcodifica sarà nuovamente aggiunta alla coda di lavoro. Ci vorrà qualche tempo per rieseguire la transcodifica.

\nContinuare?", + "timedmedia-reset-error": "Errore nel resettaggio della transcodifica.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Fonte $1", + "timedmedia-source-file-desc": "File originale $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "File originale $1 ($2)", + "timedmedia-derivative-desc-160p.ogv": "Video Ogg a bassa larghezza di banda (160P)", + "timedmedia-derivative-desc-360p.ogv": "Video Ogg trasmettibile in streaming via Web (360P)", + "timedmedia-derivative-desc-480p.ogv": "Video Ogg trasmettibile in streaming via Web (480P)", + "timedmedia-derivative-desc-720p.ogv": "Video Ogg scaricabile in alta qualità (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Video Ogg scaricabile in Full HD (1080P)", + "timedmedia-derivative-desc-160p.webm": "WebM trasmettibile in streaming via Web (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM trasmettibile in streaming via Web (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM trasmettibile in streaming via Web (480P)", + "timedmedia-derivative-desc-720p.webm": "WebM scaricabile in alta qualità (720P)", + "timedmedia-derivative-desc-1080p.webm": "WebM scaricabile in Full HD (1080P)", + "timedmedia-derivative-desc-320p.mp4": "MP4 per dispositivi compatibili (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 trasmettibile in streaming via Web (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 in qualità HD (720P)", + "timedmedia-derivative-desc-1080p.mp4": "MP4 di qualità Full HD (1080P)", + "timedmedia-subtitle-new": "Creare una nuova traduzione o modificare esistenti", + "timedmedia-subtitle-new-desc": "Seleziona la lingua e premi il pulsante '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Vai", + "timedmedia-subtitle-language": "$1 ($2) sottotitoli", + "timedmedia-subtitle-no-video": "Non non c'è nessun video associato all'attuale pagina dei sottotitoli", + "timedmedia-subtitle-no-subtitles": "Non ci sono al momento sottotitoli in $1 per questo video, è possibile [{{fullurl:{{FULLPAGENAME}}|action=edit}} modificare questa pagina] per aggiungerli", + "timedmedia-subtitle-remote": "I sottotitoli per questo file sono ospitati su $1", + "timedmedia-subtitle-remote-link": "Puoi vedere la [$1 pagina di descrizione] per questo file su $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 video}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 video Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 video WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transcodifica|$1 transcodifiche}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 transcodifica|$1 transcodifiche}} in esecuzione", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transcodifica|$1 transcodifiche}} in coda", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 transcodifica fallita|$1 transcodifiche fallite}}", + "timedmedia-file": "File", + "timedmedia-audios": "{{PLURAL:$1|$1 file audio}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 file audio Ogg}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 file audio FLAC}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 file audio WAV}}", + "right-transcode-reset": "Reimposta i video difettosi o transcodificati così che possano essere inclusi nuovamente in coda di lavoro.", + "right-transcode-status": "Visualizza [[Special:TimedMediaHandler|informazioni sull'attuale attività di transcodifica]]", + "action-transcode-status": "visualizzare l'attuale stato della transcodifica", + "orphanedtimedtext": "Pagine sottotitoli orfane", + "orphanedtimedtext-summary": "Elenco di pagine [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] che non hanno un file corrispondente.", + "orphanedtimedtext-unsupported": "Questa pagina speciale è supportata solo su database MySQL.", + "orphanedtimedtext-notimedtext": "TimedText non è abilitato su questo wiki." +} diff --git a/extensions/TimedMediaHandler/i18n/ja.json b/extensions/TimedMediaHandler/i18n/ja.json new file mode 100644 index 00000000..291a5224 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ja.json @@ -0,0 +1,91 @@ +{ + "@metadata": { + "authors": [ + "Aotake", + "Fryed-peach", + "JtFuruhata", + "Kahusi", + "Shirayuki", + "青子守歌", + "Otokoume" + ] + }, + "timedmedia-desc": "WebM、Ogg Theora、Vorbis、srt 形式に対応した、動画、音声、字幕のハンドラー", + "timedmedia-ogg-short-audio": "Ogg $1 音声ファイル、$2", + "timedmedia-ogg-short-video": "Ogg $1 動画ファイル、$2", + "timedmedia-ogg-short-general": "Ogg $1 メディアファイル、$2", + "timedmedia-ogg-long-audio": "Ogg $1 音声ファイル、長さ $2、$3", + "timedmedia-ogg-long-video": "Ogg $1 動画ファイル、長さ $2、$4 × $5 ピクセル、$3", + "timedmedia-ogg-long-multiplexed": "Ogg 多重音声/動画ファイル、$1、長さ $2、$4 × $5 ピクセル、全体で $3", + "timedmedia-ogg-long-general": "Ogg メディアファイル、長さ $2、$3", + "timedmedia-ogg-long-error": "無効な Ogg ファイル: $1", + "timedmedia-ogg-long-no-streams": "Ogg メディア ファイル。警告: このファイルで使用されているどのコーデックも認識できませんでした。", + "timedmedia-webm-short-video": "WebM $1 動画ファイル、$2", + "timedmedia-webm-long-video": "WebM 音声/動画ファイル、$1、長さ $2、$4 × $5 ピクセル、全体で $3", + "timedmedia-flac-short-audio": "FLAC 音声ファイル、$1", + "timedmedia-flac-long-audio": "FLAC 音声ファイル、長さ $1、全体で $2", + "timedmedia-wav-short-audio": "WAV 音声ファイル、$1", + "timedmedia-wav-long-audio": "WAV 音声ファイル、長さ $1、全体で $2", + "timedmedia-wav-pcm-required": "アップロードできるのは PCM (パルス符号変調) WAV のみです。", + "timedmedia-mp4-short-video": "MP4 $1 動画ファイル、$2", + "timedmedia-mp4-long-video": "MP4 音声/動画ファイル、$1、長さ $2、$4 × $5 ピクセル、全体で $3", + "timedmedia-no-player-js": "申し訳ありませんが、あなたのブラウザーではJavaScriptが無効になっているか、対応しているプレーヤーがありません。
\n再生するには、クリップをダウンロードするか、プレーヤーをダウンロードする必要があります。", + "timedmedia-more": "その他…", + "timedmedia-dismiss": "閉じる", + "timedmedia-download": "ファイルをダウンロードする", + "timedmedia-play-media": "メディアを再生する", + "timedmedia-desc-link": "ファイルの詳細", + "timedmedia-oggThumb-version": "OggHandler には oggThumb バージョン $1 またはそれ以降が必要です。", + "timedmedia-oggThumb-failed": "oggThumb によるサムネイル作成に失敗しました。", + "timedmedia-status": "状態", + "timedmedia-status-unknown": "不明な状態", + "timedmedia-transcodebitrate": "ビットレート", + "timedmedia-transcodeduration": "エンコード時間", + "timedmedia-transcodeinfo": "形式", + "timedmedia-actions": "操作", + "timedmedia-direct-link": "ダウンロード", + "timedmedia-not-ready": "準備ができていません", + "timedmedia-error-on": "$1 でのエラー", + "timedmedia-percent-done": "約 $1% 完了", + "timedmedia-in-job-queue": "$1前にジョブ キューに追加されました", + "timedmedia-days": "{{PLURAL:$1|$1 日}}", + "timedmedia-hours": "{{PLURAL:$1|$1 時間}}", + "timedmedia-minutes": "{{PLURAL:$1|$1 分}}", + "timedmedia-seconds": "{{PLURAL:$1|$1 秒}}", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 ソース", + "timedmedia-source-file-desc": "オリジナル $1 ファイル、$2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "オリジナル $1 ファイル ($2)", + "timedmedia-derivative-desc-160p.ogv": "低帯域 Ogg 動画 (160P)", + "timedmedia-derivative-desc-360p.ogv": "ウェブ ストリーミング可能 Ogg 動画 (360P)", + "timedmedia-derivative-desc-480p.ogv": "ウェブ ストリーミング可能 Ogg 動画 (480P)", + "timedmedia-derivative-desc-720p.ogv": "高品質ダウンロード可能 Ogg 動画 (720P)", + "timedmedia-derivative-desc-1080p.ogv": "フル HD ダウンロード可能 Ogg 動画 (1080P)", + "timedmedia-derivative-desc-160p.webm": "ウェブ ストリーミング可能 WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "ウェブ ストリーミング可能 WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "ウェブ ストリーミング可能 WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "高品質ダウンロード可能 WebM (720P)", + "timedmedia-derivative-desc-1080p.webm": "フル HD ダウンロード可能 WebM (1080P)", + "timedmedia-derivative-desc-320p.mp4": "デバイスフレンドリー MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "ウェブ ストリーミング可能 MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "HD 品質 MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "フル HD 品質 MP4 (1080P)", + "timedmedia-subtitle-new": "翻訳の新規作成、または既存の翻訳の編集", + "timedmedia-subtitle-new-desc": "言語を選択して、'''{{int:Timedmedia-subtitle-new-go}}'''ボタンを押してください。", + "timedmedia-subtitle-new-go": "実行", + "timedmedia-subtitle-language": "$1 ($2) 字幕", + "timedmedia-subtitle-no-video": "現在の字幕ページに関連付けられた動画はありません。", + "timedmedia-subtitle-no-subtitles": "現在、この動画には$1の字幕はありませんが、[{{fullurl:{{FULLPAGENAME}}|action=edit}} このページを編集]して追加できます。", + "timedmedia-subtitle-remote": "このファイルの字幕は$1でホストされています", + "timedmedia-subtitle-remote-link": "$2 でこのファイルの[$1 解説ページを閲覧]できます", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 件の動画}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 件の Ogg 動画}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 件の WebM 動画}}", + "timedmedia-file": "ファイル", + "timedmedia-audios": "{{PLURAL:$1|$1 件の音声ファイル}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 件の Ogg 音声ファイル}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 件の FLAC 音声ファイル}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 件の WAV 音声ファイル}}", + "apihelp-transcodereset-param-title": "メディアファイルのタイトル。" +} diff --git a/extensions/TimedMediaHandler/i18n/jut.json b/extensions/TimedMediaHandler/i18n/jut.json new file mode 100644 index 00000000..c8c30c29 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/jut.json @@ -0,0 +1,21 @@ +{ + "@metadata": { + "authors": [ + "Huslåke", + "Jyllanj" + ] + }, + "timedmedia-desc": "Håndlær før Ogg Theora og Vorbis filer, ve JavaScript spæler", + "timedmedia-ogg-short-audio": "Ogg $1 lydfile, $2", + "timedmedia-ogg-short-video": "Ogg $1 videofil, $2", + "timedmedia-ogg-short-general": "Ogg $1 mediafil, $2", + "timedmedia-ogg-long-audio": "Ogg $1 lydfil, længd $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 videofil, længd $2, $4×$5 pixel, $3", + "timedmedia-ogg-long-multiplexed": "Sammelsatt Ogg-lyd- å -videofil, $1, længd $2, $4×$5 pixel, $3 sammeltj", + "timedmedia-ogg-long-general": "Ogg mediafil, længd $2, $3", + "timedmedia-ogg-long-error": "Udjylji Ogg-fil: $1", + "timedmedia-more": "Mier...", + "timedmedia-dismiss": "Lokk", + "timedmedia-download": "Lägg fil nier", + "timedmedia-desc-link": "Om fili" +} diff --git a/extensions/TimedMediaHandler/i18n/jv.json b/extensions/TimedMediaHandler/i18n/jv.json new file mode 100644 index 00000000..0bb2c6ee --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/jv.json @@ -0,0 +1,65 @@ +{ + "@metadata": { + "authors": [ + "Meursault2004", + "NoiX180", + "Pras" + ] + }, + "timedmedia-desc": "Sing ngurusi berkas Ogg Theora lan Vorbis mawa pamain JavaScript", + "timedmedia-ogg-short-audio": "Berkas swara $1 ogg, $2", + "timedmedia-ogg-short-video": "Berkas vidéo $1 ogg, $2", + "timedmedia-ogg-short-general": "Berkas média $1 ogg, $2", + "timedmedia-ogg-long-audio": "Berkas swara $1 ogg, dawané $2, $3", + "timedmedia-ogg-long-video": "Berkas vidéo $1 ogg, dawané $2, $4×$5 piksel, $3", + "timedmedia-ogg-long-multiplexed": "Berkas audio/vidéo multiplexed ogg, $1, dawané $2, $4×$5 piksel, $3 gunggungé", + "timedmedia-ogg-long-general": "Berkas média ogg, dawané $2, $3", + "timedmedia-ogg-long-error": "Berkas ogg ora absah: $1", + "timedmedia-more": "Luwih akèh...", + "timedmedia-dismiss": "Tutup", + "timedmedia-download": "Undhuh berkas", + "timedmedia-play-media": "Puter média", + "timedmedia-desc-link": "Prekara berkas iki", + "timedmedia-oggThumb-version": "OggHandler mbutuhaké oggThumb vèrsi $1 utawa sakbanjuré.", + "timedmedia-oggThumb-failed": "oggTumb gagal nggawé gambar mini.", + "timedmedia-status-header": "Status transkodhé", + "timedmedia-update-status": "Anyari status transkodhé", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Status ora dingertèni", + "timedmedia-transcodeinfo": "Katrangan turunan transkodhé", + "timedmedia-actions": "Laku", + "timedmedia-direct-link": "Undhuh turunan", + "timedmedia-not-ready": "Durung siap", + "timedmedia-completed-on": "Nglengkapi transkodhé $1", + "timedmedia-error-on": "Kasalahan nèng transkodhé $1.", + "timedmedia-started-transcode": "Transkodhé lekas $1 kapungkur. $2", + "timedmedia-percent-done": "Kira-kira $1 rampung", + "timedmedia-in-job-queue": "Nambahaké nèng antrian Pakaryan $1 kapungkur", + "timedmedia-unknown-target-size": "Gedhé patujon oradingertèni, $1 kasandhèkaké", + "timedmedia-days": "{{PLURAL:$1|dina|dina}}", + "timedmedia-hours": "{{PLURAL:$1|jam|jam}}", + "timedmedia-minutes": "{{PLURAL:$1|menit|menit}}", + "timedmedia-seconds": "{{PLURAL:$1|detik|detik}}", + "timedmedia-reset": "Setèl ulang transkodhé", + "timedmedia-reset-error": "Kasalahan nalika nyetèl ulang tugas transkodhé.", + "timedmedia-source-file": "Sumber $1", + "timedmedia-source-file-desc": "$1 asli, $2 × $3 ($4)", + "timedmedia-derivative-desc-160p.ogv": "Vidio Ogg bandwidth cilik (160P)", + "timedmedia-derivative-desc-720p.ogv": "Vidio Ogg sing bisa diundhuh mawa kualitas dhuwur (720P)", + "timedmedia-derivative-desc-720p.webm": "WebM sing bisa diundhuh mawa kualitas dhuwur (720P)", + "timedmedia-subtitle-new": "Gawé terjemahan anyar utawa sunting sing wis ana", + "timedmedia-subtitle-new-desc": "Ganti bagéyan '''$1''' nganggo [[:en:ISO 639|kodhé basa]] Sampéyan lan pencèt tombol '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Nuju", + "timedmedia-subtitle-language": "Subjudhul $1 ($2)", + "timedmedia-subtitle-no-video": "Ora ana vidio sing cocok karo kaca subjudhul saiki", + "timedmedia-subtitle-no-subtitles": "Sawetara durung ana subjudhul nèng $1 kanggo vidio iki, Sampéyan bisa [{{fullurl:{{FULLPAGENAME}}|action=edit}} nyunting kaca iki] kanggo nambahi", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 vidio|$1 vidio}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 vidio Ogg|$1 vidio Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 vidio WebM|$1 vidio WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transkodhé|$1 transkodhé}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 transkodhé lumangsung|$1 transkodhé lumangsung}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transkodhé ngantri|$1 transkodhé ngantri}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 transkodhé gagal|$1 transkodhé gagal}}", + "timedmedia-file": "Berkas" +} diff --git a/extensions/TimedMediaHandler/i18n/ka.json b/extensions/TimedMediaHandler/i18n/ka.json new file mode 100644 index 00000000..5c43ea7b --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ka.json @@ -0,0 +1,89 @@ +{ + "@metadata": { + "authors": [ + "BRUTE", + "David1010", + "Malafaya", + "Tokoko", + "გიორგიმელა" + ] + }, + "timedmedia-desc": "აუდიოს, ვიდეოს და სუბტიტრების დამამუშავებელი, შემდეგი ფორმატების მხარდაჭერით: WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1 ხმოვანი ფაილი, $2", + "timedmedia-ogg-short-video": "Ogg $1 ვიდეო ფაილი, $2", + "timedmedia-ogg-short-general": "Ogg $1 მედია ფაილი, $2", + "timedmedia-ogg-long-audio": "Ogg $1 ხმოვანი ფაილი, სიგრძე $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 ვიდეო ფაილი, სიგრძე $2, $4 × $5 პიქსელი, $3", + "timedmedia-ogg-long-multiplexed": "Ogg მულტიპლექსური აუდიო/ვიდეო ფაილი, $1, სიგრძე $2, $4 × $5 პიქსელი, $3 სრული", + "timedmedia-ogg-long-general": "Ogg მედია ფაილი, სიგრძე $2, $3", + "timedmedia-ogg-long-error": "არასწორი Ogg-ფაილი: $1", + "timedmedia-webm-short-video": "WebM $1 ვიდეო ფაილი, $2", + "timedmedia-webm-long-video": "WebM აუდიო/ვიდეო ფაილი, $1, სიგრძე $2, $4 × $5 პიქსელი, $3 სრული", + "timedmedia-flac-short-audio": "FLAC აუდიო ფაილი, $1", + "timedmedia-wav-short-audio": "WAV აუდიო ფაილი, $1", + "timedmedia-mp4-short-video": "MP4 $1 ვიდეო ფაილი, $2", + "timedmedia-mp4-long-video": "MP4 აუდიო/ვიდეო ფაილი, $1, სიგრძე $2, $4 × $5 პიქსელი, $3 სრული", + "timedmedia-no-player-js": "სამწუხაროდ, თქვენ ბრაუზერში გამორთულია JavaScript ან არ არის საჭირო დამკვრელი.
\nთქვენ შეგიძლიათ გადმოწეროთ კლიპი ან გადმოწეროთ დამკვრელი კლიპის ბრაუზერში დასაკრავად.", + "timedmedia-more": "მეტი...", + "timedmedia-dismiss": "დახურვა", + "timedmedia-download": "ფაილის ჩამოტვირთვა", + "timedmedia-play-media": "მედია-ფაილის დაკვრა", + "timedmedia-desc-link": "ამ ფაილის შესახებ", + "timedmedia-oggThumb-version": "OggHandler-ს სჭირდება oggThumb-ის ვერსია $1 ან უფრო გვიანდელი.", + "timedmedia-oggThumb-failed": "oggThumb-მა ვერ მოახერხა მინიატიურის შექმნა.", + "timedmedia-status-header": "დეკოდირების სტატუსი", + "timedmedia-update-status": "დეკოდირების სტატუსის განახლება", + "timedmedia-status": "სტატუსი", + "timedmedia-status-unknown": "უცნობი სტატუსი", + "timedmedia-transcodeinfo": "ფორმატი", + "timedmedia-actions": "მოქმედება", + "timedmedia-direct-link": "გადმოწერა", + "timedmedia-not-ready": "არაა მზად", + "timedmedia-completed-on": "$1-ის დეკოდირება დასრულებულია", + "timedmedia-error-on": "შეცდომა დეკოდირებისას $1-ზე.", + "timedmedia-started-transcode": "დეკოდირება გაშვებულია $1 წინ. $2", + "timedmedia-percent-done": "დაახლოებით $1% შესრულებულია", + "timedmedia-in-job-queue": "დამატებულია დავალებების რიგში $1 წინ", + "timedmedia-unknown-target-size": "უცნობი სამიზნო ზომა, $1 კოდირებისას", + "timedmedia-days": "{{PLURAL:$1|1 დღე|$1 დღე}}", + "timedmedia-hours": "{{PLURAL:$1|1 საათი|$1 საათი}}", + "timedmedia-minutes": "{{PLURAL:$1|1 წუთი|$1 წუთი}}", + "timedmedia-seconds": "{{PLURAL:$1|1 წამი|$1 წამი}}", + "timedmedia-reset": "დეკოდირების თავიდან დაყენება", + "timedmedia-reset-confirm": "დეკოდირების თავიდან დაყენება წაშლის არსებულ ფაილს (თუკი ის არსებობს) და დეკოდირება კვლავ დაემატება დავალებების რიგში. განმეორებით დეკოდირებას დაჭირდება გარკვეული დრო.

\nდარწმუნებული ხართ, რომ გსურთ გააგრძელოთ?", + "timedmedia-reset-error": "შეცდომა დეკოდირების თავიდან დაყენებისას.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "წყარო $1", + "timedmedia-source-file-desc": "ორიგინალი $1 ფაილი, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "ორიგინალი $1 ფაილი ($2)", + "timedmedia-derivative-desc-160p.ogv": "დაბალი ხარისხის Ogg-ვიდეო (160P)", + "timedmedia-derivative-desc-360p.ogv": "ვებ-ნაკადური Ogg-ვიდეო (360P)", + "timedmedia-derivative-desc-480p.ogv": "ვებ-ნაკადური Ogg-ვიდეო (480P)", + "timedmedia-derivative-desc-720p.ogv": "მაღალი ხარისხის გადმოწერადი Ogg-ვიდეო (720P)", + "timedmedia-derivative-desc-160p.webm": "ვებ-ნაკადური WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "ვებ-ნაკადური WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "ვებ-ნაკადური WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "მაღალი ხარისხის გადმოწერადი WebM (720P)", + "timedmedia-derivative-desc-320p.mp4": "მეგობრული-მოწყობილობა MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "ვებ-ნაკადური MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "HD ხარისხი MP4 (720P)", + "timedmedia-subtitle-new": "შექმენით ახალი თარგმანი ან დაარედაქტირეთ არსებული", + "timedmedia-subtitle-new-desc": "აირჩიეთ ენა და დააჭირეთ ღილაკს '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "მიდი", + "timedmedia-subtitle-language": "$1 ($2) სუბტიტრები", + "timedmedia-subtitle-no-video": "აქ არ არის ვიდეო, რომელიც დაკავშირებულია სუბტიტრების მიმდინარე გვერდთან", + "timedmedia-subtitle-no-subtitles": "ამჟამად $1 სუბტიტრები ამ ვიდეოსათვის არ არის, თქვენ შეგიძლიათ [{{fullurl:{{FULLPAGENAME}}|action=edit}} შეცვალოთ ეს გვერდი] მათ დასამატებლად", + "timedmedia-subtitle-remote": "ამ ფაილის დროებითი ტექსტი განთავსებულია $1-ზე", + "timedmedia-subtitle-remote-link": "თქვენ შეგიძლიათ [$1 იხილოთ ამ ფაილის აღწერის გვერდი] $2-ზე", + "timedmediahandler": "დროული მედია მართვის პროგრამა", + "timedmedia-videos": "{{PLURAL:$1|$1 ვიდეო|$1 ვიდეო}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg ვიდეო|$1 Ogg ვიდეო}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM ვიდეო|$1 WebM ვიდეო}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 დეკოდირებული|$1 დეკოდირებული}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|მიმდინარეობს $1 დეკოდირება|მიმდინარეობს $1 დეკოდირება}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|რიგშია $1 დეკოდირება|რიგშია $1 დეკოდირება}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 დეკოდირება ჩაიშალა|$1 დეკოდირება ჩაიშალა}}", + "timedmedia-file": "ფაილი", + "right-transcode-status": "იხილეთ [[Special:TimedMediaHandler|ინფორმაცია მიმდინარე დეკოდირების შესახებ]]", + "action-transcode-status": "იხილეთ მიმდინარე დეკოდირების სტატუსი" +} diff --git a/extensions/TimedMediaHandler/i18n/khw.json b/extensions/TimedMediaHandler/i18n/khw.json new file mode 100644 index 00000000..821b2a13 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/khw.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Rachitrali" + ] + }, + "timedmedia-no-player-js": "معذرت، تہ براؤزرا جاواسکرپٹ فعال نیکی یا کیہ معاون پلیئر شامل نیکی۔
\nھیہ کلپو تان براؤزرا چلئیکو بچے تو ھیہ کلپو ڈاؤنلوڈ کورے یا or پلیئر ڈاؤنلوڈ کورے۔", + "timedmedia-source-file": "$1 مسودہ", + "timedmedia-source-audio-file-desc": "اصلی $1 فائل($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/kk-arab.json b/extensions/TimedMediaHandler/i18n/kk-arab.json new file mode 100644 index 00000000..e348e4d2 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/kk-arab.json @@ -0,0 +1,15 @@ +{ + "@metadata": [], + "timedmedia-ogg-short-audio": "Ogg $1 دىبىس فايلى, $2", + "timedmedia-ogg-short-video": "Ogg $1 بەينە فايلى, $2", + "timedmedia-ogg-short-general": "Ogg $1 تاسپا فايلى, $2", + "timedmedia-ogg-long-audio": "Ogg $1 دىبىس فايلى, ۇزاقتىعى $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 بەينە فايلى, ۇزاقتىعى $2, $4 × $5 پىيكسەل, $3", + "timedmedia-ogg-long-multiplexed": "Ogg قۇرامدى دىبىس/بەينە فايلى, $1, ۇزاقتىعى $2, $4 × $5 پىيكسەل, $3 نە بارلىعى", + "timedmedia-ogg-long-general": "Ogg تاسپا فايلى, ۇزاقتىعى $2, $3", + "timedmedia-ogg-long-error": "جارامسىز ogg فايلى: $1", + "timedmedia-more": "كوبىرەك...", + "timedmedia-dismiss": "جابۋ", + "timedmedia-download": "فايلدى جۇكتەۋ", + "timedmedia-desc-link": "بۇل فايل تۋرالى" +} diff --git a/extensions/TimedMediaHandler/i18n/kk-cyrl.json b/extensions/TimedMediaHandler/i18n/kk-cyrl.json new file mode 100644 index 00000000..c378cf48 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/kk-cyrl.json @@ -0,0 +1,56 @@ +{ + "@metadata": { + "authors": [ + "Arystanbek" + ] + }, + "timedmedia-ogg-short-audio": "Ogg $1 дыбыс файлы, $2", + "timedmedia-ogg-short-video": "Ogg $1 бейне файлы, $2", + "timedmedia-ogg-short-general": "Ogg $1 таспа файлы, $2", + "timedmedia-ogg-long-audio": "Ogg $1 дыбыс файлы, ұзақтығы $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 бейне файлы, ұзақтығы $2, $4 × $5 пиксел, $3", + "timedmedia-ogg-long-multiplexed": "Ogg құрамды дыбыс/бейне файлы, $1, ұзақтығы $2, $4 × $5 пиксел, $3 не барлығы", + "timedmedia-ogg-long-general": "Ogg таспа файлы, ұзақтығы $2, $3", + "timedmedia-ogg-long-error": "Жарамсыз ogg файлы: $1", + "timedmedia-webm-short-video": "WebM $1 видео файл, $2", + "timedmedia-flac-short-audio": "FLAC аудио файл, $1", + "timedmedia-flac-long-audio": "FLAC аудио файл, ұзындығы $1, битрейт — $2", + "timedmedia-wav-short-audio": "WAV аудио файл, $1", + "timedmedia-wav-long-audio": "WAV аудио файл, $1 ұзындығы $1, битрейт — $2", + "timedmedia-mp4-short-video": "MP4 $1 видео файл, $2", + "timedmedia-more": "Көбірек...", + "timedmedia-dismiss": "Жабу", + "timedmedia-download": "Файлды жүктеу", + "timedmedia-play-media": "Медианы көрсету", + "timedmedia-desc-link": "Бұл файл туралы", + "timedmedia-status": "Статусы", + "timedmedia-status-unknown": "Белгісіз статус", + "timedmedia-actions": "Әрекеттер", + "timedmedia-not-ready": "Дайын емес", + "timedmedia-days": "{{PLURAL:$1|1 күн|$1 күн}}", + "timedmedia-hours": "{{PLURAL:$1|бір сағат|$1 сағат}}", + "timedmedia-minutes": "{{PLURAL:$1|1 минут|$1 минут}}", + "timedmedia-seconds": "{{PLURAL:$1|$ секунт|секунт}}", + "timedmedia-source-file": "$1 қайнар", + "timedmedia-derivative-desc-160p.ogv": "Төменгі сапалы Ogg видео (160P)", + "timedmedia-derivative-desc-720p.ogv": "Жоғары сападағы жүктелінетін Ogg видео (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Толық HD жұктелінетін Ogg видео (1080P)", + "timedmedia-derivative-desc-720p.webm": "Жоғары сападағы жүктелінетін WebM (720P)", + "timedmedia-derivative-desc-1080p.webm": "Толық HD жұктелінетін WebM (1080P)", + "timedmedia-derivative-desc-720p.mp4": "HD сапа MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "Толық HD сапа MP4 (1080P)", + "timedmedia-subtitle-new": "Жаңа аударма бастау немесе барын өңдеу", + "timedmedia-subtitle-new-desc": "Тілді таңдаңыз және '''{{int:Timedmedia-subtitle-new-go}}''' батырмасын басыңыз", + "timedmedia-subtitle-new-go": "Өту", + "timedmedia-subtitle-language": "$1 ($2) субтитрлер", + "timedmedia-videos": "{{PLURAL:$1|$1 видео|$1 видеолар}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg видео|$1 Ogg видеолар}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM видео|$1 WebM видеолар}}", + "timedmedia-file": "Файл", + "timedmedia-audios": "{{PLURAL:$1|$1 аудио файл|$1 аудио файлдар}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg аудио файл|$1 Ogg аудио файлдар}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC аудио файл|$1 FLAC аудио файлдар}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV аудио файл|$1 WAV аудио файлдар}}", + "right-transcode-status": "[[Special:TimedMediaHandler|information about the current transcode activity]] көру", + "apihelp-transcodereset-param-title": "Медиа файл тақырыбы." +} diff --git a/extensions/TimedMediaHandler/i18n/kk-latn.json b/extensions/TimedMediaHandler/i18n/kk-latn.json new file mode 100644 index 00000000..ef140659 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/kk-latn.json @@ -0,0 +1,15 @@ +{ + "@metadata": [], + "timedmedia-ogg-short-audio": "Ogg $1 dıbıs faýlı, $2", + "timedmedia-ogg-short-video": "Ogg $1 beýne faýlı, $2", + "timedmedia-ogg-short-general": "Ogg $1 taspa faýlı, $2", + "timedmedia-ogg-long-audio": "Ogg $1 dıbıs faýlı, uzaqtığı $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 beýne faýlı, uzaqtığı $2, $4 × $5 pïksel, $3", + "timedmedia-ogg-long-multiplexed": "Ogg quramdı dıbıs/beýne faýlı, $1, uzaqtığı $2, $4 × $5 pïksel, $3 ne barlığı", + "timedmedia-ogg-long-general": "Ogg taspa faýlı, uzaqtığı $2, $3", + "timedmedia-ogg-long-error": "Jaramsız ogg faýlı: $1", + "timedmedia-more": "Köbirek...", + "timedmedia-dismiss": "Jabw", + "timedmedia-download": "Faýldı jüktew", + "timedmedia-desc-link": "Bul faýl twralı" +} diff --git a/extensions/TimedMediaHandler/i18n/km.json b/extensions/TimedMediaHandler/i18n/km.json new file mode 100644 index 00000000..47cf1d5e --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/km.json @@ -0,0 +1,24 @@ +{ + "@metadata": { + "authors": [ + "Chhorran", + "Lovekhmer", + "T-Rithy", + "Thearith", + "គីមស៊្រុន" + ] + }, + "timedmedia-desc": "គាំទ្រចំពោះ Ogg Theora និង Vorbis files, ជាមួយ ឧបករណ៍អាន JavaScript", + "timedmedia-ogg-short-audio": "ឯកសារ សំឡេង Ogg $1, $2", + "timedmedia-ogg-short-video": "ឯកសារវីដេអូ Ogg $1, $2", + "timedmedia-ogg-short-general": "ឯកសារមេឌាOgg $1, $2", + "timedmedia-ogg-long-audio": "ឯកសារសំឡេងប្រភេទOgg $1, រយៈពេល$2 និងទំហំ$3", + "timedmedia-ogg-long-video": "ឯកសារវីដេអូប្រភេទOgg $1, រយៈពេល$2, $4×$5px, $3", + "timedmedia-ogg-long-multiplexed": "ឯកសារអូឌីយ៉ូ/វីដេអូចម្រុះប្រភេទOgg , $1, រយៈពេល$2, $4×$5px, ប្រហែល$3", + "timedmedia-ogg-long-general": "ឯកសារមេឌាប្រភេទOgg, រយៈពេល$2, $3", + "timedmedia-ogg-long-error": "ឯកសារ ogg មិនមាន សុពលភាព ៖ $1", + "timedmedia-more": "បន្ថែម...", + "timedmedia-dismiss": "បិទ", + "timedmedia-download": "ទាញយកឯកសារ", + "timedmedia-desc-link": "អំពីឯកសារនេះ" +} diff --git a/extensions/TimedMediaHandler/i18n/kn.json b/extensions/TimedMediaHandler/i18n/kn.json new file mode 100644 index 00000000..4b39e37d --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/kn.json @@ -0,0 +1,17 @@ +{ + "@metadata": { + "authors": [ + "VASANTH S.N.", + "Omshivaprakash" + ] + }, + "timedmedia-more": "ಹೆಚ್ಚು...", + "timedmedia-dismiss": "ಮುಚ್ಚಿ", + "timedmedia-desc-link": "ಈ ಆಟದ ಬಗ್ಗೆ", + "timedmedia-status": "ಸ್ಥಾನಮಾನ", + "timedmedia-actions": "ಕ್ರಿಯೆಗಳು", + "timedmedia-source-file": "$1 ಮೂಲ", + "timedmedia-source-audio-file-desc": "ಅಸಲು $1 ಕಡತ ($2)", + "timedmedia-subtitle-new-go": "ಹೋಗು", + "timedmedia-file": "ಕಡತ" +} diff --git a/extensions/TimedMediaHandler/i18n/ko.json b/extensions/TimedMediaHandler/i18n/ko.json new file mode 100644 index 00000000..b0d45e53 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ko.json @@ -0,0 +1,104 @@ +{ + "@metadata": { + "authors": [ + "ITurtle", + "Kwj2772", + "Priviet", + "ToePeu", + "아라", + "Hym411", + "Revi", + "IRTC1015", + "Hwangjy9", + "Kurousagi" + ] + }, + "timedmedia-desc": "WebM, OGG Theora, Vorbis, srt를 위한 포맷 지원과 소리, 동영상과 자막을 위한 핸들러", + "timedmedia-ogg-short-audio": "Ogg $1 소리 파일, $2", + "timedmedia-ogg-short-video": "Ogg $1 동영상 파일, $2", + "timedmedia-ogg-short-general": "Ogg $1 미디어 파일, $2", + "timedmedia-ogg-long-audio": "Ogg $1 소리 파일, 길이 $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 동영상 파일, 길이 $2, $4 × $5 픽셀, $3", + "timedmedia-ogg-long-multiplexed": "Ogg 다중 소리/동영상 파일, $1, 길이 $2, $4 × $5 픽셀, 대략 $3", + "timedmedia-ogg-long-general": "Ogg 미디어 파일, 길이 $2, $3", + "timedmedia-ogg-long-error": "잘못된 Ogg 파일: $1", + "timedmedia-ogg-long-no-streams": "Ogg 미디어 파일입니다. 경고: 이 파일에 사용된 코덱을 알 수 없습니다.", + "timedmedia-webm-short-video": "WebM $1 동영상 파일, $2", + "timedmedia-webm-long-video": "WebM 소리/동영상 파일, $1, 길이 $2, $4 × $5픽셀, 대략 $3", + "timedmedia-flac-short-audio": "FLAC 소리 파일, $1", + "timedmedia-flac-long-audio": "FLAC 소리 파일, 길이 $1, 대략 $2", + "timedmedia-wav-short-audio": "WAV 소리 파일, $1", + "timedmedia-wav-long-audio": "WAV 소리 파일, 길이 $1, 대략 $2", + "timedmedia-wav-pcm-required": "PCM(펄스 부호 변조) WAV만 올릴 수 있습니다.", + "timedmedia-mp4-short-video": "MP4 $1 동영상 파일, $2", + "timedmedia-mp4-long-video": "MP4 소리/동영상 파일, $1, 길이 $2, $4 × $5픽셀, 대략 $3", + "timedmedia-no-player-js": "죄송합니다, 시스템은 자바스크립트를 지원하지 않거나 지원하는 미디어 플레이어가 설치되어 있지 않습니다.
\n미디어 클립을 다운로드하거나, 미디어 플레이어를 다운로드할 수 있습니다.", + "timedmedia-more": "더 보기…", + "timedmedia-dismiss": "닫기", + "timedmedia-download": "파일 다운로드", + "timedmedia-play-media": "미디어 재생", + "timedmedia-desc-link": "이 파일에 대한 정보", + "timedmedia-oggThumb-version": "OggHandler는 oggThumb 버전 $1 이상이 필요합니다.", + "timedmedia-oggThumb-failed": "oggThumb가 섬네일을 만들지 못했습니다.", + "timedmedia-status-header": "코드 변환 상태", + "timedmedia-update-status": "코드 변환 상태 업데이트", + "timedmedia-status": "상태", + "timedmedia-status-unknown": "알 수 없는 상태", + "timedmedia-transcodeinfo": "형식", + "timedmedia-actions": "동작", + "timedmedia-direct-link": "다운로드", + "timedmedia-not-ready": "준비하지 않음", + "timedmedia-completed-on": "$1 완료", + "timedmedia-error-on": "$1에 오류", + "timedmedia-started-transcode": "$1 전에 시작되었습니다. $2", + "timedmedia-percent-done": "약 $1% 완료", + "timedmedia-in-job-queue": "$1 전에 작업 대기열에 추가됨", + "timedmedia-unknown-target-size": "알 수 없는 대상 크기, $1 인코드함", + "timedmedia-days": "{{PLURAL:$1|1일|$1일}}", + "timedmedia-hours": "{{PLURAL:$1|1시간|$1시간}}", + "timedmedia-minutes": "{{PLURAL:$1|1분|$1분}}", + "timedmedia-seconds": "{{PLURAL:$1|1초|$1초}}", + "timedmedia-reset": "코드 변환 초기화", + "timedmedia-reset-confirm": "코드 변환을 초기화하면 기존 파일이 (있는 경우) 제거되며, 작업 대기열로 코드 변환을 다시 추가합니다. 다시 변환하는 데 다소 시간이 걸릴 것입니다.

\n계속하겠습니까?", + "timedmedia-reset-error": "코드 변환 작업을 초기화하는 데 오류가 났습니다.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 원본", + "timedmedia-source-file-desc": "원본 $1 파일, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "원본 $1 파일 ($2)", + "timedmedia-derivative-desc-160p.ogv": "낮은 대역폭 Ogg 비디오 (160P)", + "timedmedia-derivative-desc-240p.ogv": "웹 스트리밍 가능 Ogg 동영상 (240P)", + "timedmedia-derivative-desc-360p.ogv": "웹 스트리밍 가능 Ogg 동영상 (360P)", + "timedmedia-derivative-desc-480p.ogv": "웹 스트리밍 가능 Ogg 동영상 (480P)", + "timedmedia-derivative-desc-720p.ogv": "고화질 다운로드 가능 Ogg 동영상 (720P)", + "timedmedia-derivative-desc-160p.webm": "웹 스트리밍 가능 WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "웹 스트리밍 가능 WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "웹 스트리밍 가능 WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "고화질 다운로드 가능 WebM (720P)", + "timedmedia-derivative-desc-320p.mp4": "장치 친화적 MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "웹 스트리밍 가능 MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "HD 품질 MP4 (720P)", + "timedmedia-subtitle-new": "새 번역 만들기 또는 기존 번역 편집", + "timedmedia-subtitle-new-desc": "언어를 선택하고 '''{{int:Timedmedia-subtitle-new-go}}''' 버튼을 누르세요", + "timedmedia-subtitle-new-go": "가기", + "timedmedia-subtitle-language": "$1 ($2) 자막", + "timedmedia-subtitle-no-video": "현재 자막 페이지와 관련된 동영상이 없습니다", + "timedmedia-subtitle-no-subtitles": "현재 이 동영상에 대한 $1에 자막이 없습니다, 추가하려면 [{{fullurl:{{FULLPAGENAME}}|action=edit}} 이 문서를 편집할] 수 있습니다.", + "timedmedia-subtitle-remote": "이 파일에 대한 자막은 $1에 호스트했습니다", + "timedmedia-subtitle-remote-link": "$2에 이 파일에 대한 [$1 설명 문서를 볼] 수 있습니다", + "timedmediahandler": "자막미디어핸들러", + "timedmedia-videos": "{{PLURAL:$1|동영상 $1개}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|Ogg 동영상 $1개}}", + "timedmedia-webm-videos": "{{PLURAL:$1|WebM 동영상 $1개}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|코드 변환 $1개}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|실행 중인 코드 변환 $1개}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|대기 중인 코드 변환 $1개}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|실패한 코드 변환 $1개}}", + "timedmedia-file": "파일", + "timedmedia-audios": "{{PLURAL:$1|오디오 파일 $1개}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|Ogg 오디오 파일 $1개}}", + "timedmedia-flac-audios": "{{PLURAL:$1|FLAC 오디오 파일 $1개}}", + "timedmedia-wav-audios": "{{PLURAL:$1|WAV 오디오 파일 $1개}}", + "right-transcode-reset": "변환됐거나 실패한 비디오를 작업 대기열에 다시 추가되도록 초기화", + "right-transcode-status": "[[Special:TimedMediaHandler|현재 코드 변환 활동에 대한 정보]] 보기", + "action-transcode-status": "현재 코드 변환 상태 보기" +} diff --git a/extensions/TimedMediaHandler/i18n/krc.json b/extensions/TimedMediaHandler/i18n/krc.json new file mode 100644 index 00000000..e27d9595 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/krc.json @@ -0,0 +1,11 @@ +{ + "@metadata": { + "authors": [ + "Iltever" + ] + }, + "timedmedia-no-player-js": "Джарсыугъа, сизни браузеригизде JavaScript джукъланыбды неда керекли ойнатыучу джокъду.
\nСиз клипни эндирирге неда клипни браузерде ойнатыр ючюн ойнатыучу эндирирге боллукъсуз.", + "timedmedia-more": "Энтда…", + "timedmedia-source-file": "$1 къайнагъы", + "timedmedia-source-audio-file-desc": "Оригинал $1-файл ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/krj.json b/extensions/TimedMediaHandler/i18n/krj.json new file mode 100644 index 00000000..32fffc84 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/krj.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Jose77" + ] + }, + "timedmedia-more": "Raku pa..." +} diff --git a/extensions/TimedMediaHandler/i18n/ksh.json b/extensions/TimedMediaHandler/i18n/ksh.json new file mode 100644 index 00000000..0dacd011 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ksh.json @@ -0,0 +1,81 @@ +{ + "@metadata": { + "authors": [ + "Purodha" + ] + }, + "timedmedia-desc": "E Projamm (handler) för Meedije met Zickaandeil\n— Viddejos, Tondatteieje, timedText (Ongertittelle) —\nmet Ongershtözung för de Fommaate WebM, Ogg Theora, Vorbis un srt.", + "timedmedia-ogg-short-audio": "Ogg $1 Tondatei, $2", + "timedmedia-ogg-short-video": "Ogg $1 Viddejodatei, $2", + "timedmedia-ogg-short-general": "Ogg $1 Medijedatei, $2", + "timedmedia-ogg-long-audio": "Ogg $1 Tondatei fum Ömfang $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 Viddejodatei fum Ömfang $2 un {{PLURAL:$4|ein Pixel|$4 Pixelle|kei Pixel}} × {{PLURAL:$5|ei Pixel|$4 Pixelle|kei Pixel}}, $3", + "timedmedia-ogg-long-multiplexed": "Ogg jemultipex Ton- un Viddejodatei, $1, fum Ömfang $2 un {{PLURAL:$4|ein Pixel|$4 Pixelle|kei Pixel}} × {{PLURAL:$5|ei Pixel|$4 Pixelle|kei Pixel}}, $3 ennsjesammp", + "timedmedia-ogg-long-general": "Ogg Medijedatei fum Ömfang $2, $3", + "timedmedia-ogg-long-error": "En kapodde Ogg-Dattei: $1", + "timedmedia-webm-short-video": "En $1 Viddejo-Dattei em WebM-Fommaat vun $2", + "timedmedia-webm-long-video": "Ene Viddejo udder en Toondattei em WebM-Fommaat, $1, $2 jruuß, $4 × $5 Pixelle, $3 enßjesammp", + "timedmedia-no-player-js": "Schad, Dinge Brauser hät entweder JavaSkrepp ußjeschalldt udder kein zopaß Projramm zom Afschpelle.
Do kanns jäz dat Stöck eronger laade udder e Afspeller-Projramm eronger laade, öm dat Schtök en Dingem Brauser afzeschpelle.", + "timedmedia-more": "Enshtelle …", + "timedmedia-dismiss": "Zohmaache!", + "timedmedia-download": "Dattei eronger lahde", + "timedmedia-play-media": "Afshpelle", + "timedmedia-desc-link": "Övver di Dattei", + "timedmedia-oggThumb-version": "Dä OggHandler bruch oggThumb in dä Version $1 udder hüüter.", + "timedmedia-oggThumb-failed": "oggThumb kunnt kei MiniBelldsche maache.", + "timedmedia-status": "Zohschtand", + "timedmedia-status-unknown": "Onbikannte Zohschtand", + "timedmedia-direct-link": "Afflähjer eronger lahde", + "timedmedia-not-ready": "Nit parraht", + "timedmedia-started-transcode": "De Ömwandlong hät vör $1 bejonne. $2 sin jedonn.", + "timedmedia-percent-done": "öm de $1% jedonn", + "timedmedia-in-job-queue": "En de Waadeschlang met de Aufjahbe jedonn vör $1", + "timedmedia-days": "{{PLURAL:$1|einem Daach|$1 Dääsch|keinem Daach}}", + "timedmedia-hours": "{{PLURAL:$1|eine Schtond|$1 Schtonde|keinem Schtond}}", + "timedmedia-minutes": "{{PLURAL:$1|eine Menutt|$1 Menutte|keine Menutt}}", + "timedmedia-seconds": "{{PLURAL:$1|eine Sekund|$1 Sekunde|keine Sekund}}", + "timedmedia-ogg": "Ogg", + "timedmedia-webm": "WebM", + "timedmedia-mp4": "MP4", + "timedmedia-wav": "WAV", + "timedmedia-flac": "FLAC", + "timedmedia-source-file": "Quell-Dattei em $1-Fommaat", + "timedmedia-source-file-desc": "Ojinahl $1-Dattei, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Ojinahl $1-Dattei ($2)", + "timedmedia-derivative-160p.ogv": "Ogg 160P", + "timedmedia-derivative-desc-160p.ogv": "En Viddejo-Dattei met winnesch Bandbreide (160P) em Ogg-Fommaat", + "timedmedia-derivative-360p.ogv": "Ogg 360P", + "timedmedia-derivative-desc-360p.ogv": "En Viddejo-Dattei em Ogg-Fommaat (360p) för ene Dahteshtrohm övver et Nez", + "timedmedia-derivative-480p.ogv": "Ogg 480P", + "timedmedia-derivative-desc-480p.ogv": "En Viddejo-Dattei (met 480p) em Ogg-Fommaat för ene Dahteshtrohm övver et Nez", + "timedmedia-derivative-720p.ogv": "Ogg 720P", + "timedmedia-derivative-desc-720p.ogv": "En Viddejo-Dattei met huhe Qualiteit (met 720p) em Ogg-Fommaat zom eronger laade", + "timedmedia-derivative-1080p.ogv": "Ogg 1080P", + "timedmedia-derivative-desc-1080p.ogv": "Full HD downloadable Ogg video (1080P)", + "timedmedia-derivative-160p.webm": "WebM 160P", + "timedmedia-derivative-desc-160p.webm": "Web streamable WebM (160P)", + "timedmedia-derivative-360p.webm": "WebM 360P", + "timedmedia-derivative-desc-360p.webm": "Ene WebM Viddejo (met 360p) för ene Dahteshtrohm övver et Nez", + "timedmedia-derivative-480p.webm": "WebM 480P", + "timedmedia-derivative-desc-480p.webm": "Ene WebM Viddejo (met 480p) för ene Dahteshtrohm övver et Nez", + "timedmedia-derivative-720p.webm": "WebM 720P", + "timedmedia-derivative-desc-720p.webm": "En WebM Viddejo-Dattei met huhe Qualiteit (met 720p) zom eronger laade", + "timedmedia-derivative-1080p.webm": "WebM 1080P", + "timedmedia-derivative-desc-1080p.webm": "Full HD downloadable WebM (1080P)", + "timedmedia-derivative-desc-320p.mp4": "Device-friendly MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "Web streamable MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "HD quality MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "Full HD quality MP4 (1080P)", + "timedmedia-derivative-mp3": "MP3", + "timedmedia-derivative-desc-mp3": "MP3", + "timedmedia-subtitle-new": "Maach en neue Övversäzong udder änder ein, di et alld jitt", + "timedmedia-subtitle-new-desc": "Söhg en Schprohch uß un klegg op „{{int:Timedmedia-subtitle-new-go}}“", + "timedmedia-subtitle-new-go": "Lohß Jonn!", + "timedmedia-subtitle-language": "Ongertittele en $1 ($2)", + "timedmedia-file": "Dattei", + "timedmedia-audios": "{{PLURAL:$1|Ein Tohndattei|$1 Tohndatteije|kein Tohndattei}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|Ein Tohndattei|$1 Tohndatteije|kein Tohndattei}} em Ogg-Fommaht", + "timedmedia-flac-audios": "{{PLURAL:$1|Ein Tohndattei|$1 Tohndatteije|kein Tohndattei}} em FLAC-Fommaht", + "timedmedia-wav-audios": "{{PLURAL:$1|Ein Tohndattei|$1 Tohndatteije|kein Tohndattei}} em WAV-Fommaht", + "apihelp-transcodereset-param-title": "De Övverschreff vun dä Mehdijedattei." +} diff --git a/extensions/TimedMediaHandler/i18n/ku-latn.json b/extensions/TimedMediaHandler/i18n/ku-latn.json new file mode 100644 index 00000000..48f4f644 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ku-latn.json @@ -0,0 +1,14 @@ +{ + "@metadata": { + "authors": [ + "Bikarhêner", + "George Animal" + ] + }, + "timedmedia-more": "Zêdetir...", + "timedmedia-dismiss": "Bigre", + "timedmedia-download": "Dosyeyê daxe", + "timedmedia-desc-link": "Di derbarê vê dosyeyê de", + "timedmedia-source-file": "çavkanî $1", + "timedmedia-source-audio-file-desc": "Dosyeye orijînal a $1 ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/ky.json b/extensions/TimedMediaHandler/i18n/ky.json new file mode 100644 index 00000000..79dce232 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ky.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Janatkg" + ] + }, + "timedmedia-source-file": "$1 булактан" +} diff --git a/extensions/TimedMediaHandler/i18n/la.json b/extensions/TimedMediaHandler/i18n/la.json new file mode 100644 index 00000000..ae42c49c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/la.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "SPQRobin", + "UV" + ] + }, + "timedmedia-more": "Plus…" +} diff --git a/extensions/TimedMediaHandler/i18n/lad.json b/extensions/TimedMediaHandler/i18n/lad.json new file mode 100644 index 00000000..082e7dcb --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/lad.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Menachem.Moreira", + "Universal Life" + ] + }, + "timedmedia-subtitle-new-go": "Ir", + "timedmedia-file": "Dosya" +} diff --git a/extensions/TimedMediaHandler/i18n/lb.json b/extensions/TimedMediaHandler/i18n/lb.json new file mode 100644 index 00000000..0a3c78c8 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/lb.json @@ -0,0 +1,73 @@ +{ + "@metadata": { + "authors": [ + "Les Meloures", + "Robby" + ] + }, + "timedmedia-desc": "Steierungsprogramm fir Audio, Video an 'timed text', mat Ënnerstëtzung vun de Formater WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg-$1-Tounfichier, $2", + "timedmedia-ogg-short-video": "Ogg-$1-Videofichier, $2", + "timedmedia-ogg-short-general": "Ogg-$1-Mediefichier, $2", + "timedmedia-ogg-long-audio": "tmh-$1-Tounfichier, Dauer: $2, $3", + "timedmedia-ogg-long-video": "Ogg-$1-Videofichier, Dauer: $2, $4×$5 Pixel, $3", + "timedmedia-ogg-long-multiplexed": "Ogg-Toun-/Video-Fichier, $1, Dauer: $2, $4×$5 Pixel, $3", + "timedmedia-ogg-long-general": "Ogg Media-Fichier, Dauer $2, $3", + "timedmedia-ogg-long-error": "Ongëltegen Ogg-Fichier: $1", + "timedmedia-webm-short-video": "WebM $1 Video Fichier, $2", + "timedmedia-webm-long-video": "WebM-Audio/Video-Fichier, $1, Dauer $2, $4×$5 Pixel, $3 am Ganzen", + "timedmedia-flac-short-audio": "FLAC-Audiofichier, $1", + "timedmedia-flac-long-audio": "FLAC-audiofichier, Längt $1, $2 am Ganzen", + "timedmedia-wav-short-audio": "WAV-Audiofichier, $1", + "timedmedia-mp4-short-video": "MP4 $1 Videofichier, $2", + "timedmedia-no-player-js": "Pardon, Äre Browser huet entweder JavaScript ausgeschalt oder en huet kee Player-Programm deen ënnerstëtzt gëtt.
\nDir kënnt de Clip eroflueden oder e Player-Programm erofluede fir de Clip an Ärem Browser ze spillen.", + "timedmedia-more": "Méi ...", + "timedmedia-dismiss": "Zoumaachen", + "timedmedia-download": "Fichier eroflueden", + "timedmedia-desc-link": "Iwwer dëse Fichier", + "timedmedia-oggThumb-version": "Den OggHandler brauch d'Versioun $1 (oder méi eng nei Versioun) vun OggThumb.", + "timedmedia-oggThumb-failed": "oggThumb konnt d'Miniaturbild (thumbnail) net uleeën.", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Onbekannte Status", + "timedmedia-transcodeinfo": "Format", + "timedmedia-actions": "Aktiounen", + "timedmedia-direct-link": "Eroflueden", + "timedmedia-not-ready": "Net fäerdeg", + "timedmedia-completed-on": "Fäerdeg den $1", + "timedmedia-error-on": "Feeler op $1", + "timedmedia-started-transcode": "Ugefaang viru(n) $1. $2", + "timedmedia-percent-done": "Ongeféier $1% fäerdeg", + "timedmedia-days": "{{PLURAL:$1|1 Dag|$1 Deeg}}", + "timedmedia-hours": "{{PLURAL:$1|1 Stonn|$1 Stonnen}}", + "timedmedia-minutes": "{{PLURAL:$1|1 Minutt|$1 Minutten}}", + "timedmedia-seconds": "{{PLURAL:$1|1 Sekonn|$1 Sekonnen}}", + "timedmedia-reset": "Transcode zrécksetzen", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Quell ($1)", + "timedmedia-source-file-desc": "Original $1 Fichier, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Original $1 Fichier ($2)", + "timedmedia-derivative-desc-240p.ogv": "Ogg-Video (240P) deen iwwer de Web gestreamt gi kann", + "timedmedia-derivative-360p.vp9.webm": "VP9 360P", + "timedmedia-derivative-480p.vp9.webm": "VP9 480P", + "timedmedia-derivative-desc-2160p.vp9.webm": "Komplette streamfäege 4K-WebM-VP9-Video (2160P)", + "timedmedia-derivative-desc-720p.mp4": "HD-Qualitéit MP4 (720P)", + "timedmedia-derivative-desc-2160p.mp4": "Komplette 4K MP4-Qualitéitsvideo (2160P)", + "timedmedia-subtitle-new": "Nei Iwwersetzung maachen oder eng déi et gëtt änneren", + "timedmedia-subtitle-new-desc": "Sicht d'Sprooch eraus an dréckt de Knäppchen ''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Lass", + "timedmedia-subtitle-language": "$1 ($2) Ënnertitelen", + "timedmedia-subtitle-no-video": "Et ass kee Video mat der aktueller Säit vun Ënnertitelen associéiert.", + "timedmedia-subtitle-no-subtitles": "Et sinn elo keng Ënnertitelen op $1 fir dëse Video, Dir kënnt [{{fullurl:{{FULLPAGENAME}}|action=edit}} dës Säit änneren] fir se derbäizesetzen", + "timedmedia-subtitle-remote-link": "Dir kënnt [$1 d'Beschreiwungssäit] fir dëse Fichier op $2 gesinn", + "timedmedia-videos": "{{PLURAL:$1|$1 Video|$1 Videoen}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg Video|$1 Ogg Videoen}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM Video|$1 WebM Videoen}}", + "timedmedia-file": "Fichier", + "timedmedia-audios": "{{PLURAL:$1|$1 Audio-Fichier|$1 Audio-Fichieren}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg Audio-Fichier|$1 Ogg Audio-Fichieren}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC Audio-Fichier|$1 FLAC Audio-Fichieren}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV Audio-Fichier|$1 WAV Audio-Fichieren}}", + "orphanedtimedtext-unsupported": "Dës Spezialsäit funktionéiert nëmme mat MySQL Datebanken.", + "orphanedtimedtext-notimedtext": "TimedText ass op dëser Wiki net aktivéiert.", + "apihelp-transcodereset-param-title": "Den Titel vum Mediefichier." +} diff --git a/extensions/TimedMediaHandler/i18n/lfn.json b/extensions/TimedMediaHandler/i18n/lfn.json new file mode 100644 index 00000000..23c9c268 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/lfn.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Malafaya" + ] + }, + "timedmedia-more": "Plu…" +} diff --git a/extensions/TimedMediaHandler/i18n/li.json b/extensions/TimedMediaHandler/i18n/li.json new file mode 100644 index 00000000..2f63a4cd --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/li.json @@ -0,0 +1,21 @@ +{ + "@metadata": { + "authors": [ + "Matthias", + "Ooswesthoesbes" + ] + }, + "timedmedia-desc": "Handelt Ogg Theora- en Vorbis-bestande aaf met 'n JavaScript-mediaspeler", + "timedmedia-ogg-short-audio": "Ogg $1 geluidsbestandj, $2", + "timedmedia-ogg-short-video": "Ogg $1 videobestandj, $2", + "timedmedia-ogg-short-general": "Ogg $1 mediabestandj, $2", + "timedmedia-ogg-long-audio": "Ogg $1 geluidsbestandj, lingdje $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 videobestandj, lingdje $2, $4×$5 pixels, $3", + "timedmedia-ogg-long-multiplexed": "Ogg gemultiplexeerd geluids-/videobestandj, $1, lingdje $2, $4×$5 pixels, $3 totaal", + "timedmedia-ogg-long-general": "Ogg mediabestandj, lingdje $2, $3", + "timedmedia-ogg-long-error": "Óngeljig oggg-bestandj: $1", + "timedmedia-more": "Mieë...", + "timedmedia-dismiss": "Sloet", + "timedmedia-download": "Bestandj downloade", + "timedmedia-desc-link": "Euver dit bestandj" +} diff --git a/extensions/TimedMediaHandler/i18n/lrc.json b/extensions/TimedMediaHandler/i18n/lrc.json new file mode 100644 index 00000000..b897768a --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/lrc.json @@ -0,0 +1,44 @@ +{ + "@metadata": { + "authors": [ + "Bonevarluri", + "Mogoeilor" + ] + }, + "timedmedia-ogg-short-audio": "جانیا دنگ اوگ $1،$2", + "timedmedia-ogg-short-video": "Ogg $1 جانیا ویدیو, $2", + "timedmedia-ogg-short-general": "Ogg $1 جانیا وارسگر , $2", + "timedmedia-ogg-long-audio": "Ogg $1 جانیا دنگ , درازا $2, $3", + "timedmedia-ogg-long-error": "جانیا دنگ اوگ نامعتور:$1", + "timedmedia-webm-short-video": "WebM $1 جانیا ویدیو, $2", + "timedmedia-flac-short-audio": "FLAC جانیا دنگدار , $1", + "timedmedia-wav-short-audio": "WAV جانیا دنگدار, $1", + "timedmedia-wav-pcm-required": "شما فقط تونیت پی سی ام نه سوار بکید", + "timedmedia-mp4-short-video": "MP4 $1 جانیا ویدیو, $2", + "timedmedia-no-player-js": "د بدبختی دوارته نیئر د لحاظ جاوا نیسسه ناکشتگر بیه یا شایت حامیندار هیچ دنگیاری نئ.
\nشما می تونیتکلیپ نه بگریت یایه گل دنگیار نه بگریت سی یه که کلیپ نه د دوارته نیئرتو به ونیت..", + "timedmedia-more": "بیشتر....", + "timedmedia-dismiss": "بسن", + "timedmedia-download": "گرتن جانیا", + "timedmedia-play-media": "روشن کردن وارسگر", + "timedmedia-desc-link": "دباره ای جانیا", + "timedmedia-status": "حال وبال", + "timedmedia-status-unknown": "حال و بال نادیار", + "timedmedia-actions": "کنشگریا", + "timedmedia-not-ready": "آماده نئ", + "timedmedia-percent-done": "د حدود $1% انجوم بی", + "timedmedia-days": "{{PLURAL:$1|1 روز|$1 روز}}", + "timedmedia-hours": "{{PLURAL:$1|$1 ساعت|$1 ساعت یا}}", + "timedmedia-minutes": "{{PLURAL:$1|$1 دیقه|$1 دیقه یا}}", + "timedmedia-seconds": "{{PLURAL:$1|$1 ثانیه|$1 ثانیه یا}}", + "timedmedia-source-file": "سرچشمه $1", + "timedmedia-source-file-desc": "اصلی $1 جانیا, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "جانیا اصلی$1 ($2)", + "timedmedia-subtitle-new-go": "رؤ", + "timedmedia-subtitle-language": "$1 ($2) زیرنیس", + "timedmedia-videos": "{{PLURAL:$1|$1 ویدیو|$1 ویدیویا}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg ویدیو|$1 Ogg ویدیویا}}", + "timedmedia-file": "جانیا", + "timedmedia-audios": "{{PLURAL:$1|$1 جانیا دنگدار|$1 جانیایا دنگدار}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 جانیا دنگ نگدار اُگ|$1 جانیایا دنگدار اُگ}}", + "apihelp-transcodereset-param-title": "داسون جانیا وارسگر." +} diff --git a/extensions/TimedMediaHandler/i18n/lt.json b/extensions/TimedMediaHandler/i18n/lt.json new file mode 100644 index 00000000..bee1ed8d --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/lt.json @@ -0,0 +1,123 @@ +{ + "@metadata": { + "authors": [ + "Homo", + "Matasg", + "Hugo.arg", + "Albertas" + ] + }, + "timedmedia-desc": "Audio, video ir teksto su laiku doroklė, kuri palaiko WebM, Ogg Theora, Vorbis, srt formatus", + "timedmedia-ogg-short-audio": "Ogg $1 garso byla, $2", + "timedmedia-ogg-short-video": "Ogg $1 video byla, $2", + "timedmedia-ogg-short-general": "Ogg $1 medija byla, $2", + "timedmedia-ogg-long-audio": "Ogg $1 garso byla, ilgis $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 video byla, ilgis $2, $4×$5 pikseliai, $3", + "timedmedia-ogg-long-multiplexed": "Ogg sutankinta audio/video byla, $1, ilgis $2, $4×$5 pikseliai, $3 viso", + "timedmedia-ogg-long-general": "Ogg media byla, ilgis $2, $3", + "timedmedia-ogg-long-error": "Bloga ogg byla: $1", + "timedmedia-ogg-long-no-streams": "Ogg medijos failas. Įspėjimas: nei vienas faile naudojamas kodavimo standartas nebuvo atpažintas.", + "timedmedia-webm-short-video": "WebM $1 video failas, $2", + "timedmedia-webm-long-video": "WebM audio/video failas, $1, ilgio $2, $4 × $5 pikselių, $3 išviso", + "timedmedia-flac-short-audio": "FLAC audio failas, $1", + "timedmedia-flac-long-audio": "FLAC audio failas, ilgio $1, $2 iš viso", + "timedmedia-wav-short-audio": "WAV audio failas, $1", + "timedmedia-wav-long-audio": "WAV audio failas, ilgio $1, $2 iš viso", + "timedmedia-wav-pcm-required": "Galite įkelti tik PCM (Pulse Code Modulation) WAV.", + "timedmedia-mp4-short-video": "MP4 $1 video failas, $2", + "timedmedia-mp4-long-video": "MP4 audio/video failas, $1, ilgio $2, $4 × $5 pikselių, $3 iš viso", + "timedmedia-no-player-js": "Atsiprašome, bet jūsų naršyklėje arba išjungtas JavaScript, arba neturi jokio palaikomo grotuvo.
\nJūs galite atsisiųsti įrašą arba grotuvą, kad galėtumėte leisti įrašą savo naršyklėje.", + "timedmedia-more": "Daugiau...", + "timedmedia-dismiss": "Uždaryti", + "timedmedia-download": "Atsisiųsti bylą", + "timedmedia-play-media": "Groti mediją", + "timedmedia-desc-link": "Apie šią bylą", + "timedmedia-oggThumb-version": "OggHandler reikalauja oggThumb $1 versijos arba vėlesnės.", + "timedmedia-oggThumb-failed": "oggThumb nepavyko sukurti miniatiūros.", + "timedmedia-status-header": "Perkodavimo statusas", + "timedmedia-update-status": "Atnaujinti perkodavimo statusą", + "timedmedia-status": "Būsena", + "timedmedia-status-unknown": "Nežinomas statusas", + "timedmedia-transcodebitrate": "Kokybė", + "timedmedia-transcodeduration": "Kodavimo laikas", + "timedmedia-transcodeinfo": "Formatas", + "timedmedia-actions": "Veiksmai", + "timedmedia-direct-link": "Atsisiųsti", + "timedmedia-not-ready": "Neparuošta", + "timedmedia-completed-on": "Baigta $1", + "timedmedia-error-on": "Klaida $1", + "timedmedia-started-transcode": "Pradėta prieš $1. $2", + "timedmedia-percent-done": "Atlikta apie $1%", + "timedmedia-in-job-queue": "Pridėta prie darbų eilės prieš $1", + "timedmedia-unknown-target-size": "Nežinomas rezultato dydis, $1 užkoduota", + "timedmedia-days": "{{PLURAL:$1|1 diena|$1 dienos}}", + "timedmedia-hours": "{{PLURAL:$1|1 valanda|$1 valandos}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minutė|$1 minutės}}", + "timedmedia-seconds": "{{PLURAL:$1|1 sekundė|$1 sekundės}}", + "timedmedia-reset": "Atstatyti perkodavimą", + "timedmedia-reset-confirm": "Šio perkodavimo atstatymas pašalins visus egzistuojančius failus (jeigu jų yra) ir vėl pridės perkodavimą į darbų eilę. Perkodavimas užtruks kažkiek laiko.

\nAr tikrai norite tęsti?", + "timedmedia-reset-error": "Klaida perkodavimo atstatymo užduotyje.", + "timedmedia-source-file": "$1 šaltinis", + "timedmedia-source-file-desc": "Originalus $1 failas, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Pirminė $1 rinkmena ($2)", + "timedmedia-derivative-desc-160p.ogv": "Žemo pralaidumo Ogg video (160P)", + "timedmedia-derivative-desc-240p.ogv": "Internetu transliuotinas Ogg video (240P)", + "timedmedia-derivative-desc-360p.ogv": "Internetu transliuotinas Ogg video (360P)", + "timedmedia-derivative-desc-480p.ogv": "Internetu transliuotinas Ogg video (480P)", + "timedmedia-derivative-desc-720p.ogv": "Aukštos kokybės atsisiųstinas Ogg video (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Visiškai aukštos kokybės atsisiųstinas Ogg video (1080P)", + "timedmedia-derivative-desc-160p.webm": "Internetu transliuotinas WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "Internetu transliuotinas WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "Internetu transliuotinas WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "Aukštos kokybės atsisiųstinas WebM (720P)", + "timedmedia-derivative-desc-1080p.webm": "Visiškai aukštos kokybės atsisiųstinas WebM (1080P)", + "timedmedia-derivative-desc-2160p.webm": "Pilnai 4K atsisiųstinas WebM (2160P)", + "timedmedia-derivative-desc-360p.vp9.webm": "Internetu transliuotinas WebM VP9 (360P)", + "timedmedia-derivative-desc-480p.vp9.webm": "Internetu transliuotinas WebM VP9 (480P)", + "timedmedia-derivative-desc-720p.vp9.webm": "Aukštos kokybės transliuotinas WebM VP9 (720P)", + "timedmedia-derivative-desc-1080p.vp9.webm": "Visiškai aukštos kokybės transliuotinas WebM VP9 (1080P)", + "timedmedia-derivative-desc-2160p.vp9.webm": "Pilnai 4K transliuotinas WebM VP9 (2160P)", + "timedmedia-derivative-desc-320p.mp4": "Įrenginiams draugiškas MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "Internetu transliuotinas MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "Aukštos kokybės MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "Visiškai aukštos kokybės MP4 (1080P)", + "timedmedia-derivative-desc-2160p.mp4": "Pilnos 4K kokybės MP4 (2160P)", + "timedmedia-subtitle-new": "Sukurti naują vertimą arba redaguoti egzistuojantį", + "timedmedia-subtitle-new-desc": "Pasirinkite kalbą ir paspauskite '''{{int:Timedmedia-subtitle-new-go}}''' mygtuką", + "timedmedia-subtitle-new-go": "Vykdyti", + "timedmedia-subtitle-language": "$1 ($2) subtitrai", + "timedmedia-subtitle-no-video": "Nėra video, susieto su dabartiniu subtitrų puslapiu.", + "timedmedia-subtitle-no-subtitles": "Kol kas šiam video nėra subtitrų $1, bet Jūs galite [{{fullurl:{{FULLPAGENAME}}|action=edit}} redaguoti šį puslapį], norėdami juos pridėti.", + "timedmedia-subtitle-remote": "Šio failo tekstas su laiku yra patalpintas $1", + "timedmedia-subtitle-remote-link": "Galite [$1 peržiūrėti šio failo aprašymo puslapį] $2", + "timedmediahandler": "MedijosSuLaikuDoroklis", + "timedmedia-videos": "{{PLURAL:$1|$1 video|$1 video failai}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg video|$1 Ogg video failai}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM video|$1 WebM video failai}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 perkodavimas|$1 perkodavimai}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 vykdomas perkodavimas|$1 vykdomi perkodavimai}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 ieškomas perkodavimas|$1 ieškomi perkodavimai}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 nepavykęs perkodavimas|$1 nepavykę perkodavimai}}", + "timedmedia-file": "Failas", + "timedmedia-audios": "{{PLURAL:$1|$1 audio failas|$1 audio failai}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg audio failas|$1 Ogg audio failai}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC audio failas|$1 FLAC audio failai}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV audio failas|$1 WAV audio failai}}", + "right-transcode-reset": "Atstatymas nepavyko arba video failai buvo perkoduoti, todėl jie vėl buvo pridėti į darbų eilę", + "right-transcode-status": "Peržiūrėti [[Special:TimedMediaHandler|informaciją apie dabartines perkodavimo veiklas]]", + "action-transcode-status": "peržiūrėti dabartinę perkodavimo būseną", + "orphanedtimedtext": "Pamesto teksto su laiku puslapiai", + "orphanedtimedtext-summary": "Sąrašas [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] puslapių, kurie neturi atitinkamų failų.", + "orphanedtimedtext-unsupported": "Šis specialus puslapis palaiko tik MySQL duomenų bazes.", + "orphanedtimedtext-notimedtext": "Tekstas su laiku nėra aktyvuotas šiame vikyje.", + "apihelp-query+transcodestatus-description": "Gauti perkodavimo statusą pateiktajam failo puslapiui.", + "apihelp-query+transcodestatus-example-1": "Gauti perkodavimo būseną [[:File:Clip.webm]]", + "apihelp-query+videoinfo-description": "Papildo paveikslėlio informaciją, kad būtų pridėta video šaltinio (išvestinio) informacija", + "apihelp-query+videoinfo-param-prop": "Kurią video informaciją norite gauti:\n;timestamp:Prideda įkeltosios versijos laiko žymę.\n;user:Prideda naudotoją, kuris įkėlė video versiją.\n;userid:Prideda ID naudotojo, kuris įkėlė video versiją.\n;comment:Versijos komentaras.\n;parsedcomment:Apdorotas versijos komentaras.\n;canonicaltitle:Prideda kanoninį video failo pavadinimą.\n;url:Pateikia URL, nukreipiantį į video ir aprašymo puslapį.\n;size:Prideda video dydį baitais, jo aukštį ir plotį. Puslapio skaitliukas ir trukmė yra pridedami, jeigu prieinama.\n;dimensions:Dydžio alternatyva.\n;sha1:Prideda video SHA-1 maišos funkcijos reikšmę.\n;mime:Prideda video MIME tipą.\n;thumbmime:Prideda video MIME tipo miniatiūrą (reikalauja URL ir parametro $1urlwidth).\n;mediatype:Prideda video medijos tipą.\n;metadata:Pateikia video versijos Exif metaduomenų sąrašą.\n;commonmetadata:Pateikia video versijos failo formatų bendrų metaduomenų sąrašą.\n;extmetadata:Pateikia formatuotų metaduomenų sąrašą, sukombinuotą iš skirtingų šaltinių. Rezultatas yra suformatuotas HTML.\n;archivename:Prideda ne paskutinių versijų archyvo failo vardą.\n;bitdepth:Prideda versijos bitų gilumą.\n;uploadwarning:Naudojamas [[Special:Upload]] puslapyje gauti informacijai apie egzistuojantį failą. Nėra skirtas naudojimui MediaWiki branduolio išorėje.\n;derivatives:Prideda skirtingų prieinamų audio ir video failų formatų ir kokybės versijų sąrašą.", + "apihelp-query+videoinfo-example-1": "Gauti informaciją apie [[:File:Folgers.ogv]]", + "apihelp-transcodereset-description": "Naudotojai turintys 'perkodavimo-atsatymo' teises gali atstatyti ir pakartotinai paleisti perkodavimo užduotį.", + "apihelp-transcodereset-param-title": "Medijos failo pavadinimas.", + "apihelp-transcodereset-param-transcodekey": "Perkodavimo raktas, kurį norite atstatyti. Galite paimti iš [[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "Atstatyti visus [[:File:Clip.webm]] perkodavimus", + "apihelp-transcodereset-example-2": "Atstatyti '360_560kbs.webm' perkodavimo raktą [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/luz.json b/extensions/TimedMediaHandler/i18n/luz.json new file mode 100644 index 00000000..4ab30bc0 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/luz.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "علی ساکی لرستانی" + ] + }, + "timedmedia-no-player-js": "ۉاسی ۉبخشیت،مرۉرگر ایشا ۉھ صۉرت جاۉا اسکریپت غیرفعال ۉابیدھ ۉ هیچ پلیر پشتیبانی نارھ.
ایشا ۉا کلیژ ری ای ایترید download the clip or download a player کلیپنه مئنه مرۉرگر خۉتۉن پخش کنیت.", + "timedmedia-source-file": "$1 سرچشمه", + "timedmedia-source-audio-file-desc": "اصلی $1 فایل ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/lv.json b/extensions/TimedMediaHandler/i18n/lv.json new file mode 100644 index 00000000..9554b20b --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/lv.json @@ -0,0 +1,22 @@ +{ + "@metadata": { + "authors": [ + "Papuass", + "Xil", + "Silraks" + ] + }, + "timedmedia-more": "Vairāk...", + "timedmedia-dismiss": "Aizvērt", + "timedmedia-download": "Lejupielādēt failu", + "timedmedia-desc-link": "Par šo failu", + "timedmedia-status": "Statuss", + "timedmedia-status-unknown": "Nezināms statuss", + "timedmedia-transcodeinfo": "Formāts", + "timedmedia-actions": "Darbības", + "timedmedia-direct-link": "Lejupielādēt", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 avots", + "timedmedia-subtitle-new-go": "Aiziet!", + "timedmedia-subtitle-language": "$1 ($2) subtitri" +} diff --git a/extensions/TimedMediaHandler/i18n/mai.json b/extensions/TimedMediaHandler/i18n/mai.json new file mode 100644 index 00000000..7d189ffd --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/mai.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "बिप्लब आनन्द" + ] + }, + "timedmedia-no-player-js": "क्षमा करू, अहाँके ब्राउजर में या त जावास्क्रिप्ट अक्षम अछि या समर्थित प्लेयर नै अछि।
\nअहाँ क्लिपके डाउनलोड कैर सकै छी वा क्लिपके अपन ब्राउजरमें चलावाक लेल एक प्लेयर डाउनलोड कैर सकै छी।", + "timedmedia-source-file": "$1 स्रोत", + "timedmedia-source-audio-file-desc": "मूल $1 फाइल ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/mg.json b/extensions/TimedMediaHandler/i18n/mg.json new file mode 100644 index 00000000..ed53f4f9 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/mg.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Jagwar" + ] + }, + "timedmedia-no-player-js": "Miala tsiny indrindra, na tsy mampiasa JavaScript ny mpitety tranonkalanao, na tsy manana mpamaky zaka ilay izy.
\nAzonao atao ny mampidina ilay kilipy na mampidina mpamaky iray hamakiana ilay kilipy eo amin'ny mpitetinao.", + "timedmedia-source-file": "Fango $1", + "timedmedia-source-audio-file-desc": "Rakitra niaviana $1 ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/min.json b/extensions/TimedMediaHandler/i18n/min.json new file mode 100644 index 00000000..5b8b13a5 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/min.json @@ -0,0 +1,30 @@ +{ + "@metadata": { + "authors": [ + "Iwan Novirion" + ] + }, + "timedmedia-transcodeinfo": "Deskripsi turunan transkoder", + "timedmedia-ogg": "Ogg", + "timedmedia-webm": "WebM", + "timedmedia-derivative-160p.ogv": "Ogg 160p", + "timedmedia-derivative-360p.ogv": "Ogg 360p", + "timedmedia-derivative-480p.ogv": "Ogg 480p", + "timedmedia-derivative-720p.ogv": "Ogg 720p", + "timedmedia-derivative-160p.webm": "WebM 160p", + "timedmedia-derivative-360p.webm": "WebM 360p", + "timedmedia-derivative-480p.webm": "WebM 480p", + "timedmedia-derivative-720p.webm": "WebM 720p", + "timedmedia-derivative-320p.mp4": "H264 320p", + "timedmedia-derivative-480p.mp4": "H264 480p", + "timedmedia-derivative-720p.mp4": "H264 720p", + "timedmedia-derivative-ogg": "Ogg Vorbis", + "timedmedia-derivative-desc-ogg": "Ogg Vorbis", + "timedmedia-derivative-opus": "Opus", + "timedmedia-derivative-desc-opus": "Opus", + "timedmedia-derivative-mp3": "MP3", + "timedmedia-derivative-desc-mp3": "MP3", + "timedmedia-derivative-m4a": "AAC", + "timedmedia-derivative-desc-m4a": "AAC", + "timedmediahandler": "Daftar berkas media" +} diff --git a/extensions/TimedMediaHandler/i18n/mk.json b/extensions/TimedMediaHandler/i18n/mk.json new file mode 100644 index 00000000..69ba9495 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/mk.json @@ -0,0 +1,144 @@ +{ + "@metadata": { + "authors": [ + "Bjankuloski06", + "Brest" + ] + }, + "extensionname-timedmedia": "Титлови", + "timedmedia-desc": "Обработувач за аудио, видео и титлови, со поддршка за форматите WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1 звучна податотека, $2", + "timedmedia-ogg-short-video": "Ogg $1 видеоснимка, $2", + "timedmedia-ogg-short-general": "Мултимедијална податотека Ogg $1, $2", + "timedmedia-ogg-long-audio": "Ogg $1 звучна податотека, времетраење $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 видео податотека, времетраење $2, $4 × $5 пиксели, $3", + "timedmedia-ogg-long-multiplexed": "Ogg мултиплексирана аудио/видео снимка, $1, времетраење $2, $4 × $5 пиксели, вкупно $3", + "timedmedia-ogg-long-general": "Мултимедијална податотека Ogg, времетраење $2, $3", + "timedmedia-ogg-long-error": "Оштетена Ogg-податотека: $1", + "timedmedia-ogg-long-no-streams": "Ogg-податотека. Предупредување: Не препознав ниеден од користените кодеци.", + "timedmedia-webm-short-video": "WebM $1 видеоснимка, $2", + "timedmedia-webm-long-video": "WebM аудио/видео снимка, $1, времетраење: $2, $4 × $5 пиксели, вкупно $3", + "timedmedia-flac-short-audio": "Аудиоподатотека FLAC, $1", + "timedmedia-flac-long-audio": "Аудиоподатотека FLAC, времетраење: $1, вкупно $2", + "timedmedia-wav-short-audio": "Аудиоподатотека WAV, $1", + "timedmedia-wav-long-audio": "Аудиоподатотека WAV, времетраење: $1, вкупно $2", + "timedmedia-wav-pcm-required": "Можете да подигате само WAV-податотеки со PCM (импулсна кодна модулација).", + "timedmedia-mp4-short-video": "MP4 $1 видеоснимка, $2", + "timedmedia-mp4-long-video": "MP4 аудио/видео снимка, $1, времетраење $2, $4 × $5 пиксели, вкупно $3", + "timedmedia-no-player-js": "За жал, вашиот прелистувач или има оневозможено JavaScript, или нема ниту еден поддржан изведувач.
\nМожете да го преземете клипот или да преземете изведувач за да ја пуштите снимката во вашиот прелистувач.", + "timedmedia-more": "Повеќе...", + "timedmedia-dismiss": "Затвори", + "timedmedia-download": "Преземи податотека", + "timedmedia-play-media": "Пушти снимка", + "timedmedia-desc-link": "Информации за податотеката", + "timedmedia-oggThumb-version": "OggHandler бара oggThumb верзија $1 или понова.", + "timedmedia-oggThumb-failed": "oggThumb не успеа да ја создаде минијатурата.", + "timedmedia-status-header": "Статус на прекодирањето", + "timedmedia-update-status": "Поднови статус на прекодирање", + "timedmedia-status": "статус", + "timedmedia-status-unknown": "Непознат статус", + "timedmedia-transcodebitrate": "Пропусност", + "timedmedia-transcodeduration": "Траење на прекодирањето", + "timedmedia-transcodeinfo": "Формат", + "timedmedia-actions": "Дејства", + "timedmedia-direct-link": "Преземање", + "timedmedia-not-ready": "Не е готово", + "timedmedia-completed-on": "Завршено на $1", + "timedmedia-error-on": "Грешка на $1", + "timedmedia-started-transcode": "Започнато пред $1. $2", + "timedmedia-percent-done": "Сработено: околу $1%", + "timedmedia-in-job-queue": "Додадено во редицата на задачи пред $1", + "timedmedia-unknown-target-size": "Непозната целна големина. $1 е шифрирано", + "timedmedia-days": "{{PLURAL:$1|еден ден|$1 дена}}", + "timedmedia-hours": "{{PLURAL:$1|еден час|$1 часа}}", + "timedmedia-minutes": "{{PLURAL:$1|една минута|$1 минути}}", + "timedmedia-seconds": "{{PLURAL:$1|една секунда|$1 секунди}}", + "timedmedia-reset": "Одново", + "timedmedia-reset-confirm": "Враќајќи го ова прекодирање одново ќе ја отстраните постоечката податотека (ако ја има) и ќе го вратите прекодирањето во редицата на задачи. Повторното прекодирање ќе потрае.

Дали сте сигурни дека сакате да продолжите?", + "timedmedia-reset-error": "Грешка при повторното пуштање на транскодирањето.", + "timedmedia-ogg": "Ogg", + "timedmedia-webm": "WebM", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Извор на $1", + "timedmedia-source-file-desc": "Оригинална $1-податотека, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Оригинална $1-податотека ($2)", + "timedmedia-derivative-160p.ogv": "Ogg 160п", + "timedmedia-derivative-desc-160p.ogv": "Нископропусно Ogg-видео (160п)", + "timedmedia-derivative-240p.ogv": "Ogg 240 п", + "timedmedia-derivative-desc-240p.ogv": "Ogg-видео за емитување (240 пиксели)", + "timedmedia-derivative-360p.ogv": "Ogg 360п", + "timedmedia-derivative-desc-360p.ogv": "Ogg-видео за емитување (360 пиксели)", + "timedmedia-derivative-480p.ogv": "Ogg 480п", + "timedmedia-derivative-desc-480p.ogv": "Ogg-видео за емитување (480 пиксели)", + "timedmedia-derivative-720p.ogv": "Ogg 720п", + "timedmedia-derivative-desc-720p.ogv": "Висококвалитетно Ogg-видео за преземање (720п)", + "timedmedia-derivative-1080p.ogv": "Ogg 1080п", + "timedmedia-derivative-desc-1080p.ogv": "Високоразделна Ogg-видеоснимка за преземање (1080п)", + "timedmedia-derivative-160p.webm": "WebM 160п", + "timedmedia-derivative-desc-160p.webm": "WebM за каналско емитување (160п)", + "timedmedia-derivative-360p.webm": "WebM 360п", + "timedmedia-derivative-desc-360p.webm": "WebM за каналско емитување (360п)", + "timedmedia-derivative-480p.webm": "WebM 480п", + "timedmedia-derivative-desc-480p.webm": "WebM за каналско емитување (480п)", + "timedmedia-derivative-720p.webm": "WebM 720п", + "timedmedia-derivative-desc-720p.webm": "Висококвалитетен WebM за преземање (720п)", + "timedmedia-derivative-1080p.webm": "WebM 1080п", + "timedmedia-derivative-desc-1080p.webm": "Полноразделна WebM-видеоснимка за преземање (1080п)", + "timedmedia-derivative-desc-2160p.webm": "Полноразделна 4К WebM-видеоснимка за преземање (2160п)", + "timedmedia-derivative-360p.vp9.webm": "VP9 360п", + "timedmedia-derivative-desc-360p.vp9.webm": "WebM за каналско емитување (360п)", + "timedmedia-derivative-480p.vp9.webm": "VP9 480п", + "timedmedia-derivative-desc-480p.vp9.webm": "WebM за каналско емитување (480п)", + "timedmedia-derivative-720p.vp9.webm": "VP9 720п", + "timedmedia-derivative-desc-720p.vp9.webm": "WebM за високоразделно каналско емитување (720п)", + "timedmedia-derivative-1080p.vp9.webm": "VP9 1080п", + "timedmedia-derivative-desc-1080p.vp9.webm": "WebM за полноразделно каналско емитување (1080п)", + "timedmedia-derivative-desc-2160p.vp9.webm": "Полноразделна 4К WebM VP9 видеоснимка за преземање (2160п)", + "timedmedia-derivative-320p.mp4": "H264 320п", + "timedmedia-derivative-desc-320p.mp4": "MP4 за уреди (320п)", + "timedmedia-derivative-480p.mp4": "H264 480п", + "timedmedia-derivative-desc-480p.mp4": "MP4 за каналско емитување (480п)", + "timedmedia-derivative-720p.mp4": "H264 720п", + "timedmedia-derivative-desc-720p.mp4": "MP4-снимка со HD-квалитет (720п)", + "timedmedia-derivative-1080p.mp4": "H264 1080п", + "timedmedia-derivative-desc-1080p.mp4": "Високоразделба MP4-видеоснимка (1080п) за преземање", + "timedmedia-derivative-desc-2160p.mp4": "Полноразделна 4К квалитетна MP4-видеоснимка (2160п)", + "timedmedia-subtitle-new": "Создај нов превод или измени постоечки", + "timedmedia-subtitle-new-desc": "Изберете јазик и стиснете на копчето '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Дај", + "timedmedia-subtitle-language": "$1 ($2) титлови", + "timedmedia-subtitle-no-video": "Нема видеоснимка поврзана со тековната страница со титлови", + "timedmedia-subtitle-no-subtitles": "Моментално нема $1 титлови за оваа видеоснимка. Можете да ги додадете [{{fullurl:{{FULLPAGENAME}}|action=edit}} уредувајќи ја страницава]", + "timedmedia-subtitle-remote": "Титловите на податотекава се вдомени во $1", + "timedmedia-subtitle-remote-link": "Можете да ја [$1 погледате описната страница] на податотекава на $2", + "timedmediahandler": "Титлови", + "timedmedia-videos": "{{PLURAL:$1|$1 видео|$1 видеа}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg-видео|$1 Ogg-видеа}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM-видео|$1 WebM-видеа}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 прекодирање|$1 прекодирања}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 прекодирање во тек|$1 прекодирања во тек}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 прекодирање во редица на чекање|$1 прекодирања во редица на чекање}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 неуспешно прекодирање|$1 неуспешни прекодирања}}", + "timedmedia-file": "Податотека", + "timedmedia-audios": "{{PLURAL:$1|Една аудиоснимка|$1 аудиоснимки}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|Една Ogg-аудиоснимка|$1 Ogg-аудиоснимки}}", + "timedmedia-flac-audios": "{{PLURAL:$1|Една FLAC-аудиоснимка|$1 FLAC-аудиоснимки}}", + "timedmedia-wav-audios": "{{PLURAL:$1|Една WAV-аудиоснимка|$1 WAV-аудиоснимки}}", + "right-transcode-reset": "Презадавањето не успеа или има прекодирани видеа, па затоа повторно се ставени во редицата на задачи.", + "right-transcode-status": "Преглед на [[Special:TimedMediaHandler|информации за тековните активности во прекодирањето]]", + "action-transcode-status": "преглед на тековниот статус на прекодирањето", + "orphanedtimedtext": "Осамени страници со титлови", + "orphanedtimedtext-summary": "Список на страници со [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] што немаат своја податотека.", + "orphanedtimedtext-unsupported": "Оваа службена страница е поддржана само на MySQL-бази.", + "orphanedtimedtext-notimedtext": "Титловите не се овозможени на ова вики.", + "apihelp-query+transcodestatus-description": "Дај статус на прекодирањето на дадена податочна страница.", + "apihelp-query+transcodestatus-example-1": "Дај статус на прекодирањето на [[:File:Clip.webm]]", + "apihelp-query+videoinfo-description": "Ги дополнува информациите со податок за изворот на видеото (изведени)", + "apihelp-query+videoinfo-param-prop": "Кои информации за видеото да се дадат:\n;timestamp: Додава датум и време за подигнатата верзија.\n;user: Го додава корисникот што ја подигнал верзијата на видеото.\n;userid: Ја додава назнаката на корисникот што го подигнал видеото.\n;comment: Коментар на верзијата.\n;parsedcomment: Расчленување на коментарот на верзијата.\n;canonicaltitle: Го додава канонскиот наслов на видеоподатотеката.\n;url: Дава URL-адреса на видеото и описната страница.\n;size: Ја додава зафатнината на видеото во бајти, неговата висина и ширина. Ќе се додаде и бројот на страници и времетраењето, ако се применливи.\n;dimensions: Алијас за зафатнина.\n;sha1: Додава тараба SHA-1 за видеото.\n;mime: Додава MIME-тип на видеото.\n;thumbmime: Додава MIME-тип ан минијатурата на видеото (бара URL-адреса и параметарот $1urlwidth).\n;mediatype: Го додава медиумскиот тип на видеото.\n;metadata: Испишува Exif-метаподатоци за верзијата на видеото.\n;commonmetadata: Испишува општи метаподатоци на податочниот формат за верзијата на видеото.\n;extmetadata: Испишува форматирани метаподатоци собрани од повеќе извори. Резултатите се форматирани во HTML.\n;archivename: Го додава податотечното име на архивската верзија за сите верзии освен најновата.\n;bitdepth: Ја додава битовата длабочина на верзијата.\n;uploadwarning: Се користи на страницата [[Special:Upload]] за добивање на информации за постоечка податотека. Не е предвидено за употреба вон јадрото на МедијаВики.\n;derivatives: Додава низа од различниот формат и расположивите квалитети на аудио- или видеоподатотеката.", + "apihelp-query+videoinfo-example-1": "Дај информации за [[:File:Folgers.ogv]]", + "apihelp-transcodereset-description": "Корисниците со правото „transcode-reset“ можат да ги враќаат по основно прекодирањата и да ги вршат одново.", + "apihelp-transcodereset-param-title": "Наслов на податотеката.", + "apihelp-transcodereset-param-transcodekey": "Клучот на прекодирањето што сакате да го презададете. Преземи од [[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "Врати ги по основно сите прекодирања на [[:File:Clip.webm]]", + "apihelp-transcodereset-example-2": "Врати го по основно клучот „360_560kbs.webm“ за прекодирањето на [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/ml.json b/extensions/TimedMediaHandler/i18n/ml.json new file mode 100644 index 00000000..0b04df45 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ml.json @@ -0,0 +1,124 @@ +{ + "@metadata": { + "authors": [ + "Praveenp", + "Shijualex", + "Vssun", + "Santhosh.thottingal" + ] + }, + "timedmedia-desc": "ഓഗ് തിയോറ, വോർബിസ്, എസ്.ആർ.ടി, വെബ്എം തുടങ്ങിയവയ്ക്കുള്ള പിന്തുണയടക്കമുള്ള ശബ്ദ, ചലച്ചിത്ര, സമയമനുസരിച്ച് പ്രദർശിപ്പിക്കുന്ന എഴുത്തുകൾ എന്നിവയ്ക്കുള്ള കൈകാര്യോപകരണം", + "timedmedia-ogg-short-audio": "ഓഗ് $1 ശബ്ദപ്രമാണം, $2", + "timedmedia-ogg-short-video": "ഓഗ് $1 വീഡിയോ പ്രമാണം, $2", + "timedmedia-ogg-short-general": "ഓഗ് $1 മീഡിയ പ്രമാണം, $2", + "timedmedia-ogg-long-audio": "ഓഗ് $1 ശബ്ദ പ്രമാണം, ദൈർഘ്യം $2, $3", + "timedmedia-ogg-long-video": "ഓഗ് $1 വീഡിയോ പ്രമാണം, ദൈർഘ്യം $2, $4×$5 ബിന്ദു, $3", + "timedmedia-ogg-long-multiplexed": "ഓഗ് മൾട്ടിപ്ലക്സ്‌‌ഡ് ശബ്ദ/ചലച്ചിത്ര പ്രമാണം, $1, ദൈർഘ്യം $2, $4×$5 ബിന്ദു, ആകെക്കൂടി $3", + "timedmedia-ogg-long-general": "ഓഗ് മീഡിയ പ്രമാണം, ദൈർഘ്യം $2, $3", + "timedmedia-ogg-long-error": "അസാധുവായ ഓഗ് പ്രമാണം: $1", + "timedmedia-ogg-long-no-streams": "ഓഗ് മീഡിയ പ്രമാണം. മുന്നറിയിപ്പ്: ഈ പ്രമാണത്തിൽ ഉപയോഗിച്ചിരിക്കുന്ന കോഡെകുകളൊന്നും തിരിച്ചറിഞ്ഞിട്ടില്ല.", + "timedmedia-webm-short-video": "വെബ്എം $1 ചലച്ചിത്രപ്രമാണം, $2", + "timedmedia-webm-long-video": "വെബ്എം ശബ്ദ/ചലച്ചിത്ര പ്രമാണം, $1, ദൈർഘ്യം $2, $4 × $5 ബിന്ദു, ആകെക്കൂടി $3", + "timedmedia-flac-short-audio": "ഫ്ലാക് ശബ്ദപ്രമാണം, $1", + "timedmedia-flac-long-audio": "ഫ്ലാക് ശബ്ദപ്രമാണം, ദൈർഘ്യം $1, ആകെ $2", + "timedmedia-wav-short-audio": "വേവ് ശബ്ദപ്രമാണം, $1", + "timedmedia-wav-long-audio": "വേവ് ശബ്ദപ്രമാണം, ദൈർഘ്യം $1, ആകെ $2", + "timedmedia-wav-pcm-required": "പി.സി.എം. (പൾസ് ഓഡിയോ മോഡുലേഷൻ) വേവ് മാത്രമേ അപ്‌ലോഡ് ചെയ്യാനാവൂ.", + "timedmedia-mp4-short-video": "എംപി4 $1 വീഡിയോ പ്രമാണം, $2", + "timedmedia-mp4-long-video": "എംപി4 ശബ്ദ/ചലച്ചിത്ര പ്രമാണം, $1, ദൈർഘ്യം $2, $4 × $5 ബിന്ദു, ആകെക്കൂടി $3", + "timedmedia-no-player-js": "ക്ഷമിക്കുക, താങ്കളുടെ ബ്രൗസറിൽ ജാവാസ്ക്രിപ്റ്റ് സജ്ജമാക്കിയിട്ടില്ല അല്ലെങ്കിൽ പിന്തുണയുള്ള പ്ലേയർ ലഭ്യമല്ല.
\nതാങ്കൾക്ക് ചലച്ചിത്രഭാഗം ഡൗൺലോഡ് ചെയ്യാവുന്നതാണ് അല്ലെങ്കിൽ ബ്രൗസറിൽ പ്രവർത്തിപ്പിക്കാനായി ഒരു പ്ലേയർ ഡൗൺലോഡ് ചെയ്യാവുന്നതാണ്.", + "timedmedia-more": "കൂടുതൽ...", + "timedmedia-dismiss": "അടയ്ക്കുക", + "timedmedia-download": "പ്രമാണം ഡൗൺലോഡ് ചെയ്യുക", + "timedmedia-play-media": "മീഡിയ പ്രവർത്തിപ്പിക്കുക", + "timedmedia-desc-link": "ഈ പ്രമാണത്തെക്കുറിച്ച്", + "timedmedia-oggThumb-version": "ഓഗ്-തമ്പ് പതിപ്പ് $1 അല്ലെങ്കിൽ പുതിയത് ഓഗ്-ഹാൻഡ്ലറിനാവശ്യമാണ്.", + "timedmedia-oggThumb-failed": "ലഘുചിത്രം സൃഷ്ടിക്കുന്നതിൽ ഓഗ്-തമ്പ് പരാജയപ്പെട്ടു.", + "timedmedia-status-header": "ട്രാൻസ്‌കോഡ് സ്ഥിതി", + "timedmedia-update-status": "ട്രാൻസ്‌കോഡ് സ്ഥിതി പുതുക്കുക", + "timedmedia-status": "സ്ഥിതി", + "timedmedia-status-unknown": "അപരിചിതമായ സ്ഥിതി", + "timedmedia-transcodeinfo": "ട്രാൻസ്‌കോഡ് വ്യുൽപ്പന്നത്തിന്റെ വിവരണം", + "timedmedia-actions": "പ്രവൃത്തികൾ", + "timedmedia-direct-link": "വ്യുൽപ്പന്നം ഡൗൺലോഡ് ചെയ്യുക", + "timedmedia-not-ready": "തയ്യാറായിട്ടില്ല", + "timedmedia-completed-on": "ട്രാൻസ്‌കോഡ് ചെയ്യൽ പൂർത്തിയായി $1", + "timedmedia-error-on": "ട്രാൻസ്‌കോഡ് ചെയ്യലിൽ $1 എന്ന സമയത്ത് പിഴവുണ്ടായി", + "timedmedia-started-transcode": "ട്രാൻസ്‌കോഡ് ചെയ്യൽ $1 മുമ്പ് ആരംഭിച്ചു. $2", + "timedmedia-percent-done": "ഏകദേശം $1% പൂർത്തിയായി", + "timedmedia-in-job-queue": "ജോബ് ക്യൂവിലേയ്ക്ക് $1 മുമ്പ് ചേർത്തു", + "timedmedia-unknown-target-size": "ലക്ഷ്യമിടുന്ന വലിപ്പം അറിയില്ല, $1 എൻകോഡ് ചെയ്തു", + "timedmedia-days": "{{PLURAL:$1|ഒരു ദിവസം|$1 ദിവസം}}", + "timedmedia-hours": "{{PLURAL:$1|ഒരു മണിക്കൂർ|$1 മണിക്കൂർ}}", + "timedmedia-minutes": "{{PLURAL:$1|ഒരു മിനിറ്റ്|$1 മിനിറ്റ്}}", + "timedmedia-seconds": "{{PLURAL:$1|ഒരു സെക്കന്റ്|$1 സെക്കന്റ്}}", + "timedmedia-reset": "ട്രാൻസ്‌കോഡ് ചെയ്യൽ പുനഃസജ്ജീകരിക്കുക", + "timedmedia-reset-confirm": "ഈ ട്രാൻസ്‌കോഡ് ചെയ്യൽ പുനഃസജ്ജീകരിക്കുമ്പോൾ നിലവിലുള്ള ഫയൽ (അങ്ങനെയൊന്നുണ്ടെങ്കിൽ) നീക്കം ചെയ്യപ്പെടും, ഒപ്പം അത് ജോബ് ക്യൂവിലേയ്ക്ക് ട്രാൻസ്‌കോഡ് ചെയ്യാൻ വീണ്ടും ചേർക്കപ്പെടുകയും ചെയ്യും. വീണ്ടും-ട്രാൻസ്‌കോഡ് ചെയ്യാൻ അല്പസമയമെടുക്കുന്നതാണ്.

\nതുടരണമെന്ന് താങ്കൾക്കുറപ്പാണോ?", + "timedmedia-reset-error": "ട്രാൻസ്‌കോഡ് ചെയ്യൽ പുനഃസജ്ജീകരിക്കുന്നതിൽ പിഴവുണ്ടായി.", + "timedmedia-ogg": "ഓഗ്", + "timedmedia-webm": "വെബ്എം", + "timedmedia-mp4": "എംപി4", + "timedmedia-source-file": "$1 സ്രോതസ്സ്", + "timedmedia-source-file-desc": "യഥാർത്ഥ പ്രമാണം $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "യഥാർത്ഥ പ്രമാണം $1 ($2)", + "timedmedia-derivative-160p.ogv": "ഓഗ് 160പി", + "timedmedia-derivative-desc-160p.ogv": "കുറഞ്ഞ ബാൻഡ്‌വിഡ്ത് ഓഗ് ചലച്ചിത്രം (160ബി)", + "timedmedia-derivative-desc-240p.ogv": "വെബിലൂടെ എടുക്കാവുന്ന ഓഗ് ചലച്ചിത്രം (240P)", + "timedmedia-derivative-360p.ogv": "ഓഗ് 360പി", + "timedmedia-derivative-desc-360p.ogv": "വെബിലൂടെ എടുക്കാവുന്ന ഓഗ് ചലച്ചിത്രം (360ബി)", + "timedmedia-derivative-480p.ogv": "ഓഗ് 480പി", + "timedmedia-derivative-desc-480p.ogv": "വെബിലൂടെ എടുക്കാവുന്ന ഓഗ് ചലച്ചിത്രം (480ബി)", + "timedmedia-derivative-720p.ogv": "ഓഗ് 720പി", + "timedmedia-derivative-desc-720p.ogv": "ഉന്നത ഗുണമേന്മയിൽ ഡൗൺലോഡ് ചെയ്യാവുന്ന ഓഗ് ചലച്ചിത്രം (720ബി)", + "timedmedia-derivative-desc-1080p.ogv": "പൂർണ്ണ എച്ച്.ഡി. ഡൗൺലോഡ് ചെയ്യാവുന്ന ഓഗ് ചലച്ചിത്രം (1080 പി)", + "timedmedia-derivative-160p.webm": "വെബ്എം 160പി", + "timedmedia-derivative-desc-160p.webm": "വെബിലൂടെ എടുക്കാവുന്ന വെബ്എം (160ബി)", + "timedmedia-derivative-360p.webm": "വെബ്എം 360പി", + "timedmedia-derivative-desc-360p.webm": "വെബിലൂടെ എടുക്കാവുന്ന വെബ്എം (360ബി)", + "timedmedia-derivative-480p.webm": "വെബ്എം 480പി", + "timedmedia-derivative-desc-480p.webm": "വെബിലൂടെ എടുക്കാവുന്ന വെബ്എം (360ബി)", + "timedmedia-derivative-720p.webm": "വെബ്എം 720പി", + "timedmedia-derivative-desc-720p.webm": "ഉന്നത ഗുണമേന്മയിൽ ഡൗൺലോഡ് ചെയ്യാവുന്ന വെബ്എം (720ബി)", + "timedmedia-derivative-desc-1080p.webm": "പൂർണ്ണ എച്ച്.ഡി. ഡൗൺലോഡ് ചെയ്യാവുന്ന വെബ്എം (1080 പി)", + "timedmedia-derivative-desc-320p.mp4": "ഉപകരണ-സുഹൃദ് എംപി4 (320ബി)", + "timedmedia-derivative-desc-480p.mp4": "വെബിലൂടെ എടുക്കാവുന്ന എംപി4 (480ബി)", + "timedmedia-derivative-desc-720p.mp4": "എച്ച്.ഡി. ഗുണമേന്മയുള്ള എംപി4 (720ബി)", + "timedmedia-derivative-desc-1080p.mp4": "പൂർണ്ണ എച്ച്.ഡി. ഡൗൺലോഡ് ചെയ്യാവുന്ന എംപി4 (1080 പി)", + "timedmedia-derivative-ogg": "ഓഗ് വോർബിസ്", + "timedmedia-derivative-desc-ogg": "ഓഗ് വോർബിസ്", + "timedmedia-derivative-opus": "ഓപസ്", + "timedmedia-derivative-desc-opus": "ഓപസ്", + "timedmedia-derivative-mp3": "എം.പി.3", + "timedmedia-derivative-desc-mp3": "എം.പി.3", + "timedmedia-derivative-m4a": "എ.എ.സി.", + "timedmedia-derivative-desc-m4a": "എ.എ.സി.", + "timedmedia-subtitle-new": "പുതിയ പരിഭാഷ തുടങ്ങുക അല്ലെങ്കിൽ നിലവിലുള്ളത് തിരുത്തുക", + "timedmedia-subtitle-new-desc": "ഭാഷ തിരഞ്ഞെടുത്ത്, '''{{int:Timedmedia-subtitle-new-go}}''' ബട്ടൺ അമർത്തുക", + "timedmedia-subtitle-new-go": "പോകൂ", + "timedmedia-subtitle-language": "$1 ($2) സംഭാഷണരേഖകൾ", + "timedmedia-subtitle-no-video": "ഇപ്പോഴത്തെ സംഭാഷണരേഖാ താളുമായി ചലച്ചിത്രങ്ങളൊന്നും ചേർത്തിട്ടില്ല", + "timedmedia-subtitle-no-subtitles": "ഈ ചലച്ചിത്രത്തിന് $1 ഭാഷയിൽ ഇപ്പോൾ സംഭാഷണരേഖയൊന്നും ഇല്ല, താങ്കൾക്ക് [{{fullurl:{{FULLPAGENAME}}|action=edit}} ഈ താൾ തിരുത്തി] അവ കൂട്ടിച്ചേർക്കാവുന്നതാണ്", + "timedmedia-subtitle-remote": "ഈ പ്രമാണത്തിനുള്ള സമയമനുസരിച്ചുള്ള എഴുത്ത് $1-ൽ ഹോസ്റ്റ് ചെയ്തിട്ടുണ്ട്.", + "timedmedia-subtitle-remote-link": "ഈ പ്രമാണത്തിനുള്ള [$1 വിവരണ താൾ] $2-ൽ താങ്കൾക്ക് കാണാവുന്നതാണ്", + "timedmediahandler": "മീഡിയ സമയമനുസരിച്ച് കൈകാര്യം ചെയ്യാനുള്ളയുപകരണം", + "timedmedia-videos": "{{PLURAL:$1|ഒരു ചലച്ചിത്രം|$1 ചലച്ചിത്രങ്ങൾ}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|ഒരു ഓഗ് ചലച്ചിത്രം|$1 ഓഗ് ചലച്ചിത്രങ്ങൾ}}", + "timedmedia-webm-videos": "{{PLURAL:$1|ഒരു വെബ്എം ചലച്ചിത്രം|$1 വെബ്എം ചലച്ചിത്രങ്ങൾ}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|ഒരു ട്രാൻസ്കോഡ്|$1 ട്രാൻസ്കോഡുകൾ}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|ഒരെണ്ണം ട്രാൻസ്കോഡ് ചെയ്യുന്നു|$1 എണ്ണം ട്രാൻസ്കോഡ് ചെയ്യുന്നു}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|ഒരെണ്ണം ട്രാൻസ്കോഡ് ചെയ്യാനായി ഉണ്ട്|$1 എണ്ണം ട്രാൻസ്കോഡ് ചെയ്യാനായി ഉണ്ട്}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|ഒരെണ്ണം ട്രാൻസ്കോഡ് ചെയ്യുന്നത് പരാജയപ്പെട്ടു|$1 എണ്ണം ട്രാൻസ്കോഡ് ചെയ്യുന്നത് പരാജയപ്പെട്ടു}}", + "timedmedia-file": "പ്രമാണം", + "timedmedia-audios": "{{PLURAL:$1|ഒരു ശബ്ദപ്രമാണം|$1 ശബ്ദപ്രമാണങ്ങൾ}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|ഒരു ഓഗ് (Ogg) ശബ്ദപ്രമാണം|$1 ഓഗ് (Ogg) ശബ്ദപ്രമാണങ്ങൾ}}", + "timedmedia-flac-audios": "{{PLURAL:$1|ഒരു ഫ്ലാക് (FLAC) ശബ്ദപ്രമാണം|$1 ഫ്ലാക് (FLAC) ശബ്ദപ്രമാണങ്ങൾ}}", + "timedmedia-wav-audios": "{{PLURAL:$1|ഒരു വേവ് (WAV) ശബ്ദപ്രമാണം|$1 വേവ് (WAV) ശബ്ദപ്രമാണങ്ങൾ}}", + "right-transcode-reset": "പുനഃക്രമീകരണം പരാജയപ്പെട്ടു അല്ലെങ്കിൽ ട്രാൻസ്കോഡ് ചെയ്ത ചലച്ചിത്രങ്ങൾ ജോബ് ക്യൂവിലേയ്ക്ക് വീണ്ടും ചേർത്തു", + "right-transcode-status": "[[Special:TimedMediaHandler|ഇപ്പോഴത്തെ ട്രാൻസ്കോഡ് പ്രവർത്തനത്തിന്റെ വിവരങ്ങൾ]] കാണുക", + "action-transcode-status": "ഇപ്പോഴത്തെ ട്രാൻസ്കോഡിങ് സ്ഥിതി കാണുക", + "orphanedtimedtext": "ഉപയോഗിക്കപ്പെടാത്ത സമയാനുസൃതയെഴുത്ത് താളുകൾ", + "orphanedtimedtext-summary": "ബന്ധപ്പെട്ട പ്രമാണങ്ങൾ ഇല്ലാത്ത [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] താളുകളുടെ പട്ടിക.", + "orphanedtimedtext-unsupported": "മൈഎസ്ക്യുഎൽ ഡേറ്റാബേസുകളെ മാത്രമേ ഈ പ്രത്യേകതാൾ പിന്തുണയ്ക്കുന്നുള്ളു.", + "orphanedtimedtext-notimedtext": "സമയാനുസൃതയെഴുത്ത് ഈ വിക്കിയിൽ സജ്ജമാക്കിയിട്ടില്ല.", + "apihelp-transcodereset-param-title": "മീഡിയ പ്രമാണത്തിന്റെ തലക്കെട്ട്." +} diff --git a/extensions/TimedMediaHandler/i18n/mr.json b/extensions/TimedMediaHandler/i18n/mr.json new file mode 100644 index 00000000..0be5fe88 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/mr.json @@ -0,0 +1,28 @@ +{ + "@metadata": { + "authors": [ + "Kaustubh", + "V.narsikar", + "Htt" + ] + }, + "timedmedia-desc": "ऑग थियोरा व वॉर्बिस संचिकांसाठीचा चालक, जावास्क्रीप्ट प्लेयर सकट", + "timedmedia-ogg-short-audio": "ऑग $1 ध्वनी संचिका, $2", + "timedmedia-ogg-short-video": "ऑग $1 चलतचित्र संचिका, $2", + "timedmedia-ogg-short-general": "ऑग $1 मीडिया संचिका, $2", + "timedmedia-ogg-long-audio": "ऑग $1 ध्वनी संचिका, लांबी $2, $3", + "timedmedia-ogg-long-video": "ऑग $1 चलतचित्र संचिका, लांबी $2, $4×$5 पीक्सेल्स, $3", + "timedmedia-ogg-long-multiplexed": "ऑग ध्वनी/चित्र संचिका, $1, लांबी $2, $4×$5 पिक्सेल्स, $3 एकूण", + "timedmedia-ogg-long-general": "ऑग मीडिया संचिका, लांबी $2, $3", + "timedmedia-ogg-long-error": "चुकीची ऑग संचिका: $1", + "timedmedia-more": "आणखी...", + "timedmedia-dismiss": "बंद करा", + "timedmedia-download": "संचिका अधिभारण करा", + "timedmedia-desc-link": "या संचिकेबद्दलची माहिती", + "timedmedia-source-file": "$1 स्रोत", + "timedmedia-source-audio-file-desc": "मूळ $1 संचिका ($2)", + "timedmediahandler": "टाईम्डमिडियाहॅंडलर", + "timedmedia-videos": "{{PLURAL:$1|$1 व्हीडिओ}}", + "timedmedia-audios": "{{PLURAL:$1|$1 ध्वनी संचिका}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg ध्वनी संचिका}}" +} diff --git a/extensions/TimedMediaHandler/i18n/ms.json b/extensions/TimedMediaHandler/i18n/ms.json new file mode 100644 index 00000000..504aa6f0 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ms.json @@ -0,0 +1,97 @@ +{ + "@metadata": { + "authors": [ + "Anakmalaysia", + "Aviator" + ] + }, + "timedmedia-desc": "Pemproses audio, video dan teks bermasa, dengan sokongan format untuk WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "fail bunyi Ogg $1, $2", + "timedmedia-ogg-short-video": "fail video Ogg $1, $2", + "timedmedia-ogg-short-general": "fail media Ogg $1, $2", + "timedmedia-ogg-long-audio": "fail bunyi Ogg $1, tempoh $2, $3", + "timedmedia-ogg-long-video": "fail video Ogg $1, tempoh $2, $4×$5 piksel, $3", + "timedmedia-ogg-long-multiplexed": "fail audio/video multipleks Ogg, $1, tempoh $2, $4×$5 piksel, keseluruhan $3", + "timedmedia-ogg-long-general": "fail media Ogg, tempoh $2, $3", + "timedmedia-ogg-long-error": "Fail Ogg tidak sah: $1", + "timedmedia-webm-short-video": "fail video WebM $1, $2", + "timedmedia-webm-long-video": "fail audio/video webM, $1, tempoh $2, $4×$5 piksel, $3 keseluruhannya", + "timedmedia-flac-short-audio": "Fail audio FLAC, $1", + "timedmedia-flac-long-audio": "Fail audio FLAC, tempoh $1, $2 keseluruhannya", + "timedmedia-wav-short-audio": "Fail audio WAV, $1", + "timedmedia-wav-long-audio": "Fail audio WAV, tempoh $1, $2 keseluruhannya", + "timedmedia-wav-pcm-required": "Anda boleh memuat naik WAV PCM (Pulse Code Modulation) sahaja.", + "timedmedia-mp4-short-video": "fail video MP4 $1, $2", + "timedmedia-mp4-long-video": "fail audio/video MP4, $1, tempoh $2, $4×$5 piksel, $3 keseluruhannya", + "timedmedia-no-player-js": "Maaf, pelayar anda sama ada telah dimatikan JavaScript-nya ataupun tidak mempunyai sebarang pemain yang disokong.
\nAnda boleh memuat turun sedutan itu atau memuat turun pemain yang boleh memutarkan sedutan berkenaan dalam pelayar anda.", + "timedmedia-more": "Lagi…", + "timedmedia-dismiss": "Tutup", + "timedmedia-download": "Muat turun fail", + "timedmedia-play-media": "Mainkan media", + "timedmedia-desc-link": "Perihal fail ini", + "timedmedia-oggThumb-version": "OggHandler memerlukan oggThumb versi $1 ke atas.", + "timedmedia-oggThumb-failed": "oggThumb gagal mencipta gambar kenit.", + "timedmedia-status-header": "Status transkod", + "timedmedia-update-status": "Kemaskinikan status transkod", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Status tidak diketahui", + "timedmedia-transcodeinfo": "Keterangan hasil terbitan transkod", + "timedmedia-actions": "Tindakan", + "timedmedia-direct-link": "Muat turun terbitan", + "timedmedia-not-ready": "Belum sedia", + "timedmedia-completed-on": "Pentranskodan selesai pada $1", + "timedmedia-error-on": "Ralat dalam transkod pada $1", + "timedmedia-started-transcode": "Pentranskodan bermula $1 lalu. $2", + "timedmedia-percent-done": "Kira-kira $1% selesai", + "timedmedia-in-job-queue": "Ditambahkan ke dalam baris gilir kerja $1 lalu", + "timedmedia-unknown-target-size": "Saiz sasaran tidak diketahui, $1 dikodkan", + "timedmedia-days": "$1 hari", + "timedmedia-hours": "$1 jam", + "timedmedia-minutes": "$1 minit", + "timedmedia-seconds": "$1 saat", + "timedmedia-reset": "Set semula transkod", + "timedmedia-reset-confirm": "Mengeset semula transkod ini akan membuang sebarang fail yang sedia ada (jika ada), lalu menambahkan semula transkod ke dalam baris gilir tugas. Pentranskodan semula akan memakan masa.

Adakah anda tetap ingin melakukannya?", + "timedmedia-reset-error": "Ralat ketika mengeset semula tugas pentranskodan.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Sumber $1", + "timedmedia-source-file-desc": "Fail $1 asli, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Fail $1 asli ($2)", + "timedmedia-derivative-desc-160p.ogv": "Video Ogg lebar jalur rendah (160P)", + "timedmedia-derivative-desc-360p.ogv": "Video Ogg boleh strim web (360P)", + "timedmedia-derivative-desc-480p.ogv": "Video Ogg boleh strim web (480P)", + "timedmedia-derivative-desc-720p.ogv": "Video Ogg boleh muat turun bermutu tinggi (720P)", + "timedmedia-derivative-desc-1080p.ogv": "video Ogg HD sepenuhnya yang boleh dimuat turun (1080P)", + "timedmedia-derivative-desc-160p.webm": "WebM boleh strim web (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM boleh strim web (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM boleh strim web (480P)", + "timedmedia-derivative-desc-720p.webm": "Video WebM boleh muat turun bermutu tinggi (720P)", + "timedmedia-derivative-desc-1080p.webm": "WebM HD sepenuhnya yang boleh dimuat turun (1080P)", + "timedmedia-derivative-desc-320p.mp4": "MP4 mesra peranti (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 boleh strim web (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 bermutu HD (720P)", + "timedmedia-derivative-desc-1080p.mp4": "MP4 bermutu HD sepenuhnya (1080P)", + "timedmedia-subtitle-new": "Buat terjemahan baru atau sunting terjemahan sedia ada", + "timedmedia-subtitle-new-desc": "Pilih bahasa dan tekan butang '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Pergi", + "timedmedia-subtitle-language": "Sari kata $1 ($2)", + "timedmedia-subtitle-no-video": "Tiada video yang berkaitan dengan halaman sari kata ini", + "timedmedia-subtitle-no-subtitles": "Sekarang, tiada sari kata dalam $1 bagi video ini; anda boleh [{{fullurl:{{FULLPAGENAME}}|action=edit}} menyunting halaman ini] untuk menambahkannya", + "timedmedia-subtitle-remote": "Teks termasa untuk fail ini dihoskan di $1", + "timedmedia-subtitle-remote-link": "Anda boleh [$1 membaca halaman keterangan] untuk fail ini di $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "$1 video", + "timedmedia-ogg-videos": "$1 video Ogg", + "timedmedia-webm-videos": "$1 video WebM", + "timedmedia-derivative-state-transcodes": "$1 transkod", + "timedmedia-derivative-state-active": "$1 transkod aktif", + "timedmedia-derivative-state-queued": "$1 transkod berbaris", + "timedmedia-derivative-state-failed": "$1 transkod gagal", + "timedmedia-file": "Fail", + "timedmedia-audios": "$1 fail audio", + "timedmedia-ogg-audios": "$1 fail audio Ogg", + "timedmedia-flac-audios": "$1 fail audio FLAC", + "timedmedia-wav-audios": "$1 fail audio WAV", + "right-transcode-reset": "Mengeset semula video-video yang gagal atau ditranskodkan supaya dimasukkan semula ke dalam barisan gilir.", + "right-transcode-status": "Melihat [[Special:TimedMediaHandler|maklumat tentang kegiatan transkod semasa]]", + "action-transcode-status": "melihat status pentranskodan semasa" +} diff --git a/extensions/TimedMediaHandler/i18n/mt.json b/extensions/TimedMediaHandler/i18n/mt.json new file mode 100644 index 00000000..a60fd776 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/mt.json @@ -0,0 +1,11 @@ +{ + "@metadata": { + "authors": [ + "Leli Forte" + ] + }, + "timedmedia-no-player-js": "Jiddispjaċina li l-browser tiegħek jew m'għandux JavaScript attivat jew m'għandu l-ebda player supportat.
\nTista' tniżżel il-vidjow jew tniżżel player biex tara l-vidjow fuq il-browser tiegħek.", + "timedmedia-more": "Aktar", + "timedmedia-source-file": "Sors $1", + "timedmedia-source-audio-file-desc": "Fajl oriġinali $1 ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/myv.json b/extensions/TimedMediaHandler/i18n/myv.json new file mode 100644 index 00000000..35b195ae --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/myv.json @@ -0,0 +1,13 @@ +{ + "@metadata": { + "authors": [ + "Botuzhaleny-sodamo" + ] + }, + "timedmedia-more": "Седе ламо...", + "timedmedia-dismiss": "Пекстамс", + "timedmedia-download": "Таргамо файла", + "timedmedia-play-media": "Седямо медия", + "timedmedia-desc-link": "Те файладонть", + "timedmedia-source-file": "$1 лисьмапря" +} diff --git a/extensions/TimedMediaHandler/i18n/mzn.json b/extensions/TimedMediaHandler/i18n/mzn.json new file mode 100644 index 00000000..f383daec --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/mzn.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "محک" + ] + }, + "timedmedia-no-player-js": "بَخشِنّی، جاوا اسکریپت ِنرم افزار شمه بروزر دله فعال نی‌یه، یا شمه بروزر این نرم افزار ره ساپورت نکانده.
\nشما توندی این ویدیو ره دانلود و یا اتا پلیر این ویدیو ره هارشی‌ین وسّه دانلود هاکنین.", + "timedmedia-source-file": "$1 مبدأ", + "timedmedia-source-audio-file-desc": "اصلی $1 پرونده ( $2 )" +} diff --git a/extensions/TimedMediaHandler/i18n/nah.json b/extensions/TimedMediaHandler/i18n/nah.json new file mode 100644 index 00000000..5c41b6ca --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/nah.json @@ -0,0 +1,12 @@ +{ + "@metadata": { + "authors": [ + "Fluence", + "Taresi" + ] + }, + "timedmedia-more": "Huehca ōmpa...", + "timedmedia-download": "Tictemōz tlahcuilōlli", + "timedmedia-desc-link": "Inīn tlahcuilōltechcopa", + "timedmedia-source-file": "$1 mēyalli" +} diff --git a/extensions/TimedMediaHandler/i18n/nap.json b/extensions/TimedMediaHandler/i18n/nap.json new file mode 100644 index 00000000..c9953c8f --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/nap.json @@ -0,0 +1,122 @@ +{ + "@metadata": { + "authors": [ + "C.R.", + "Chelin", + "Candalua" + ] + }, + "timedmedia-desc": "Gestore p' 'e file audio, video e teste temporizzate, cu 'e formate supportate pe WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "File audio Ogg $1, $2", + "timedmedia-ogg-short-video": "File video Ogg $1, $2", + "timedmedia-ogg-short-general": "File multimediale Ogg $1, $2", + "timedmedia-ogg-long-audio": "File audio Ogg $1, durata $2, $3", + "timedmedia-ogg-long-video": "File video Ogg $1, durata $2, dimenziune $4×$5 pixel, $3", + "timedmedia-ogg-long-multiplexed": "File multiplexed audie e video Ogg $1, lunghezze $2, $4 x $5 pixel, $3 in totale", + "timedmedia-ogg-long-general": "File media Ogg, lunghezze $2, $3", + "timedmedia-ogg-long-error": "Ogg file invalide: $1", + "timedmedia-ogg-long-no-streams": "File multimediale Ogg. Attenziò: nun è stato ricanusciuto nisciuno codec ausato ind'a stu file.", + "timedmedia-webm-short-video": "WebM $1 file video, $2", + "timedmedia-webm-long-video": "File WebM audio/video, $1, lunghezze $2, $4 × $5 pixel, $3 in totale", + "timedmedia-flac-short-audio": "File audio FLAC, $1", + "timedmedia-flac-long-audio": "File audio FLAC, lunghezze $1, in totale $2", + "timedmedia-wav-short-audio": "File audio WAV, $1", + "timedmedia-wav-long-audio": "File audio WAV, lunghezze $1, in totale $2", + "timedmedia-wav-pcm-required": "Putite surtanto carrecà WAV 'e tipo PCM (Codece 'e Modulazione Pulsate).", + "timedmedia-mp4-short-video": "MP4 $1 file video, $2", + "timedmedia-mp4-long-video": "File MP4 audio/video, $1, lunghezze $2, $4 × $5 pixel, $3 in totale", + "timedmedia-no-player-js": "Spiacente, 'o browser vuosto tene 'o JavaScript stutato o nun tène nu secutore suppurtato.
\nPutite scarrecà 'o video o scarrecà nu secutore pe' putè miette 'o play ô video int' 'o browser vuosto.", + "timedmedia-more": "Ato...", + "timedmedia-dismiss": "Chiure", + "timedmedia-download": "Scarreca 'o file", + "timedmedia-play-media": "Secuta 'u media", + "timedmedia-desc-link": "'Mfurmaziune ncopp'a stu file", + "timedmedia-oggThumb-version": "OggHandler vulesse 'a verziona 'e oggThumb $1 o cchiù nnanz.", + "timedmedia-oggThumb-failed": "oggThumn ha fallite a ccrià 'e miniature.", + "timedmedia-status-header": "Stato d' 'a codifeca", + "timedmedia-update-status": "Agghiuorna 'o stato d' 'a codifeca", + "timedmedia-status": "Stato", + "timedmedia-status-unknown": "Stato scanusciuto", + "timedmedia-transcodebitrate": "Bitrate", + "timedmedia-transcodeduration": "Tiempo 'e codifeca", + "timedmedia-transcodeinfo": "Furmato", + "timedmedia-actions": "Aziune", + "timedmedia-direct-link": "Scarreca", + "timedmedia-not-ready": "Nun è pronto", + "timedmedia-completed-on": "$1 fernuto", + "timedmedia-error-on": "Errore dint' 'o $1", + "timedmedia-started-transcode": "Accummenciato $1 fa. $2", + "timedmedia-percent-done": "Cchiù o meno $1% fatto", + "timedmedia-in-job-queue": "Ghiuonta a 'a Fatica misa ncóra $1 fà", + "timedmedia-unknown-target-size": "Diminziona 'e destinazione scanusciuta, $1 encoded", + "timedmedia-days": "{{PLURAL:$1|1 juorno|$1 ghiuorne}}", + "timedmedia-hours": "{{PLURAL:$1|n'ora|$1 ore}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuto|$1 minute}}", + "timedmedia-seconds": "{{PLURAL:$1|1 secondo|$1 seconde}}", + "timedmedia-reset": "Azzere 'a transcodifeca", + "timedmedia-reset-confirm": "Resettanno sta transcodifica tuttuquante 'e file ch'esisteno mò (si ce stanno), e po' azzeccasse 'a transcodifica int'a 'o codece d' 'a fatica. Se pigliasse nu poco 'e tiempo pe' puté ffà 'o re-transcode.

\nSite sicuro/a ca vulite ghì annanze?", + "timedmedia-reset-error": "Errore pe' tramente ca se steva azzeranno 'a fatica 'e transcodifeca.", + "timedmedia-source-file": "$1 funte", + "timedmedia-source-file-desc": "File origgenale $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "File origgenale $1 ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg video cu banda vascia (160P)", + "timedmedia-derivative-desc-240p.ogv": "Ogg video ca se putesse mannà pe' webstreaming (240P)", + "timedmedia-derivative-desc-360p.ogv": "Ogg video ca se putesse mannà pe' webstreaming (360P)", + "timedmedia-derivative-desc-480p.ogv": "Ogg video ca se putesse mannà pe' webstreaming (480P)", + "timedmedia-derivative-desc-720p.ogv": "Ogg video 'e qualità auta scarrecabbele (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Video Ogg scarrecabbele 'n Full HD (1080P)", + "timedmedia-derivative-desc-160p.webm": "Flusso web WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "Flusse web WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "Flusse web WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "Ogg video 'e qualità auta scarrecabbele WebM (720P)", + "timedmedia-derivative-desc-1080p.webm": "Video Ogg scarrecabbele 'n Full HD WebM (1080P)", + "timedmedia-derivative-desc-2160p.webm": "WebM scarrecabbele 'n Full 4K (2160P)", + "timedmedia-derivative-desc-360p.vp9.webm": "Flusse web WebM VP9 (360P)", + "timedmedia-derivative-desc-480p.vp9.webm": "Flusse web WebM (480P)", + "timedmedia-derivative-desc-720p.vp9.webm": "Flusse HD WebM VP9 (720P)", + "timedmedia-derivative-desc-1080p.vp9.webm": "Flusse Full HD WebM VP9 (1080P)", + "timedmedia-derivative-desc-2160p.vp9.webm": "WebM VP9 'e qualità Full 4K (2160P)", + "timedmedia-derivative-desc-320p.mp4": "Semprice pe' ll'apparicchie MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "Flusse web WebM (480P)", + "timedmedia-derivative-desc-720p.mp4": "Qualità HD MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "MP4 'e qualità Full HD (1080P)", + "timedmedia-derivative-desc-2160p.mp4": "MP4 'e qualità Full 4K (2160P)", + "timedmedia-subtitle-new": "Crìa na traduzione o cagna chella esistente", + "timedmedia-subtitle-new-desc": "Sceglie na lengua e clicca 'o buttone '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Vàje", + "timedmedia-subtitle-language": "$1 ($2) sottotitele", + "timedmedia-subtitle-no-video": "Nun ce stanno video ssuocie ch' 'e sottotitole d' 'e paggene 'e mò.", + "timedmedia-subtitle-no-subtitles": "Nun ce stanno mò sottotitule dint'a $1 pe' stu video, putite [{{fullurl:{{FULLPAGENAME}}|action=cagnà}} 'a paggena] pe' putè azzeccà chiste.", + "timedmedia-subtitle-remote": "'O testo temporizzate pe' stu file fuje ospitato ncopp'a $1", + "timedmedia-subtitle-remote-link": "Putite [$1 vedé 'a paggena 'e descrizione] 'e stu file ncopp'a $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 video}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 video Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 video WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transcodifica|$1 transcodifiche}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 transcodifica|$1 transcodifiche}} in esecuzzione", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transcodifica|$1 transcodifiche}} in coda", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 guastaje transcodifeca|$1 guastaje 'e transcodifiche}}", + "timedmedia-file": "File", + "timedmedia-audios": "{{PLURAL:$1|$1 file audio}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 file audio Ogg}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 file audio FLAC}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 file audio WAV}}", + "right-transcode-reset": "Appiccia n'ata vota 'e transcodifiche guaste 'e video ca songo state mise dint'a córa 'e fatiche n'ata vota", + "right-transcode-status": "Vide [[Special:TimedMediaHandler|'e nfurmaziune ncopp' 'a transcodifeca 'e mò]]", + "action-transcode-status": "vide 'o stato 'e transcodifeca 'e mo", + "orphanedtimedtext": "Paggene TimedText (sottotitule) urfanielle", + "orphanedtimedtext-summary": "Elenco 'e pagine [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] ca nun teneno 'o file currispunnente.", + "orphanedtimedtext-unsupported": "Sta paggena speciale è surtanto suppurtata dint' 'e database MySQL.", + "orphanedtimedtext-notimedtext": "'O TimedText (sottotitule) nun songo abbiàte dint'a stu wiki.", + "apihelp-query+transcodestatus-description": "Piglia 'o stato 'e transcodifeca pe' na paggena data.", + "apihelp-query+transcodestatus-example-1": "Piglia 'o stato transcode pe' [[:File:Clip.webm]]", + "apihelp-query+videoinfo-description": "Spanne imageinfo pe' putè nzertà 'e nfurmaziune fonte 'e video (derivative)", + "apihelp-query+videoinfo-param-prop": "Che nfurmaziune se pigliasse 'o video:\n;timestamp:Azzecca 'a timestamp p' 'e verziune carrecate.\n;user:Azzecca l'utente c'avesse fatto l'upload dint'a verziona 'e video.\n;userid:Azzecca l'ID utente ca carrecaje sta verziona 'e video.\n;comment:Nu cummento ncopp' 'a verziona.\n;parsedcomment:Passa-scagna 'o cummento dint'a verziona.\n;canonicaltitle:Azzecca 'o titulo canonico 'e stu file 'e video.\n;url:Mpizzasse n'URL dint'o video e 'a paggena 'e descriziona.\n;size:Azzeccasse 'a pesantezza d' 'o file comm'a nu valore 'n byte, l'auto e 'o luongo suojo. 'O cunt' 'e paggene e 'a durata songo azzeccate si apprecabbele.\n;dimensions:Aliasse pe diminziona.\n;sha1:Azzecca nu hash SHA-1 p' 'o video video.\n;mime:Azzecca 'o tipo MIME d' 'o video.\n;thumbmime:Azzecca 'o tipo MIME p' 'o video thumbnail (servesse n'url e 'o param $1urlwidth).\n;mediatype:Azzecca 'o tipo 'e media dint' 'o video.\n;metadata:Elenca 'e metadate Exif p' 'a verziona d' 'o video.\n;commonmetadata:Elenca nu furmat' 'e file generico 'e metadate p' 'a verziona d' 'o video.\n;extmetadata:Elenca 'e date furmattate metadata cumbinate 'a fonte differente. 'E risultate songo HTML furmattate.\n;archivename:Azzecca 'o filename (nomm' 'e file) 'e sta verziona archivio pe' verziune ca nun fossero ll'urdema.\n;bitdepth:Azzecca 'o prufunno 'e bit d' 'a verziona.\n;uploadwarning:Ausato p' 'a paggena [[Special:Upload]] pe' se piglià nfurmaziune ncopp'a nu file ch'esiste mò. Nun ce buò stu coso pe' putè ausà 'o file for' 'o core 'e MediaWiki.\n;derivatives:Azzecca n'array 'e furmate differente cu qualità e verziune 'audio o video ca fosse mò a disposizione.", + "apihelp-query+videoinfo-example-1": "Arrepiglia mò 'e nfurmaziune ncopp' 'o [[:File:Folgers.ogv]]", + "apihelp-transcodereset-description": "Utente c' 'o deritto 'e 'transcode-reset' putessero resettà e secutà n'ata vota na fatica 'e transcodifeca.", + "apihelp-transcodereset-param-title": "'O titolo d' 'o file media.", + "apihelp-transcodereset-param-transcodekey": "'A chiave 'e transcodifeca ca vulite re-appiccià. Arrepiglia mò [[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "Re-appiccia tuttuquante transcodes pe' [[:File:Clip.webm]]", + "apihelp-transcodereset-example-2": "Re-appiccia 'a chiave 'e transcodifeca '360_560kbs.webm' pe' [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/nb.json b/extensions/TimedMediaHandler/i18n/nb.json new file mode 100644 index 00000000..7a32ec0a --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/nb.json @@ -0,0 +1,105 @@ +{ + "@metadata": { + "authors": [ + "Danmichaelo", + "Laaknor", + "Nghtwlkr" + ] + }, + "timedmedia-desc": "Håndteringsprogram for lyd, video og tidsbestemt tekst, med formatstøtte for WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1 lydfil, $2", + "timedmedia-ogg-short-video": "Ogg $1 videofil, $2", + "timedmedia-ogg-short-general": "Ogg $1 mediefil, $2", + "timedmedia-ogg-long-audio": "Ogg $1 lydfil, lengde $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 videofil, lengde $2, $4×$5 piksler, $3", + "timedmedia-ogg-long-multiplexed": "Sammensatt ogg lyd-/videofil, $1, lengde $2, $4×$5 piksler, $3 til sammen", + "timedmedia-ogg-long-general": "Ogg mediefil, lengde $2, $3", + "timedmedia-ogg-long-error": "Ugyldig Ogg-fil: $1", + "timedmedia-ogg-long-no-streams": "Ogg-mediafil. Advarsel: Ingen av omkoderne brukt i denne filen ble gjenkjent.", + "timedmedia-webm-short-video": "WebM $1 videofil, $2", + "timedmedia-webm-long-video": "WebM lyd-/videofil, $1, lengde $2, $4 × $5 piksler, $3 til sammen", + "timedmedia-flac-short-audio": "FLAC-lydfil, $1", + "timedmedia-flac-long-audio": "FLAC-lydfil, varighet $1, $2 totalt", + "timedmedia-wav-short-audio": "WAV-lydfil, $1", + "timedmedia-wav-long-audio": "WAV-lydfil, varighet $1, $2 totalt", + "timedmedia-wav-pcm-required": "Du kan kun laste opp PCM (Pulse Code Modulation) WAV.", + "timedmedia-mp4-short-video": "MP4 $1 videofil, $2", + "timedmedia-mp4-long-video": "MP4-fil for lyd/video, $1, lengde $2, $4 × $5 piksler, $3 totalt", + "timedmedia-no-player-js": "Beklager, nettleseren din har enten deaktivert JavaScript eller har ingen støttet spiller.
\nDu kan laste ned klippet eller laste ned en spiller for å spille av klippet i nettleseren din.", + "timedmedia-more": "Mer …", + "timedmedia-dismiss": "Lukk", + "timedmedia-download": "Last ned fil", + "timedmedia-play-media": "Spill av media", + "timedmedia-desc-link": "Om denne filen", + "timedmedia-oggThumb-version": "OggHandler krever oggThumb versjon $1 eller senere.", + "timedmedia-oggThumb-failed": "oggThumb klarte ikke å opprette miniatyrbildet.", + "timedmedia-status-header": "Omkodingsstatus", + "timedmedia-update-status": "Oppdater omkodingsstatus", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Ukjent status", + "timedmedia-transcodeinfo": "Beskrivelse", + "timedmedia-actions": "Handlinger", + "timedmedia-direct-link": "Last ned utgave", + "timedmedia-not-ready": "Ikke klar", + "timedmedia-completed-on": "Omkoding ferdig $1", + "timedmedia-error-on": "Omkoding feilet den $1", + "timedmedia-started-transcode": "Omkoding startet for $1 siden. $2", + "timedmedia-percent-done": "Rundt $1% ferdig", + "timedmedia-in-job-queue": "Lagt til i jobbkø for $1 siden", + "timedmedia-unknown-target-size": "Ukjent målstørrelse, $1 ferdig kodet", + "timedmedia-days": "{{PLURAL:$1|1 dag|$1 dager}}", + "timedmedia-hours": "{{PLURAL:$1|1 time|$1 timer}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minutt|$1 minutter}}", + "timedmedia-seconds": "{{PLURAL:$1|1 sekund|$1 sekunder}}", + "timedmedia-reset": "Nullstill omkoding", + "timedmedia-reset-confirm": "Nullstilling av denne omkodingsjobben vil slette eksisterende filer (hvis det er noen), og gjeninnsette omkodingsjobben i jobbkøen. Dette vil naturligvis ta noe tid.

\nEr du sikker på at du ønsker å fortsette?", + "timedmedia-reset-error": "Det oppsto en feil ved forsøk på å nullstille omkodingsjobben.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 kilde", + "timedmedia-source-file-desc": "Original $1-fil, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Original $1-fil ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg-video for lav båndbredde (160P)", + "timedmedia-derivative-desc-360p.ogv": "Nett-strømbar Ogg-video (360P)", + "timedmedia-derivative-desc-480p.ogv": "Nett-strømbar Ogg-video (480P)", + "timedmedia-derivative-desc-720p.ogv": "Høykvalitets nedlastbar Ogg-video (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Nedlastbar Ogg-video i full HD (1080P)", + "timedmedia-derivative-desc-160p.webm": "Nett-strømbar WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "Nett-strømbar WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "Nett-strømbar WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "Høykvalitets nedlastbar WebM (720P)", + "timedmedia-derivative-desc-1080p.webm": "Nedlastbar WebM-video i full HD (1080P)", + "timedmedia-derivative-desc-320p.mp4": "Mobilvennlig MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "Nett-strømbar MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "HD-kvalitet MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "MP4-video i full HD (1080P)", + "timedmedia-subtitle-new": "Opprett ny oversettelse eller rediger eksisterende", + "timedmedia-subtitle-new-desc": "Velg språk eller trykk på '''{{int:Timedmedia-subtitle-new-go}}'''-knappen", + "timedmedia-subtitle-new-go": "Kjør", + "timedmedia-subtitle-language": "$1 ($2) undertekster", + "timedmedia-subtitle-no-video": "Det finnes ingen video koblet til den aktuelle undertekstsiden", + "timedmedia-subtitle-no-subtitles": "Det er for øyeblikket ingen undertekster på $1 for denne videoen. Du kan [{{fullurl:{{FULLPAGENAME}}|action=edit}} redigere denne siden] for å legge de til", + "timedmedia-subtitle-remote": "Undertekster (timed text) for denne filen befinner seg på $1", + "timedmedia-subtitle-remote-link": "Du kan [$1 vise beskrivelsessiden] for denne filen på $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 video|$1 videoer}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg-video|$1 Ogg-videoer}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM-video|$1 WebM-videoer}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 omkoding|$1 omkodinger}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 pågående omkoding|$1 pågående omkodinger}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 omkoding|$1 omkodinger}} i kø", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 omkoding|$1 omkodinger}} feilet", + "timedmedia-file": "Fil", + "timedmedia-audios": "{{PLURAL:$1|$1 lydfil|$1 lydfiler}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg-lydfil|$1 Ogg-lydfiler}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC-lydfil|$1 FLAC-lydfiler}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV-lydfil|$1 WAV-lydfiler}}", + "right-transcode-reset": "Nullstill feilede eller omkodede videoer så de blir gjeninnsatt i jobbkøen.", + "right-transcode-status": "Vis [[Special:TimedMediaHandler|informasjon om pågående omkodinger]]", + "action-transcode-status": "vise pågående omkodingsstatus", + "orphanedtimedtext": "Foreldreløse TimedText-sider", + "orphanedtimedtext-summary": "Liste over [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]]-sider som ikke har tilknytning til noen fil.", + "orphanedtimedtext-unsupported": "Denne spesialsiden er kun støttet for MySQL-databaser.", + "orphanedtimedtext-notimedtext": "TimedText er ikke påskrudd på denne wikien.", + "apihelp-query+transcodestatus-description": "Hent transkodingsstatus for en gitt filside.", + "apihelp-query+transcodestatus-example-1": "Hent transkodingsstatus for [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/nds-nl.json b/extensions/TimedMediaHandler/i18n/nds-nl.json new file mode 100644 index 00000000..f785cf7c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/nds-nl.json @@ -0,0 +1,24 @@ +{ + "@metadata": { + "authors": [ + "Servien" + ] + }, + "timedmedia-desc": "Haandelt audio, video en ondertitels aof mit ondersteuning veur WebM, Ogg Theora, Vorbis en srt", + "timedmedia-ogg-short-audio": "Ogg $1 geluudsbestaand, $2", + "timedmedia-ogg-short-video": "Ogg $1 videobestaand, $2", + "timedmedia-ogg-short-general": "Ogg $1 mediabestaand, $2", + "timedmedia-ogg-long-audio": "Ogg $1 geluudsbestaand, lengte $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 videobestaand, lengte $2, $4×$5 pixels, $3", + "timedmedia-ogg-long-multiplexed": "Ogg-emultiplext geluuds-/videobestaand, $1, lengte $2, $4×$5 pixels, $3 totaal", + "timedmedia-ogg-long-general": "Ogg-mediabestaand, lengte $2, $3", + "timedmedia-ogg-long-error": "Ongeldig Ogg-bestaand: $1", + "timedmedia-no-player-js": "Joew systeem hef JavaScript uutstaon of der is gien ondersteunde mediaspeuler.
\nJe kunnen de klip neerlaojen of n mediaspeuler neerlaojen um de klip aof te speulen in joew webkieker.", + "timedmedia-more": "Meer...", + "timedmedia-dismiss": "Sluten", + "timedmedia-download": "Bestaand neerlaojen", + "timedmedia-desc-link": "Over dit bestaand", + "timedmedia-source-file": "Bron van $1", + "timedmedia-source-audio-file-desc": "Oorspronkelik $1-bestaand ($2)", + "timedmediahandler": "Timed Media Handler" +} diff --git a/extensions/TimedMediaHandler/i18n/nds.json b/extensions/TimedMediaHandler/i18n/nds.json new file mode 100644 index 00000000..b8404001 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/nds.json @@ -0,0 +1,20 @@ +{ + "@metadata": { + "authors": [ + "Slomox" + ] + }, + "timedmedia-desc": "Stüürprogramm för Ogg-Theora- un Vorbis Datein, mitsamt en Afspeler in JavaScript", + "timedmedia-ogg-short-audio": "Ogg-$1-Toondatei, $2", + "timedmedia-ogg-short-video": "Ogg-$1-Videodatei, $2", + "timedmedia-ogg-short-general": "Ogg-$1-Mediendatei, $2", + "timedmedia-ogg-long-audio": "Ogg-$1-Toondatei, $2 lang, $3", + "timedmedia-ogg-long-video": "Ogg-$1-Videodatei, $2 lang, $4×$5 Pixels, $3", + "timedmedia-ogg-long-multiplexed": "Ogg-Multiplexed-Audio-/Video-Datei, $1, $2 lang, $4×$5 Pixels, $3 alltohoop", + "timedmedia-ogg-long-general": "Ogg-Mediendatei, $2 lang, $3", + "timedmedia-ogg-long-error": "Kaputte Ogg-Datei: $1", + "timedmedia-more": "Mehr...", + "timedmedia-dismiss": "Dichtmaken", + "timedmedia-download": "Datei dalladen", + "timedmedia-desc-link": "Över disse Datei" +} diff --git a/extensions/TimedMediaHandler/i18n/ne.json b/extensions/TimedMediaHandler/i18n/ne.json new file mode 100644 index 00000000..acf3a5d1 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ne.json @@ -0,0 +1,25 @@ +{ + "@metadata": { + "authors": [ + "सरोज कुमार ढकाल", + "बिप्लब आनन्द", + "NehalDaveND", + "राम प्रसाद जोशी" + ] + }, + "timedmedia-ogg-short-audio": "Ogg $1 ध्वनि फाइल, $2", + "timedmedia-ogg-short-video": "Ogg $1 विडियो फाइल, $2", + "timedmedia-ogg-short-general": "Ogg $1 मीडिया फाइल, $2", + "timedmedia-no-player-js": "माफ गर्नुहोला, तपाईंको ब्राउजरमा या त जावास्क्रिप्ट अक्षम छ या समर्थित प्लेयर छैन ।
\nतपाईं क्लिपलाई डाउनलोड गर्न सक्नुहुन्छ अथवा क्लिपलाई आफ्नो ब्राउजरमा चलाउनको लागि एक प्लेयर डाउनलोड गर्न सक्नु हुन्छ ।", + "timedmedia-more": "थप...", + "timedmedia-dismiss": "बन्ध गर्ने", + "timedmedia-download": "डाउनलोड गर्ने फाइल", + "timedmedia-status": "स्थिति", + "timedmedia-status-unknown": "अज्ञात स्थिति", + "timedmedia-actions": "कार्यहरु", + "timedmedia-source-file": "$1 श्रोत", + "timedmedia-source-file-desc": "मूल $1 फाईल, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "मूल $1 फाईल ($2)", + "timedmedia-subtitle-new-go": "जाने", + "timedmedia-file": "फाइल" +} diff --git a/extensions/TimedMediaHandler/i18n/nl.json b/extensions/TimedMediaHandler/i18n/nl.json new file mode 100644 index 00000000..cfe2115f --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/nl.json @@ -0,0 +1,108 @@ +{ + "@metadata": { + "authors": [ + "McDutchie", + "SPQRobin", + "Siebrand", + "Romaine" + ] + }, + "timedmedia-desc": "Handelt audio, video en ondertitels af met ondersteuning voor WebM, Ogg Theora, Vorbis en srt", + "timedmedia-ogg-short-audio": "Ogg $1 geluidsbestand, $2", + "timedmedia-ogg-short-video": "Ogg $1 videobestand, $2", + "timedmedia-ogg-short-general": "Ogg $1 mediabestand, $2", + "timedmedia-ogg-long-audio": "Ogg $1 geluidsbestand, lengte $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 videobestand, lengte $2, $4×$5 pixels, $3", + "timedmedia-ogg-long-multiplexed": "Ogg gemultiplexed geluids/videobestand, $1, lengte $2, $4×$5 pixels, $3 totaal", + "timedmedia-ogg-long-general": "Ogg mediabestand, lengte $2, $3", + "timedmedia-ogg-long-error": "Ongeldig Ogg-bestand: $1", + "timedmedia-webm-short-video": "WebM $1 videobestand, $2", + "timedmedia-webm-long-video": "WebM audio/videobestand, $1, lengte $2, $4x$5 pixels, $3 totaal", + "timedmedia-flac-short-audio": "FLAC-audiobestand, $1", + "timedmedia-flac-long-audio": "FLAC-audiobestand, lengte $1, $2 gemiddeld", + "timedmedia-wav-short-audio": "WAV-audiobestand, $1", + "timedmedia-wav-long-audio": "WAV-audiobestand, lengte $1, $2 gemiddeld", + "timedmedia-wav-pcm-required": "U kunt alleen PCM (Pulse Code Modulation) WAV uploaden.", + "timedmedia-mp4-short-video": "MP4 $1 videobestand, $2", + "timedmedia-mp4-long-video": "MP4 audio/videobestand, $1, lengte $2, $4x$5 pixels, $3 totaal", + "timedmedia-no-player-js": "Uw systeem heeft JavaScript uitgeschakeld of er is geen ondersteunde mediaspeler.
\nU kunt de clip downloaden of een mediaspeler downloaden om de clip af te spelen in uw browser.", + "timedmedia-more": "Meer…", + "timedmedia-dismiss": "Sluiten", + "timedmedia-download": "Bestand downloaden", + "timedmedia-play-media": "Media afspelen", + "timedmedia-desc-link": "Over dit bestand", + "timedmedia-oggThumb-version": "OggHandler vereist oggThumb versie $1 of hoger.", + "timedmedia-oggThumb-failed": "oggThumb kon geen miniatuur aanmaken.", + "timedmedia-status-header": "Conversiestatus", + "timedmedia-update-status": "Conversiestatus bijwerken", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Onbekende status", + "timedmedia-transcodeinfo": "Beschrijving van afgeleide conversie", + "timedmedia-actions": "Handelingen", + "timedmedia-direct-link": "Afgeleid werk downloaden", + "timedmedia-not-ready": "Niet klaar", + "timedmedia-completed-on": "Conversie voltooid op $1", + "timedmedia-error-on": "Fout in de conversie om $1", + "timedmedia-started-transcode": "De conversie is $1 geleden begonnen. $2", + "timedmedia-percent-done": "Ongeveer $1% afgerond", + "timedmedia-in-job-queue": "$1 geleden toegevoegd aan de Job queue", + "timedmedia-unknown-target-size": "Onbekende doelgrootte, $1 gecodeerd", + "timedmedia-days": "{{PLURAL:$1|1 dag|$1 dagen}}", + "timedmedia-hours": "{{PLURAL:$1|1 uur|$1 uur}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuut|$1 minuten}}", + "timedmedia-seconds": "{{PLURAL:$1|1 seconde|$1 seconden}}", + "timedmedia-reset": "Transcodering opnieuw instellen", + "timedmedia-reset-confirm": "Door de transcodering opnieuw in te stellen worden alle bestanden bestanden (als aanwezig) verwijderd en de transcodering wordt opnieuw toegevoegd aan de wachtrij. Transcoderen kost tijd.

\nWeet u zeker dat u wilt doorgaan?", + "timedmedia-reset-error": "Er is een fout opgetreden tijdens het opnieuw instellen van de transcoderingstaak.", + "timedmedia-ogg": "Ogg", + "timedmedia-webm": "WebM", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Bron van $1", + "timedmedia-source-file-desc": "Origineel $1-bestand, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Origineel $1-bestand ($2)", + "timedmedia-derivative-160p.ogv": "Ogg 160P", + "timedmedia-derivative-desc-160p.ogv": "Lage bandbreedte Ogg video (160P)", + "timedmedia-derivative-360p.ogv": "Ogg 360P", + "timedmedia-derivative-desc-360p.ogv": "Te webstreamen Ogg video (360P)", + "timedmedia-derivative-480p.ogv": "Ogg 480P", + "timedmedia-derivative-desc-480p.ogv": "Te webstreamen Ogg video (480P)", + "timedmedia-derivative-720p.ogv": "Ogg 720P", + "timedmedia-derivative-desc-720p.ogv": "Hoge kwaliteit downloadbare Ogg video (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Full HD downloadbare Ogg video (1080P)", + "timedmedia-derivative-desc-160p.webm": "Web streamable WebM (160P)", + "timedmedia-derivative-360p.webm": "WebM 360P", + "timedmedia-derivative-desc-360p.webm": "Te webstreamen WebM (360P)", + "timedmedia-derivative-480p.webm": "WebM 480P", + "timedmedia-derivative-desc-480p.webm": "Te webstreamen WebM (480P)", + "timedmedia-derivative-720p.webm": "WebM 720P", + "timedmedia-derivative-desc-720p.webm": "Hoge kwaliteit downloadbare WebM (720P)", + "timedmedia-derivative-desc-1080p.webm": "Full HD downloadbare WebM (1080P)", + "timedmedia-derivative-desc-320p.mp4": "Apparaatvriendelijke MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "Te webstreamen MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "HD-kwaliteit MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "Full HD-kwaliteit MP4 (1080P)", + "timedmedia-subtitle-new": "Nieuw vertaling maken of bestaande bewerken", + "timedmedia-subtitle-new-desc": "Selecteer een taal en klik op de knop '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "OK", + "timedmedia-subtitle-language": "Ondertitels in $1 ($2)", + "timedmedia-subtitle-no-video": "Er is geen video gekoppeld aan de huidige ondertitelpagina", + "timedmedia-subtitle-no-subtitles": "Er zijn op het moment geen ondertitels in het $1 voor deze video. U kunt [{{fullurl:{{FULLPAGENAME}}|action=edit}} deze pagina bewerken] om ze toe te voegen", + "timedmedia-subtitle-remote": "De ondertitels voor dit bestand komen van $1", + "timedmedia-subtitle-remote-link": "U kunt de [$1 beschrijvingspagina voor dit bestand] bekijken op $2", + "timedmediahandler": "Timed Media Handler", + "timedmedia-videos": "{{PLURAL:$1|één video|$1 video's}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|één Oggvideo|$1 Oggvideo's}}", + "timedmedia-webm-videos": "{{PLURAL:$1|één WebM-video|$1 WebM-video's}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|één transcodering|$1 transcoderingen}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|één lopende transcodering|$1 lopende transcoderingen}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|één transcodering|$1 transcoderingen}} in de wachtrij", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|één mislukte transcodering|$1 mislukte transcoderingen}}", + "timedmedia-file": "Bestand", + "timedmedia-audios": "{{PLURAL:$1|$1 audiobestand|$1 audiobestanden}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg audiobestand|$1 Ogg audiobestanden}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC audiobestand|$1 FLAC audiobestanden}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV audiobestand|$1 WAV audiobestanden}}", + "right-transcode-reset": "Video's waarvoor transcoderen mislukt is of die al getranscodeerd zijn opnieuw in de jobqueue plaatsen.", + "right-transcode-status": "[[Special:TimedMediaHandler|Gegevens over de huidige transcoderingsactiviteit]] bekijken", + "action-transcode-status": "huidige transcoderingsstatus te bekijken" +} diff --git a/extensions/TimedMediaHandler/i18n/nn.json b/extensions/TimedMediaHandler/i18n/nn.json new file mode 100644 index 00000000..cc6a3761 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/nn.json @@ -0,0 +1,41 @@ +{ + "@metadata": { + "authors": [ + "Eirik", + "Harald Khan", + "Njardarlogar", + "Ranveig" + ] + }, + "timedmedia-desc": "Handteringsprogram for ljod, video og tidbunden tekst, med formatstøtte for WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1-lydfil, $2", + "timedmedia-ogg-short-video": "Ogg $1-videofil, $2", + "timedmedia-ogg-short-general": "Ogg $1-mediafil, $2", + "timedmedia-ogg-long-audio": "Ogg $1-lydfil, lengd $2, $3", + "timedmedia-ogg-long-video": "Ogg $1-videofil, lengd $2, $4×$5 pikslar, $3", + "timedmedia-ogg-long-multiplexed": "Samansett ogg lyd-/videofil, $1, lengd $2, $4×$5 pikslar, $3 til saman", + "timedmedia-ogg-long-general": "Ogg mediafil, lengd $2, $3", + "timedmedia-ogg-long-error": "Ugyldig Ogg-fil: $1", + "timedmedia-no-player-js": "Orsak. Nettleseren din har anten ikkje JavaScript aktiv eller har ingen støtta spelar.
\nDu kan laste ned klippet eller laste ned ein spelar for å spele av klippet i nettlesaren din.", + "timedmedia-more": "Meir...", + "timedmedia-dismiss": "Lat att", + "timedmedia-download": "Last ned fila", + "timedmedia-play-media": "Spel av media", + "timedmedia-desc-link": "Om denne fila", + "timedmedia-actions": "Handlingar", + "timedmedia-not-ready": "Ikkje ferdig", + "timedmedia-days": "{{PLURAL:$1|1 dag|$1 dagar}}", + "timedmedia-hours": "{{PLURAL:$1|1 time|$1 timar}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minutt|$1 minutt}}", + "timedmedia-seconds": "{{PLURAL:$1|1 sekund|$1 sekund}}", + "timedmedia-source-file": "$1-kjelde", + "timedmedia-source-file-desc": "Opphavleg $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Opphavleg $1-fil ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg-video med låg bandbreidd (160P)", + "timedmedia-derivative-desc-360p.ogv": "Ogg-video som kan strøymast på nett (360P)", + "timedmedia-derivative-desc-480p.ogv": "Ogg-video som kan strøymast på nett (480P)", + "timedmedia-derivative-desc-720p.ogv": "Ogg-video av høg kvalitet som kan lastast ned (720P)", + "timedmedia-subtitle-new": "Lag ny omsetjing eller endra eksisterande", + "timedmedia-subtitle-new-desc": "Byt ut '''$1'''-delen med [[:en:ISO 639|språkkoden]] din og trykk på '''{{int:Timedmedia-subtitle-new-go}}'''-knappen", + "timedmedia-subtitle-new-go": "Gå" +} diff --git a/extensions/TimedMediaHandler/i18n/oc.json b/extensions/TimedMediaHandler/i18n/oc.json new file mode 100644 index 00000000..892307d8 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/oc.json @@ -0,0 +1,31 @@ +{ + "@metadata": { + "authors": [ + "Cedric31" + ] + }, + "timedmedia-desc": "Supòrt pels fichièrs Ogg Theora e Vorbis, amb un lector Javascript", + "timedmedia-ogg-short-audio": "Fichièr son Ogg $1, $2", + "timedmedia-ogg-short-video": "Fichièr vidèo Ogg $1, $2", + "timedmedia-ogg-short-general": "Fichièr mèdia Ogg $1, $2", + "timedmedia-ogg-long-audio": "Fichièr son Ogg $1, durada $2, $3", + "timedmedia-ogg-long-video": "Fichièr vidèo Ogg $1, durada $2, $4×$5 pixèls, $3", + "timedmedia-ogg-long-multiplexed": "Fichièr multiplexat àudio/vidèo Ogg, $1, durada $2, $4×$5 pixèls, $3", + "timedmedia-ogg-long-general": "Fichièr mèdia Ogg, durada $2, $3", + "timedmedia-ogg-long-error": "Fichièr Ogg invalid : $1", + "timedmedia-webm-short-video": "Fichièr vidèo WebM $1, $2", + "timedmedia-webm-long-video": "Fichièr àudio/vidèo WebM, $1, longor $2, $4 x $5 pixèls, $3 l'ensemble", + "timedmedia-flac-short-audio": "Fichièr àudio FLAC, $1", + "timedmedia-flac-long-audio": "Fichièr àudio FLAC, durada $1, debit $2 sus l’ensemble", + "timedmedia-wav-short-audio": "Fichièr àudio WAV, $1", + "timedmedia-no-player-js": "O planhèm, siá vòstre navigador a JavaScript de desactivat, siá dispausa pas de cap de lector de pres en carga.
\nPodètz telecargar lo clip o telecargar un lector per legir lo clip dins vòstre navigador.", + "timedmedia-more": "Mai…", + "timedmedia-dismiss": "Tampar", + "timedmedia-download": "Telecargar lo fichièr", + "timedmedia-desc-link": "A prepaus d'aqueste fichièr", + "timedmedia-source-file": "Font $1", + "timedmedia-source-audio-file-desc": "Fichièr original $1 ($2)", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-file": "Fichièr", + "right-transcode-reset": "Reïnicializar las vidèos en fracàs o transcodadas per que sián inseridas tornamai dins la fila dels trabalhs" +} diff --git a/extensions/TimedMediaHandler/i18n/olo.json b/extensions/TimedMediaHandler/i18n/olo.json new file mode 100644 index 00000000..7e3ad6b5 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/olo.json @@ -0,0 +1,18 @@ +{ + "@metadata": { + "authors": [ + "Mashoi7" + ] + }, + "timedmedia-no-player-js": "Pahakse mielekse sinun livaimes libo JavaScript ei ole käytös, libo sit ei ole tuvettuu soitindu.
\nVoit lad'd'ata palan libo lad'd'ata soittimen ku soittua pala sinun livaimes.", + "timedmedia-more": "Enämbi...", + "timedmedia-dismiss": "Salbua", + "timedmedia-play-media": "Soita medii", + "timedmedia-desc-link": "Täh failah näh", + "timedmedia-status": "Stuatussu", + "timedmedia-status-unknown": "Tundematoi stuatussu", + "timedmedia-source-file": "$1 lähteh", + "timedmedia-source-audio-file-desc": "Alguperäine $1 failu ($2)", + "timedmedia-subtitle-new-go": "Mene", + "timedmedia-file": "Failu" +} diff --git a/extensions/TimedMediaHandler/i18n/or.json b/extensions/TimedMediaHandler/i18n/or.json new file mode 100644 index 00000000..c2df361d --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/or.json @@ -0,0 +1,85 @@ +{ + "@metadata": { + "authors": [ + "Jnanaranjan Sahu", + "Psubhashish" + ] + }, + "timedmedia-desc": "ଅଡିଓ, ଭିଡ଼ିଓ ଏବଂ ସାମୟିକ ଲେଖା ଗୁଡିକ ପାଇଁ ୱେବଏମ, ଓଜିଜି ଥିଓରା, ଭୋରବିସ, ଏସଆରଟି (WebM, Ogg Theora, Vorbis, srt) ସହାୟତା ଥିବା ପରିଚାଳନା କାରୀ", + "timedmedia-ogg-short-audio": "Ogg $1 ଶବ୍ଦ ଫାଇଲ, $2", + "timedmedia-ogg-short-video": "Ogg $1 ଭିଡ଼ିଓ ଫାଇଲ, $2", + "timedmedia-ogg-short-general": "Ogg $1 ମିଡ଼ିଆ ଫାଇଲ, $2", + "timedmedia-ogg-long-audio": "Ogg $1 ଶବ୍ଦ ଫାଇଲ, ଲମ୍ବ $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 ଭିଡ଼ିଓ, ଲମ୍ବ $2, $4×$5 ପିକ୍ସେଲ, $3", + "timedmedia-ogg-long-multiplexed": "Ogg ମଲଟିପ୍ଲେକ୍ସ ଧ୍ୱନି/ଚିତ୍ର ଫାଇଲ, $1, ଲମ୍ବ $2, $4×$5 ପିକ୍ସେଲ, ମୋଟ $3", + "timedmedia-ogg-long-general": "Ogg ମିଡ଼ିଆ ଫାଇଲ, ଲମ୍ବ $2, $3", + "timedmedia-ogg-long-error": "ଅବୈଧ ogg ଫାଇଲ: $1", + "timedmedia-webm-short-video": "Ogg $1 ଭିଡ଼ିଓ ଫାଇଲ, $2", + "timedmedia-webm-long-video": "ogg ଅଡିଓ/ଭିଡ଼ିଓ ଫାଇଲ, $1, ଲମ୍ବ $2, $4×$5 ପିକ୍ସେଲ, ମୋଟ $3", + "timedmedia-mp4-short-video": "MP4 $1 ଭିଡ଼ିଓ ଫାଇଲ, $2", + "timedmedia-mp4-long-video": "MP4 ଅଡିଓ/ଭିଡ଼ିଓ ଫାଇଲ, $1, ଲମ୍ବ $2, $4×$5 ପିକ୍ସେଲ, ମୋଟ $3", + "timedmedia-no-player-js": "ଦୁଖୀତଃ, ଆପଣଙ୍କ ବ୍ରାଉଜରରେ ଜାଭାସ୍କ୍ରିପ୍ଟ ଅଚଳ ହୋଇଯାଇଛି କିମ୍ବା କିଛି ଖାପଖାଉଥିବା ପ୍ଲେଆର ନାହିଁ ।
\nଆପଣ ଏହି ଭିଡ଼ିଓଟିକୁ ଆପଣଙ୍କ ବ୍ରାଉଜରରେ ଚଲାଇବା ପାଇଁଭିଡିଓ କ୍ଲିପଟିକୁ ଡାଉନଲୋଡ କରିପାରିବେ କିମ୍ବା ଭିଡ଼ିଓ ପ୍ଲେୟାର ମଧ୍ୟ ଡାଉନଲୋଡ କରିପାରିବେ ।", + "timedmedia-more": "ଅଧିକ...", + "timedmedia-dismiss": "ବନ୍ଦ କରିବେ", + "timedmedia-download": "ଫାଇଲ ଡାଉନଲୋଡ଼", + "timedmedia-play-media": "ମେଡିଆଟିକୁ ଚଲେଇବେ", + "timedmedia-desc-link": "ଏହି ଫାଇଲ ବିଷୟରେ", + "timedmedia-oggThumb-version": "oggପରିଚାଳନାକାରୀ $1 ସଂସ୍କରଣ କିମ୍ବା ତାଠୁ ନୂତନତମ oggଥମ୍ବନେଲ ଆବଶ୍ୟକ କରେ", + "timedmedia-oggThumb-failed": "oggThumb ଛୋଟଛବି ତିଆରି କରିପାରିଲା ନାହିଁ ।", + "timedmedia-status-header": "ଟ୍ରାନ୍ସକୋଡ ଅବସ୍ଥା", + "timedmedia-update-status": "ଟ୍ରାନ୍ସକୋଡ ଅବସ୍ଥା ଅପଡେଟ କରିବେ", + "timedmedia-status": "ଅବସ୍ଥା", + "timedmedia-status-unknown": "ଅଜଣା ଅବସ୍ଥା", + "timedmedia-transcodeinfo": "ଟ୍ରାନ୍ସକୋଡ ଭଜନର ବିବରଣୀ", + "timedmedia-actions": "କାମସବୁ", + "timedmedia-direct-link": "ଭାଜକଗୁଡିକୁ ଡାଉନଲୋଡ କରିବେ", + "timedmedia-not-ready": "ପ୍ରସ୍ତୁତ ହୋଇନାହିଁ", + "timedmedia-completed-on": "$1 ଟ୍ରାନ୍ସକୋଡ ଶେଷହେଲା", + "timedmedia-error-on": "$1 ଟ୍ରାନ୍ସକୋଡରେ ଅସୁବିଧା ।", + "timedmedia-started-transcode": "ଟ୍ରାନ୍ସକୋଡ $1ରୁ ଆରମ୍ଭ ହେଲା । $2", + "timedmedia-percent-done": "ହାରାହାରି $1% ସରିଛି", + "timedmedia-in-job-queue": "$1 ପୃର୍ବରୁ କାମରେ ଯୋଡାଗଲା", + "timedmedia-unknown-target-size": "ଅଜଣା ଆକାର, $1 ଏନକୋଡ ହେଲା", + "timedmedia-days": "{{PLURAL:$1|$1 ଦିନ|$1 ଦିନଗୁଡିକ}}", + "timedmedia-hours": "($1 {{PLURAL:$1|ଘଣ୍ଟା| ଘଣ୍ଟା}})", + "timedmedia-minutes": "$1 {{PLURAL:$1|ମିନିଟ|ମିନିଟଗୁଡିକ}}", + "timedmedia-seconds": "{{PLURAL:$1|ସେକେଣ୍ଡ|ସେକଣ୍ଡ}}", + "timedmedia-reset": "ଟ୍ରାନ୍ସକୋଡ ପୁନଃସ୍ଥାପନ କରିବେ", + "timedmedia-reset-confirm": "ଟ୍ରାନ୍ସକୋଡ ପୁନଃସ୍ଥାପନା କରିବାଦ୍ଵାରା ଅଗରୁଥିବା ଫାଇଲ ହଟିଯିବ, ଏବଂ ଏହା ପ୍ରକ୍ରିୟାରେ ଟ୍ରାନ୍ସକୋଡକୁ ଆଉଥରେ ଯୋଡିଦେବ । ଏହା ପୁନଃ ଟ୍ରାନ୍ସକୋଡ ପାଇଁ କିଛି ସମୟ ନେବ ।\nଆପଣ ପ୍ରକ୍ରିୟାକୁ ଚାଲୁରଖିବା ପାଇଁ ନିଶ୍ଚିତ ?", + "timedmedia-reset-error": "ଟ୍ରାନ୍ସକୋଡ ପୁନଃସ୍ଥାପନ କରିବାରେ ଅସୁବଧା ଉପୁଜିଲା", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 ଉତ୍ସ", + "timedmedia-source-file-desc": "ମୂଳ $1 ଫାଇଲ, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "ମୂଳ $1 ଫାଇଲ ($2)", + "timedmedia-derivative-desc-160p.ogv": "କମ ବ୍ୟାଣ୍ଡୱିଥ ogg ଭିଡ଼ିଓ (160P)", + "timedmedia-derivative-desc-360p.ogv": "ୱେବରେ ସିଧାଚାଲିଲା ଭଳି Ogg ଭିଡିଓ (360p)", + "timedmedia-derivative-desc-480p.ogv": "ୱେବରେ ସିଧାଚାଲିଲା ଭଳି Ogg ଭିଡିଓ (480p)", + "timedmedia-derivative-desc-720p.ogv": "ୱେବରେ ସିଧାଚାଲିଲା ଭଳି Ogg ଭିଡିଓ (720p)", + "timedmedia-derivative-desc-160p.webm": "ୱେବରେ ସିଧାଚାଲିଲା ଭଳି WebM ଭିଡିଓ (160p)", + "timedmedia-derivative-desc-360p.webm": "ୱେବରେ ସିଧାଚାଲିଲା ଭଳି WebM ଭିଡିଓ (360p)", + "timedmedia-derivative-desc-480p.webm": "ୱେବରେ ସିଧାଚାଲିଲା ଭଳି WebM ଭିଡିଓ (480p)", + "timedmedia-derivative-desc-720p.webm": "ଭଲ ପ୍ରକାରଥିବା ଏବଂ ଡାଉନଲୋଡ ହୋଇପାରୁଥିବା WebM(720P)", + "timedmedia-derivative-desc-320p.mp4": "ଯନ୍ତ୍ର-ସହାୟକ ଏମପି4(320P)", + "timedmedia-derivative-desc-480p.mp4": "ୱେବରେ ସିଧାଚାଲିଲା ଭଳି MP4 ଭିଡିଓ (480p)", + "timedmedia-derivative-desc-720p.mp4": "ଏଚଡ଼ି ପ୍ରକାରର MP4 (720P)", + "timedmedia-subtitle-new": "ନୂଆ ଅନୁବାଦ ଆରମ୍ଭ କରିବେ କିମ୍ବା ଆଗରୁଥିବା ଅନୁବାଦକୁ ବଦଳାଇବେ", + "timedmedia-subtitle-new-desc": "ଭାଷା ଚୟନ କରନ୍ତୁ ଏବଂ {{int:Timedmedia-subtitle-new-go}}'''ବଟନ ଦବାନ୍ତୁ", + "timedmedia-subtitle-new-go": "ଯିବେ", + "timedmedia-subtitle-language": "$1 ($2) ସବଟାଇଟଲ", + "timedmedia-subtitle-no-video": "ବର୍ତମାନର ସବଟାଇଟଲ ପୃଷ୍ଠା ସହ କୌଣସି ଭିଡିଓ ସଂଶ୍ଳିଷ୍ଟ ନାହିଁ", + "timedmedia-subtitle-no-subtitles": "ବର୍ତମାନ $1ରେ ଏହି ଭିଡିଓ ପାଇଁ କୌଣସି ସବଟାଇଟଲ ନାହିଁ, ଆପଣ [{{fullurl:{{FULLPAGENAME}}|action=edit}} ବଦଳାଇ] ଏହାକୁ ଯୋଡି ପାରିବେ", + "timedmedia-subtitle-remote": "ଏହି ଫାଇଲ ପାଇଁ ସାମୟିକ ଲେଖାଗୁଡିକ $1ରେ ଅଛି", + "timedmedia-subtitle-remote-link": "ଆପଣ $2ରେ ଥିବା ଏହି ଫାଇଲ ପାଇଁ [$1 ବିବରଣୀ ପୃଷ୍ଠା ଦେଖିପାରିବେ]", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 ଭିଡିଓ|$1 ଭିଡିଓ ଗୁଡିକ}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg ଭିଡିଓ|$1 Ogg ଭିଡିଓ ଗୁଡିକ}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM ଭିଡିଓ|$1 WebM ଭିଡିଓ ଗୁଡିକ}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 ଟ୍ରାନ୍ସକୋଡ|$1 ଟ୍ରାନ୍ସକୋଡଗୁଡିକ}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 ଚାଲୁଥିବା ଟ୍ରାନ୍ସକୋଡ|$1 ଚାଲୁଥିବା ଟ୍ରାନ୍ସକୋଡଗୁଡିକ}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 ଧାଡିରେ ଥିବା ଟ୍ରାନ୍ସକୋଡ|$1 ଧାଡିରେ ଥିବା ଟ୍ରାନ୍ସକୋଡଗୁଡିକ}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 ବିଫଳ ଟ୍ରାନ୍ସକୋଡ|$1 ବିଫଳ ଟ୍ରାନ୍ସକୋଡଗୁଡିକ}}", + "timedmedia-file": "ଫାଇଲ", + "right-transcode-reset": "ବିଫଳ ହୋଇଥିବା କିମ୍ବା ଟ୍ରାନ୍ସକୋଡ ଭିଡିଓଗୁଡିକ ପୁନଃସ୍ଥାପନ କରିବେ ଯାହାଫଳରେକି ସେଗୁଡିକ ପୁଣି ପ୍ରକ୍ରିୟାକରଣ ହେବ ।", + "right-transcode-status": "[[Special:TimedMediaHandler|ଏବେକାର ଟ୍ରାନ୍ସକୋଡ ଗତିବିଧି]] ଦେଖିବେ", + "action-transcode-status": "ଏବେକାର ଟ୍ରାନ୍ସକୋଡ ସ୍ଥିତି ଦେଖିବେ" +} diff --git a/extensions/TimedMediaHandler/i18n/os.json b/extensions/TimedMediaHandler/i18n/os.json new file mode 100644 index 00000000..b5da6ba3 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/os.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Amikeco" + ] + }, + "timedmedia-more": "Фылдæр…", + "timedmedia-download": "Файл æрбавгæн" +} diff --git a/extensions/TimedMediaHandler/i18n/pa.json b/extensions/TimedMediaHandler/i18n/pa.json new file mode 100644 index 00000000..80ffc841 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/pa.json @@ -0,0 +1,13 @@ +{ + "@metadata": { + "authors": [ + "Gman124", + "Babanwalia", + "Satdeep gill" + ] + }, + "timedmedia-no-player-js": "ਮੁਆਫ਼ ਕਰਨਾ, ਜਾਂ ਤਾਂ ਤੁਹਾਡੇ ਬਰਾਊਜ਼ਰ ਉੱਤੇ ਜਾਵਾਸਕ੍ਰਿਪਟ ਚਾਲੂ ਨਹੀਂ ਜਾਂ ਫ਼ਿਰ ਕੋਈ ਹੋਰ ਸਹਾਇਕ ਪਲੇਅਰ ਮੌਜੂਦ ਨਹੀਂ ਹੈ।
\nਤੁਸੀਂ ਕਲਿੱਪ ਡਾਊਨਲੋਡ ਕਰ ਸਕਦੇ ਹੋ ਜਾਂ ਕੋਈ ਪਲੇਅਰ ਡਾਊਨਲੋਡ ਕਰਕੇ ਇਹ ਕਲਿੱਪ ਆਪਣੇ ਬਰਾਊਜ਼ਰ ਵਿੱਚ ਚਲਾ ਸਕਦੇ ਹੋ।", + "timedmedia-more": "ਹੋਰ...", + "timedmedia-source-file": "$1 ਸਰੋਤ", + "timedmedia-source-audio-file-desc": "ਮੂਲ $1 ਫ਼ਾਈਲ ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/pdc.json b/extensions/TimedMediaHandler/i18n/pdc.json new file mode 100644 index 00000000..d213f28c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/pdc.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Xqt" + ] + }, + "timedmedia-more": "Mehr…", + "timedmedia-download": "Feil runnerlaade" +} diff --git a/extensions/TimedMediaHandler/i18n/pfl.json b/extensions/TimedMediaHandler/i18n/pfl.json new file mode 100644 index 00000000..f4d3bbb6 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/pfl.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Manuae" + ] + }, + "timedmedia-no-player-js": "Endwedda hoschd JavaScript abgschdeld odda de Browsa hodd kä unnaschdizdi Abschbielsofdwaa.
\nKonschd de Glibb runnalaade odda ä Abschbielsofdwaa umde Glibb oagugge zu kenne.", + "timedmedia-source-file": "$1 Gwell", + "timedmedia-source-audio-file-desc": "Uaschbringlischi $1-Dadai ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/pl.json b/extensions/TimedMediaHandler/i18n/pl.json new file mode 100644 index 00000000..cbd0fac5 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/pl.json @@ -0,0 +1,101 @@ +{ + "@metadata": { + "authors": [ + "Ankry", + "Chrumps", + "Derbeth", + "Faren", + "Leinad", + "Matma Rex", + "Przemub", + "Rzuwig", + "Sp5uhe" + ] + }, + "timedmedia-desc": "Obsługa plików audio, wideo i napisów filmowych w formatach WebM, Ogg Theora, Vorbis i srt", + "timedmedia-ogg-short-audio": "Plik dźwiękowy Ogg $1, $2", + "timedmedia-ogg-short-video": "Plik wideo Ogg $1, $2", + "timedmedia-ogg-short-general": "Plik multimedialny Ogg $1, $2", + "timedmedia-ogg-long-audio": "plik dźwiękowy Ogg $1, długość $2, $3", + "timedmedia-ogg-long-video": "plik wideo Ogg $1, długość $2, rozdzielczość $4×$5, $3", + "timedmedia-ogg-long-multiplexed": "plik audio/wideo Ogg, $1, długość $2, rozdzielczość $4×$5, ogółem $3", + "timedmedia-ogg-long-general": "plik multimedialny Ogg, długość $2, $3", + "timedmedia-ogg-long-error": "niepoprawny plik Ogg: $1", + "timedmedia-webm-short-video": "WebM $1 plik wideo, $2", + "timedmedia-webm-long-video": "WebM plik audio-wideo, $1, długość $2, $4 × $5 pikseli, ogółem $3", + "timedmedia-flac-short-audio": "Plik audio FLAC, $1", + "timedmedia-flac-long-audio": "Plik dźwiękowy FLAC, długość $1, przepływność $2", + "timedmedia-wav-short-audio": "Plik audio WAV, $1", + "timedmedia-wav-long-audio": "Plik dźwiękowy WAV, długość $1, przepływność $2", + "timedmedia-no-player-js": "Niestety, Twoja przeglądarka ma wyłączoną obsługę JavaScript lub nie wspiera odtwarzania.
\nMożesz pobrać plik lub pobrać odtwarzacz pozwalający oglądać wideo w przeglądarce.", + "timedmedia-more": "Więcej...", + "timedmedia-dismiss": "Zamknij", + "timedmedia-download": "Pobierz plik", + "timedmedia-play-media": "Odtwórz plik multimedialny", + "timedmedia-desc-link": "Właściwości pliku", + "timedmedia-oggThumb-version": "OggHandler wymaga oggThumb w wersji $1 lub późniejszej.", + "timedmedia-oggThumb-failed": "oggThumb nie udało się utworzyć miniaturki.", + "timedmedia-status-header": "Status konwersji", + "timedmedia-update-status": "Zaktualizuj status konwersji", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Status nieznany", + "timedmedia-transcodeduration": "Czas kodowania", + "timedmedia-transcodeinfo": "Format", + "timedmedia-actions": "Operacje", + "timedmedia-direct-link": "Pobierz", + "timedmedia-not-ready": "Niegotowe", + "timedmedia-completed-on": "Ukończono $1", + "timedmedia-error-on": "Błąd o $1", + "timedmedia-started-transcode": "Konwersja rozpoczęła się $1 temu. $2", + "timedmedia-percent-done": "Ukończono ok. $1%", + "timedmedia-in-job-queue": "Dodano do kolejki $1 temu", + "timedmedia-unknown-target-size": "Rozmiar docelowy nieznany, $1 zakodowane", + "timedmedia-days": "$1 {{PLURAL:$1|dzień|dni}}", + "timedmedia-hours": "$1 {{PLURAL:$1|godzina|godziny|godzin}}", + "timedmedia-minutes": "$1 {{PLURAL:$1|minuta|minuty|minut}}", + "timedmedia-seconds": "$1 {{PLURAL:$1|sekunda|sekundy|sekund}}", + "timedmedia-reset": "Resetuj konwersję", + "timedmedia-reset-confirm": "Reset konwersji spowoduje usunięcie obecnego pliku (jeżeli takowy istnieje) i doda ją ponownie do kolejki. Zabierze to trochę czasu.

\nCzy na pewno chcesz kontynuować?", + "timedmedia-reset-error": "Błąd w resecie konwersji.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Źródło $1", + "timedmedia-source-file-desc": "Oryginalny plik $1, $2 x $3 ($4)", + "timedmedia-source-audio-file-desc": "Oryginalny plik $1 ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg video o niskiej przepustowości (160P)", + "timedmedia-derivative-desc-360p.ogv": "Ogg video o przepustowości sieciowej (360P)", + "timedmedia-derivative-desc-480p.ogv": "Ogg video o przepustowości sieciowej (480P)", + "timedmedia-derivative-desc-720p.ogv": "Ogg video o wysokiej jakości (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Do pobrania wideo Ogg z Full HD (1080p)", + "timedmedia-derivative-desc-160p.webm": "WebM w jakości sieciowej (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM w jakości sieciowej (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM w jakości sieciowej (480P)", + "timedmedia-derivative-desc-720p.webm": "WebM o wysokiej jakości (720P)", + "timedmedia-derivative-desc-1080p.webm": "Do pobrania WebM Full HD (1080p)", + "timedmedia-derivative-desc-320p.mp4": "MP4 odpowiednie dla tego urządzenia (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 w jakości sieciowej (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 w jakości HD (720P)", + "timedmedia-derivative-desc-1080p.mp4": "Jakości Full HD (1080p) MP4", + "timedmedia-subtitle-new": "Utwórz nowe tłumaczenie lub edytuj istniejące", + "timedmedia-subtitle-new-go": "Przejdź", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 film|$1 filmy|$1 filmów}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 film Ogg|$1 filmy Ogg|$1 filmów Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 film WebM|$1 filmy WebM|$1 filmów WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transkodowanie|$1 transkodowania|$1 transkodowań}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 trwające transkodowanie|$1 trwające transkodowania|$1 trwających transkodowań}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transkodowanie|$1 transkodowania|$1 transkodowań}} w kolejce", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 nieudane transkodowanie|$1 nieudane transkodowania|$1 nieudanych transkodowań}}", + "timedmedia-file": "Plik", + "timedmedia-audios": "{{PLURAL:$1|$1 plik dźwiękowy|$1 pliki dźwiękowe|plików dźwiękowych}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 plik dźwiękowy|$1 pliki dźwiękowe|plików dźwiękowych}} Ogg", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 plik dźwiękowy|$1 pliki dźwiękowe|plików dźwiękowych}} FLAC", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 plik dźwiękowy|$1 pliki dźwiękowe|plików dźwiękowych}} WAV", + "right-transcode-reset": "Resetowanie błędnych lub transkodowanych filmów, które zostały ponownie umieszczone w kolejce zadań", + "right-transcode-status": "Podgląd [[Special:TimedMediaHandler|informacji o aktualnym statusie transkodowania]]", + "action-transcode-status": "zobaczenia aktualnego statusu transkodowania", + "orphanedtimedtext": "Osierocone strony TimedText", + "orphanedtimedtext-unsupported": "To strona specjalna jest obsługiwana tylko przez bazy danych MySQL.", + "orphanedtimedtext-notimedtext": "TimedText nie jest włączony w tej wiki.", + "apihelp-query+videoinfo-example-1": "Pobierz informacji o [[:File:Folgers.ogv]]", + "apihelp-transcodereset-param-title": "Tytuł pliku multimedialnego." +} diff --git a/extensions/TimedMediaHandler/i18n/pms.json b/extensions/TimedMediaHandler/i18n/pms.json new file mode 100644 index 00000000..62681327 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/pms.json @@ -0,0 +1,96 @@ +{ + "@metadata": { + "authors": [ + "Borichèt", + "Bèrto 'd Sèra", + "Dragonòt", + "පසිඳු කාවින්ද" + ] + }, + "timedmedia-desc": "Gestor për ij son, filmà e test a sincronisà, con manteniment dij formà për WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Registrassion Ogg $1, $2", + "timedmedia-ogg-short-video": "Film Ogg $1, $2", + "timedmedia-ogg-short-general": "Archivi Multimojen Ogg $1, $2", + "timedmedia-ogg-long-audio": "Registrassion Ogg $1, ch'a dura $2, $3", + "timedmedia-ogg-long-video": "Film Ogg $1, ch'a dura $2, formà $4×$5 px, $3", + "timedmedia-ogg-long-multiplexed": "Archivi audio/video multiplessà Ogg, $1, ch'a dura $2, formà $4×$5 px, $3 an tut", + "timedmedia-ogg-long-general": "Archivi multimojen Ogg, ch'a dura $2, $3", + "timedmedia-ogg-long-error": "Archivi ogg nen bon: $1", + "timedmedia-webm-short-video": "Archivi filmà WebM $1, $2", + "timedmedia-webm-long-video": "WebM archivi sonor/filmà, $1, longheur $2, $4 x $5 pontin, $3 an tut", + "timedmedia-flac-short-audio": "Archivi sonor FLAC, $1", + "timedmedia-flac-long-audio": "Archivi sonor FLAC, durà $1, ritm $2 an sël total", + "timedmedia-wav-short-audio": "Archivi sonor WAV, $1", + "timedmedia-wav-long-audio": "Archivi sonor WAV, durà $1, $2 sël total", + "timedmedia-wav-pcm-required": "A peul mach carié dij WAV PCM (Modolassion d'ampulsion codificà).", + "timedmedia-mp4-short-video": "Archivi filmà MP4 $1, $2", + "timedmedia-mp4-long-video": "MP4 archivi sonor/filmà, $1, longheur $2, $4 x $5 pontin, $3 an tut", + "timedmedia-no-player-js": "Darmagi, sò navigador a l'ha JavaScript disabilità o a manten pa ël riprodutor.
\nA peul dëscarié la senëtta o dëscarié un riprodutor për visualisé la senëtta su sò navigador.", + "timedmedia-more": "Dë pì...", + "timedmedia-dismiss": "sëré", + "timedmedia-download": "Dëscarié l'archivi", + "timedmedia-play-media": "Lese ël mojen", + "timedmedia-desc-link": "Rësgoard a st'archivi", + "timedmedia-oggThumb-version": "OggHandler a ciama la version $1 d'oggThumb o pi agiornà.", + "timedmedia-oggThumb-failed": "oggThumb a l'ha falì a creé la figurin-a.", + "timedmedia-status-header": "Statù ëd trascodìfica", + "timedmedia-update-status": "Agiorné lë statù ëd trascodìfica", + "timedmedia-status": "Stat", + "timedmedia-status-unknown": "Stat pa conossù", + "timedmedia-transcodeinfo": "Descrission derivà da la trascodìfica", + "timedmedia-actions": "Assion", + "timedmedia-direct-link": "Dëscarié ël derivà", + "timedmedia-not-ready": "Pa pront", + "timedmedia-completed-on": "Trascodìfica completà $1", + "timedmedia-error-on": "Eror ant la trascodìfica ëd $1", + "timedmedia-started-transcode": "Trascodìfica ancaminà $1 fà. $2", + "timedmedia-percent-done": "Sirca $1% fàit", + "timedmedia-in-job-queue": "Giontà a la coa dij Travaj $1 fà", + "timedmedia-unknown-target-size": "Dimension ëd la destinassion pa conossùa, $1 codificà", + "timedmedia-days": "{{PLURAL:$1|1 di|$1 di}}", + "timedmedia-hours": "{{PLURAL:$1|1 ora|$1 ore}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuta|$1 minute}}", + "timedmedia-seconds": "{{PLURAL:$1|1 second|$1 second}}", + "timedmedia-reset": "Amposté torna la trascodìfica", + "timedmedia-reset-confirm": "Amposté torna sa trascodìfica a tramuderà tut archivi esistent (s'a-i na j'é) e a gionterà torna la trascodìfica a la coa dij travaj. A-i andrà dël temp a trascodifiché torna.

\nÉ-lo sigur ëd vorèj continué?", + "timedmedia-reset-error": "Eror an ampostand torna ël travaj ëd trascodìfica.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 sorziss", + "timedmedia-source-file-desc": "Archivi original $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Archivi original $1 ($2)", + "timedmedia-derivative-desc-160p.ogv": "Larghëssa ëd banda bassa Ogg video (160P)", + "timedmedia-derivative-desc-360p.ogv": "Web streamable Ogg video (360P)", + "timedmedia-derivative-desc-480p.ogv": "Filmà Ogg lesìbil an continuà an sl'aragnà (480P)", + "timedmedia-derivative-desc-720p.ogv": "Filmà Ogg dëscariàbil d'àuta qualità (720P)", + "timedmedia-derivative-desc-160p.webm": "WebM lesìbil an continuà an sl'aragnà (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM lesìbil an continuà an sl'aragnà (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM lesìbil an continuà an sl'aragnà (480P)", + "timedmedia-derivative-desc-720p.webm": "WebM dëscariàbil d'àuta qualità (720P)", + "timedmedia-derivative-desc-320p.mp4": "Dispositiv compatìbil MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 lesìbil an continuà an sl'aragnà (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 an qualità HD (720P)", + "timedmedia-subtitle-new": "Creé na neuva tradussion o modifichene un-a esistenta", + "timedmedia-subtitle-new-desc": "Ch'a selession-a la lenga e ch'a sgnaca an sël boton '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Va", + "timedmedia-subtitle-language": "$1 ($2) sot-tìtoj", + "timedmedia-subtitle-no-video": "A-i é gnun filmà associà con la pagina corenta dël sot-tìtol", + "timedmedia-subtitle-no-subtitles": "A-i é al moment gnun sot-tìtoj an $1 për ës filmà, a peul [{{fullurl:{{FULLPAGENAME}}|action=edit}} modifiché costa pàgina] për gionteje", + "timedmedia-subtitle-remote": "Ël test previst për cost archivi a l'é ospità dzor $1", + "timedmedia-subtitle-remote-link": "A peul [$1 vëdde la pàgina ëd descrission] ëd s'archivi dzor $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 filmà}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 filmà Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 filmà WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 trascodìfica|$1 trascodìfiche}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 trascodìfica|$1 trascodìfiche}} an esecussion", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 trascodìfica|$1 trascodìfiche}} an coa", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 trascodìfica falìa|$1 trascodìfiche falìe}}", + "timedmedia-file": "Archivi", + "timedmedia-audios": "{{PLURAL:$1|$1 archivi sonor}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 archivi sonor Ogg}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 archivi sonor FLAC}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 archivi sonor WAV}}", + "right-transcode-reset": "Amposté torna ij filmà falì o trascodificà për ch'a sio torna anserì ant la coa dij travaj.", + "right-transcode-status": "Smon-e [[Special:TimedMediaHandler|l'anformassion a propòsit ëd l'atività corenta ëd trascodìfica]]", + "action-transcode-status": "varda lë stat ëd trascodìfica corent" +} diff --git a/extensions/TimedMediaHandler/i18n/pnb.json b/extensions/TimedMediaHandler/i18n/pnb.json new file mode 100644 index 00000000..3ff940e9 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/pnb.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Khalid Mahmood" + ] + }, + "timedmedia-no-player-js": "تواڈے براؤزر تے جاواسکرپٹ کم نہیں کر ریا یا فیر ہور پلئیر نہیں
\nتسیں کلپ ڈاؤنلوڈ کرسکدے او یا یا اپنے براؤزر وچ کلپ چلان لئی پلیئر ڈاؤنلوڈ کرو .", + "timedmedia-source-file": "$1 مڈھ", + "timedmedia-source-audio-file-desc": "اصلی$1 فائل ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/ps.json b/extensions/TimedMediaHandler/i18n/ps.json new file mode 100644 index 00000000..f602ceb3 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ps.json @@ -0,0 +1,29 @@ +{ + "@metadata": { + "authors": [ + "Ahmed-Najib-Biabani-Ibrahimkhel" + ] + }, + "timedmedia-ogg-short-audio": "Ogg $1 غږيزه دوتنه، $2", + "timedmedia-ogg-short-video": "Ogg $1 ويډيويي دوتنه، $2", + "timedmedia-ogg-short-general": "Ogg $1 رسنيزه دوتنه، $2", + "timedmedia-no-player-js": "بخښنه دې وي، يا خو ستاسې په کتنمل باندې جاواسکرېپټ ناچارن دی او يا هم تاسې کوم ملاتړلی غږونکی نه لری.
\nتاسې کلېپ ښکته کولی شئ او يا هم يو داسې غږونکی ښکته کړئ چې کلېپ په کتنمل باندې وغږوي.", + "timedmedia-more": "نور...", + "timedmedia-dismiss": "تړل", + "timedmedia-download": "دوتنه ښکته کول", + "timedmedia-play-media": "رسنۍ غږول", + "timedmedia-desc-link": "د همدې دوتنې په اړه", + "timedmedia-status": "دريځ", + "timedmedia-transcodeinfo": "بڼه", + "timedmedia-actions": "چارې", + "timedmedia-direct-link": "ښکته کول", + "timedmedia-days": "{{PLURAL:$1|1 ورځ|$1 ورځې}}", + "timedmedia-hours": "{{PLURAL:$1|1 گړۍ|$1 گړۍ}}", + "timedmedia-minutes": "{{PLURAL:$1|1 دقيقه|دقيقې}}", + "timedmedia-source-file": "$1 سرچينه", + "timedmedia-source-audio-file-desc": "اصلي $1 دوتنه ($2)", + "timedmedia-subtitle-new-go": "ورځه", + "timedmedia-videos": "{{PLURAL:$1|$1 ويډيو|$1 ويډيوگانې}}", + "timedmedia-file": "دوتنه", + "timedmedia-audios": "{{PLURAL:$1|$1 غږيزه دوتنه|$1 غږيزې دوتنې}}" +} diff --git a/extensions/TimedMediaHandler/i18n/pt-br.json b/extensions/TimedMediaHandler/i18n/pt-br.json new file mode 100644 index 00000000..99b96b5e --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/pt-br.json @@ -0,0 +1,40 @@ +{ + "@metadata": { + "authors": [ + "Eduardo.mps", + "Giro720", + "Luckas", + "Opraco", + "Guilhermemau", + "Fasouzafreitas" + ] + }, + "timedmedia-desc": "Manipulador para arquivos Ogg Theora e Vorbis, com reprodutor JavaScript", + "timedmedia-ogg-short-audio": "Arquivo de áudio Ogg $1, $2", + "timedmedia-ogg-short-video": "Arquivo de vídeo Ogg $1, $2", + "timedmedia-ogg-short-general": "Arquivo multimídia Ogg $1, $2", + "timedmedia-ogg-long-audio": "arquivo de áudio Ogg $1, $2 de duração, $3", + "timedmedia-ogg-long-video": "Vídeo Ogg $1, $2 de duração, $4×$5 pixels, $3", + "timedmedia-ogg-long-multiplexed": "Áudio/vídeo Ogg multifacetado, $1, $2 de duração, $4×$5 pixels, $3 no todo", + "timedmedia-ogg-long-general": "Multimídia Ogg, $2 de duração, $3", + "timedmedia-ogg-long-error": "Arquivo ogg inválido: $1", + "timedmedia-no-player-js": "Desculpe, seu navegador ou está com JavaScript desabilitado ou não tem nenhum \"player\" suportado.
\nVocê pode descarregar o clipe ou descarregar um \"player\" para executar o clipe em seu navegador.", + "timedmedia-more": "Mais...", + "timedmedia-dismiss": "Fechar", + "timedmedia-download": "Descarregar arquivo", + "timedmedia-desc-link": "Sobre este arquivo", + "timedmedia-oggThumb-version": "O oggHandler requer o oggThumb versão $1 ou posterior.", + "timedmedia-oggThumb-failed": "O oggThumb não conseguiu criar a miniatura.", + "timedmedia-actions": "Ações", + "timedmedia-days": "{{PLURAL:$1|1 dia|$1 dias}}", + "timedmedia-hours": "{{PLURAL:$1|1 hora|$1 horas}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuto|$1 minutos}}", + "timedmedia-seconds": "{{PLURAL:$1|1 segundo|$1 segundos}}", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Fonte $1", + "timedmedia-source-audio-file-desc": "Arquivo $1 original ($2)", + "timedmedia-derivative-desc-1080p.webm": "WebM para download de Full HD (1080p)", + "timedmedia-derivative-desc-1080p.mp4": "Qualidade total do HD MP4 (1080 P)", + "timedmedia-subtitle-new-go": "Ir", + "timedmedia-file": "Arquivo" +} diff --git a/extensions/TimedMediaHandler/i18n/pt.json b/extensions/TimedMediaHandler/i18n/pt.json new file mode 100644 index 00000000..379e2271 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/pt.json @@ -0,0 +1,102 @@ +{ + "@metadata": { + "authors": [ + "Giro720", + "Hamilton Abreu", + "Luckas", + "Malafaya", + "Opraco", + "Vitorvicentevalente", + "Waldir", + "555", + "Tooguether", + "Macofe", + "Fúlvio" + ] + }, + "timedmedia-desc": "Tratamento de áudio, vídeo e legendagem, nos formatos WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Áudio Ogg $1, $2", + "timedmedia-ogg-short-video": "Vídeo Ogg $1, $2", + "timedmedia-ogg-short-general": "Multimédia Ogg $1, $2", + "timedmedia-ogg-long-audio": "áudio Ogg $1, $2 de duração, $3", + "timedmedia-ogg-long-video": "Vídeo Ogg $1, $2 de duração, $4×$5 pixels, $3", + "timedmedia-ogg-long-multiplexed": "Áudio/vídeo Ogg multifacetado, $1, $2 de duração, $4×$5 pixels, $3 no todo", + "timedmedia-ogg-long-general": "Multimédia Ogg, $2 de duração, $3", + "timedmedia-ogg-long-error": "Ficheiro ogg inválido: $1", + "timedmedia-ogg-long-no-streams": "Arquivo de mídia OGG. Aviso: Nenhum dos codecs utilizados neste arquivo são reconhecidos.", + "timedmedia-webm-short-video": "Vídeo WebM $1, $2", + "timedmedia-webm-long-video": "Áudio/vídeo WebM, $1, $2 de duração, $4 × $5 pixels, $3 no todo", + "timedmedia-flac-short-audio": "Ficheiro de áudio FLAC, $1", + "timedmedia-flac-long-audio": "Ficheiro de áudio FLAC, duração $1, $2", + "timedmedia-wav-short-audio": "Ficheiro de áudio WAV, $1", + "timedmedia-wav-long-audio": "Ficheiro de áudio WAV, duração $1, $2", + "timedmedia-mp4-short-video": "Vídeo MP4 $1, $2", + "timedmedia-mp4-long-video": "Áudio/vídeo MP$, $1, duração $2, $4 × $5 pixels, $3", + "timedmedia-no-player-js": "Desculpe, mas ou o seu navegador está com o JavaScript desactivado ou não tem nenhum dos leitores suportados.
\nPode fazer a descarga do vídeo ou o de um leitor para assistir ao vídeo no seu navegador.", + "timedmedia-more": "Mais...", + "timedmedia-dismiss": "Fechar", + "timedmedia-download": "Fazer descarga do ficheiro", + "timedmedia-play-media": "Reproduzir conteúdo", + "timedmedia-desc-link": "Sobre este ficheiro", + "timedmedia-oggThumb-version": "O oggHandler requer a versão $1 do oggThumb ou posterior.", + "timedmedia-oggThumb-failed": "O oggThumb não conseguiu criar a miniatura.", + "timedmedia-status-header": "Estado da transcodificação", + "timedmedia-update-status": "Actualizar o estado da transcodificação", + "timedmedia-status": "Estado", + "timedmedia-status-unknown": "Estado desconhecido", + "timedmedia-transcodeinfo": "Formato", + "timedmedia-actions": "Acções", + "timedmedia-direct-link": "Descarregar", + "timedmedia-not-ready": "Ainda não está pronto", + "timedmedia-completed-on": "Concluída a $1", + "timedmedia-error-on": "Erro em $1", + "timedmedia-started-transcode": "Iniciou há $1. $2", + "timedmedia-percent-done": "O progresso é cerca de $1%", + "timedmedia-in-job-queue": "Adicionado à fila de tarefas há $1", + "timedmedia-unknown-target-size": "Dimensão do destino desconhecida; $1 codificados", + "timedmedia-days": "{{PLURAL:$1|1 dia|$1 dias}}", + "timedmedia-hours": "{{PLURAL:$1|1 hora|$1 horas}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuto|$1 minutos}}", + "timedmedia-seconds": "{{PLURAL:$1|1 segundo|$1 segundos}}", + "timedmedia-reset": "Reiniciar transcodificação", + "timedmedia-reset-confirm": "Reiniciar esta transcodificação elimina qualquer ficheiro existente e volta a adicionar a transcodificação à fila de tarefas. A nova transcodificação irá demorar algum tempo.

Tem a certeza de que pretende continuar?", + "timedmedia-reset-error": "Ocorreu um erro ao reiniciar a tarefa de transcodificação", + "timedmedia-source-file": "Fonte $1", + "timedmedia-source-file-desc": "Ficheiro $1 original, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Ficheiro $1 original ($2)", + "timedmedia-derivative-desc-160p.ogv": "Vídeo Ogg de baixa largura de banda (160P)", + "timedmedia-derivative-desc-360p.ogv": "Vídeo Ogg para web streaming (360P)", + "timedmedia-derivative-desc-480p.ogv": "Vídeo Ogg para web streaming (480P)", + "timedmedia-derivative-desc-720p.ogv": "Vídeo Ogg de alta qualidade para download (720 P)", + "timedmedia-derivative-desc-360p.webm": "WebM para web streaming (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM para web streaming (480P)", + "timedmedia-derivative-desc-720p.webm": "WebM de alta qualidade para download (720P)", + "timedmedia-derivative-desc-720p.mp4": "Qualidade HD MP4 (720P)", + "timedmedia-subtitle-new": "Criar nova tradução ou editar existente", + "timedmedia-subtitle-new-desc": "Seleccione o idioma e pressione o botão '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Enviar", + "timedmedia-subtitle-language": "Legendas em $1 ($2)", + "timedmedia-subtitle-no-video": "Não existe nenhum vídeo associado à actual página de legendas", + "timedmedia-subtitle-no-subtitles": "Neste momento não existem legendas em $1 para este vídeo. Pode [{{fullurl:{{FULLPAGENAME}}|action=edit}} editar a página] e adicioná-las.", + "timedmedia-subtitle-remote": "A legenda para este ficheiro está hospedada em $1", + "timedmedia-subtitle-remote-link": "Pode [$1 ver a página de descrição] para este ficheiro em $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 vídeo|$1 vídeos}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 vídeo ogg|$1 vídeos ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 vídeo WebM|$1 vídeos WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transcodificação|$1 transcodificações}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 transcodificação em andamento|$1 transcodificações em andamento}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transcodificação em espera|$1 transcodificações em espera}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 transcodificação falhada|$1 transcodificações falhadas}}", + "timedmedia-file": "Ficheiro", + "timedmedia-audios": "{{PLURAL:$1|$1 ficheiro de áudio|$1 ficheiros de áudio}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 ficheiro de áudio Ogg|$1 ficheiros de áudio Ogg}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 ficheiro de áudio FLAC|$1 ficheiros de áudio FLAC}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 ficheiro de áudio WAV|$1 ficheiros de áudio WAV}}", + "right-transcode-status": "Ver [[Special:TimedMediaHandler|informação sobre a actividade actual de transcodificação]]", + "action-transcode-status": "ver o estado actual de transcodificação", + "orphanedtimedtext": "Páginas Legendagem órfãs", + "orphanedtimedtext-summary": "Lista de páginas [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] que não têm um ficheiro correspondente.", + "orphanedtimedtext-unsupported": "Esta página especial só é suportada nas bases de dados MySQL.", + "orphanedtimedtext-notimedtext": "O domínio Legendagem não está habilitado nesta wiki." +} diff --git a/extensions/TimedMediaHandler/i18n/qqq.json b/extensions/TimedMediaHandler/i18n/qqq.json new file mode 100644 index 00000000..4fe17fe8 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/qqq.json @@ -0,0 +1,172 @@ +{ + "@metadata": { + "authors": [ + "Aotake", + "BrokenArrow", + "EugeneZelenko", + "Fryed-peach", + "Jon Harald Søby", + "Meno25", + "Minh Nguyen", + "Mormegil", + "Purodha", + "Raymond", + "Shirayuki", + "Siebrand", + "Umherirrender", + "아라", + "Liuxinyu970226", + "Amire80", + "Robby" + ] + }, + "extensionname-timedmedia": "{{optional}}", + "timedmedia-desc": "{{desc|name=Timed Media Handler|url=https://www.mediawiki.org/wiki/Extension:TimedMediaHandler}}", + "timedmedia-ogg-short-audio": "File details for Ogg sound (audio) files, short version.\nParameters:\n* $1 - stream type name. Any one of the following: Vorbis, Speex, FLAC\n* $2 - duration of the sound/audio (localized) - e.g. 1m34s\n{{Related|Timedmedia-ogg-short}}", + "timedmedia-ogg-short-video": "File details for Ogg video files, short version.\nParameters:\n* $1 - stream type name: Theora\n* $2 - duration of the video (localized) - e.g. 1m34s\n{{Related|Timedmedia-ogg-short}}", + "timedmedia-ogg-short-general": "File details for generic (non-audio, non-video) Ogg files, short version.\nParameters:\n* $1 - stream type name\n* $2 - duration of the media (localized) - e.g. 1m34s\n{{Related|Timedmedia-ogg-short}}", + "timedmedia-ogg-long-audio": "File details for Ogg sound (audio) files, long version.\n\nShown after the filename in the image description page.\n\nParameters:\n* $1 - stream type name. Any one of the following: Vorbis, Speex, FLAC\n* $2 - duration of the sound (localized) - e.g. 1m34s\n* $3 - bit-rate of the sound (localized) - e.g. 97kbps\n{{Related|Timedmedia-ogg-long}}", + "timedmedia-ogg-long-video": "File details for Ogg video files, long version.\n\nShown after the filename in the image description page.\n\nParameters:\n* $1 - stream type name: Theora\n* $2 - duration of the video (localized) - e.g. 1m34s\n* $3 - bit-rate of the video (localized) - e.g. 97kbps\n* $4 - width of the video (in pixels)\n* $5 - height of the video (in pixels)\n{{Related|Timedmedia-ogg-long}}", + "timedmedia-ogg-long-multiplexed": "{{doc-important|Start with a lowercase letter, unless the first word is \"Ogg\".}}\nFile details for Ogg multiplexed audio/video files, long version.\n\nShown after the filename in the image description page.\n\nParameters:\n* $1 - stream type names, slash-separated - e.g. Theora/Vorbis\n* $2 - duration (localized) - e.g. 1m34s\n* $3 - bit-rate (localized) - e.g. 97kbps\n* $4 - width of the video (in pixels)\n* $5 - height of the video (in pixels)\n{{Related|Timedmedia-ogg-long}}", + "timedmedia-ogg-long-general": "File details for Ogg generic (non-video, non-audio) files, long version.\n\nShown after the filename in the image description page.\n\nParameters:\n* $1 - (Unused)\n* $2 - duration (localized) - e.g. 1m34s\n* $3 - bit-rate (localized) - e.g. 97kbps\n{{Related|Timedmedia-ogg-long}}", + "timedmedia-ogg-long-error": "Used as error message. Parameters:\n* $1 - error message. e.g. File not found, invalid stream, etc.", + "timedmedia-ogg-long-no-streams": "Similar to {{msg-mw|timedmedia-ogg-long-error}}. Used as the long description for an ogg media file if there are no streams in the file using a recognized codec. This can be caused by an invalid ogg file, or by one using an obscure type of data (e.g. dirac video with no audio).", + "timedmedia-webm-short-video": "File details for WebM video files, short version.\nParameters:\n* $1 - stream type names (slash separated) - e.g. Vorbis/VP8\n* $2 - duration of the video (localized) - e.g. 1m34s\nSee also:\n* {{msg-mw|Timedmedia-webm-long-video}}", + "timedmedia-webm-long-video": "File details for WebM multiplexed audio/video files, long version.\n\nShown after the filename in the image description page.\n\nParameters:\n* $1 - stream type names (slash separated) - e.g. Vorbis/VP8\n* $2 - duration (localized) - e.g. 1m34s\n* $3 - bit-rate (localized) - e.g. 97kbps\n* $4 - width of the video (in pixels)\n* $5 - height of the video (in pixels)\nSee also:\n* {{msg-mw|Timedmedia-webm-short-video}}", + "timedmedia-flac-short-audio": "File details for FLAC audio files, short version.\nParameters:\n* $1 - duration of the audio (localized) - e.g. 1m34s\n\nPrimarily used on [[Special:Search]] results pages, i.e.\n https://commons.wikimedia.org/w/index.php?title=Special%3ASearch&profile=advanced&search=What%27s+a+love+dart&fulltext=Search&ns6=1&profile=advanced\n\nSee also:\n* {{msg-mw|Timedmedia-flac-long-audio}}\n* {{msg-mw|Timedmedia-ogg-short-audio}}", + "timedmedia-flac-long-audio": "File details for FLAC files, long version.\n\nShown after the filename in the image description page.\n\nParameters:\n* $1 - duration (localized) - e.g. 1m34s\n* $2 - bit-rate (localized) - e.g. 97kbps\nSee also:\n* {{msg-mw|Timedmedia-flac-short-audio}}", + "timedmedia-wav-short-audio": "File details for WAV audio files, short version.\nParameters:\n* $1 - duration of the audio (localized) - e.g. 1m34s\nSee also:\n* {{msg-mw|Timedmedia-wav-long-audio}}", + "timedmedia-wav-long-audio": "File details for WAV files, long version.\nShown after the filename in the image description page.\n\nParameters:\n* $1 - duration of the audio (localized) - e.g. 1m34s\n* $2 - bit-rate (localized) - e.g. 97kbps\nSee also:\n* {{msg-mw|Timedmedia-wav-short-audio}}", + "timedmedia-wav-pcm-required": "Message shown at upload if user tries to upload a WAV file using a codec that is not PCM", + "timedmedia-mp4-short-video": "File details for MP4 video files, short version.\nParameters:\n* $1 - stream type names (slash separated) - e.g. AAC/h.264\n* $2 - duration of the video (localized) - e.g. 1m34s\nSee also:\n* {{msg-mw|Timedmedia-mp4-long-video}}", + "timedmedia-mp4-long-video": "File details for MP4 multiplexed audio/video files, long version.\n\nShown after the filename in the image description page.\n\nParameters:\n* $1 - stream type names (slash separated) - e.g. AAC/h.264\n* $2 - duration (localized) - e.g. 1m34s\n* $3 - bit-rate (localized) - e.g. 97kbps\n* $4 - width of the video (in pixels)\n* $5 - height of the video (in pixels)\nSee also:\n* {{msg-mw|Timedmedia-mp4-short-video}}", + "timedmedia-no-player-js": "Used as fallback text displayed for browsers without js and without video tag support.\n\nParameters:\n* $1 - media source URL", + "timedmedia-more": "Unused at this time.\n{{Identical|More...}}", + "timedmedia-dismiss": "Unused at this time.\n{{Identical|Close}}", + "timedmedia-download": "Used as tooltip for the Download button.\n{{Identical|Download}}", + "timedmedia-play-media": "Used as tooltip for the link.", + "timedmedia-desc-link": "Unused at this time.\n{{Identical|About this file}}", + "timedmedia-oggThumb-version": "Used as error message when the specified option is invalid. Parameters:\n* $1 - version number \"0.9\" (hard-coded)", + "timedmedia-oggThumb-failed": "{{doc-important|Do not translate oggThumb.}}\nUsed as error message when executing oggThumb.", + "timedmedia-status-header": "Used as section heading.", + "timedmedia-update-status": "Used as text for the link which is used to purge.", + "timedmedia-status": "Used as column header.\n{{Identical|Status}}", + "timedmedia-status-unknown": "Used as status message on error.\n{{Identical|Unknown status}}", + "timedmedia-transcodebitrate": "A table column header for description of transcode bitrate", + "timedmedia-transcodeduration": "A table column header for description of transcode encoding time", + "timedmedia-transcodeinfo": "A table column header for description of Transcode derivative\n{{Identical|Format}}", + "timedmedia-actions": "Used as column header.\n{{Identical|Action}}", + "timedmedia-direct-link": "Used as column header.\n{{Identical|Download}}", + "timedmedia-not-ready": "State of a given transcode job being not yet complete or not yet ready", + "timedmedia-completed-on": "Completed transcode message. Parameters:\n* $1 - the timestamp (time and date) that the transcode was completed", + "timedmedia-error-on": "Parameters:\n* $1 - timestamp", + "timedmedia-started-transcode": "Status update for Transcodes. Parameters:\n* $1 - time passed since transcoded started (e.g. \"{{int:timedmedia-minutes|1}}{{int:comma-separator}}{{int:timedmedia-seconds|42}}\"), uses the following messages:\n** {{msg-mw|Timedmedia-days}}\n** {{msg-mw|Timedmedia-hours}}\n** {{msg-mw|Timedmedia-minutes}}\n** {{msg-mw|Timedmedia-seconds}}\n** {{msg-mw|Comma-separator}}\n* $2 - percentage of transcode complete", + "timedmedia-percent-done": "Commented out at this time.\n\nStatus update for Transcodes. Parameters:\n* $1 - percentage of the file transcoded so far\nSee also:\n* {{msg-mw|timedmedia-unknown-target-size}}", + "timedmedia-in-job-queue": "Shown on the file description page in the {{msg-mw|timedmedia-status-header}} section.\n\nParameters:\n* $1 - the time the media has been in the job queue (e.g. \"{{int:timedmedia-minutes|1}}{{int:comma-separator}}{{int:timedmedia-seconds|42}}\"), uses the following messages:\n** {{msg-mw|Timedmedia-days}}\n** {{msg-mw|Timedmedia-hours}}\n** {{msg-mw|Timedmedia-minutes}}\n** {{msg-mw|Timedmedia-seconds}}\n** {{msg-mw|Comma-separator}}", + "timedmedia-unknown-target-size": "Commented out at this time.\n\nStatus update for Transcodes. Parameters:\n* $1 - the number of bytes of the file transcoded so far (localized)\nSee also:\n* {{msg-mw|Timedmedia-percent-done}}", + "timedmedia-days": "Used as the duration, as $1 in the following messages:\n* {{msg-mw|Timedmedia-in-job-queue}}\n* {{msg-mw|Timedmedia-started-transcode}}\nParameters:\n* $1 - number of days\n{{Related|Timedmedia-days}}\n{{Identical|Day}}", + "timedmedia-hours": "Used as the duration, as $1 in the following messages:\n* {{msg-mw|Timedmedia-in-job-queue}}\n* {{msg-mw|Timedmedia-started-transcode}}\nParameters:\n* $1 - number of hours\n{{Related|Timedmedia-days}}\n{{Identical|Hour}}", + "timedmedia-minutes": "Used as the duration, as $1 in the following messages:\n* {{msg-mw|Timedmedia-in-job-queue}}\n* {{msg-mw|Timedmedia-started-transcode}}\nParameters:\n* $1 - minutes\n{{Related|Timedmedia-days}}\n{{Identical|Minute}}", + "timedmedia-seconds": "Used as the duration, as $1 in the following messages:\n* {{msg-mw|Timedmedia-in-job-queue}}\n* {{msg-mw|Timedmedia-started-transcode}}\nParameters:\n* $1 - number of seconds\n{{Related|Timedmedia-days}}\n{{Identical|Second}}", + "timedmedia-reset": "Used as action link text and as dialog title.\n\nThe contents of the dialog is {{msg-mw|Timedmedia-reset-confirm}}.", + "timedmedia-reset-confirm": "Used as confirmation message in the dialog which has the title {{msg-mw|Timedmedia-reset}}.", + "timedmedia-reset-error": "Used as generic error message.", + "timedmedia-ogg": "{{optional}}\nUsed as $1 in the following messages:\n* {{msg-mw|Timedmedia-source-audio-file-desc}}\n* {{msg-mw|Timedmedia-source-file-desc}}\n* {{msg-mw|Timedmedia-source-file}}", + "timedmedia-webm": "{{optional}}\nUsed as $1 in the following messages:\n* {{msg-mw|Timedmedia-source-audio-file-desc}}\n* {{msg-mw|Timedmedia-source-file-desc}}\n* {{msg-mw|Timedmedia-source-file}}", + "timedmedia-mp4": "{{Optional}}\nUsed as $1 in the following messages:\n* {{msg-mw|Timedmedia-source-audio-file-desc}}\n* {{msg-mw|Timedmedia-source-file-desc}}\n* {{msg-mw|Timedmedia-source-file}}", + "timedmedia-wav": "{{Optional}}\nUsed as $1 in the following messages:\n* {{msg-mw|Timedmedia-source-audio-file-desc}}\n* {{msg-mw|Timedmedia-source-file-desc}}\n* {{msg-mw|Timedmedia-source-file}}", + "timedmedia-flac": "{{Optional}}\n\nUsed as $1 in the following messages:\n* {{msg-mw|Timedmedia-source-audio-file-desc}}\n* {{msg-mw|Timedmedia-source-file-desc}}\n* {{msg-mw|Timedmedia-source-file}}", + "timedmedia-source-file": "The source file. Parameters:\n* $1 - file type; any one of the following messages:\n** {{msg-mw|Timedmedia-ogg}}\n** {{msg-mw|Timedmedia-webm}}\n** {{msg-mw|Timedmedia-mp4}}\n** {{msg-mw|Timedmedia-flac}}\n** {{msg-mw|Timedmedia-wav}}\nSee also:\n* {{msg-mw|Timedmedia-source-audio-file-desc}}\n* {{msg-mw|Timedmedia-source-file-desc}}\n{{Identical|Source}}", + "timedmedia-source-file-desc": "Source file description. This is a file title in the primary source attributes of a timed media file. I can look like \"''Original Ogg file, 640 × 480 (10 Mbps)''\".\n\nParameters:\n* $1 - file type; any one of the following messages:\n** {{msg-mw|Timedmedia-ogg}}\n** {{msg-mw|Timedmedia-webm}}\n** {{msg-mw|Timedmedia-mp4}}\n** {{msg-mw|Timedmedia-flac}}\n** {{msg-mw|Timedmedia-wav}}\n* $2 - resolution width\n* $3 - resolution height\n* $4 - human readable bitrate\nSee also:\n* {{msg-mw|Timedmedia-source-audio-file-desc}} - for audio file", + "timedmedia-source-audio-file-desc": "Source file description. Parameters:\n* $1 - file type; any one of the following messages:\n** {{msg-mw|Timedmedia-ogg}}\n** {{msg-mw|Timedmedia-webm}}\n** {{msg-mw|Timedmedia-mp4}}\n** {{msg-mw|Timedmedia-flac}}\n** {{msg-mw|Timedmedia-wav}}\n* $2 - human readable bitrate\nSee also:\n* {{msg-mw|Timedmedia-source-file-desc}} - for video file", + "timedmedia-derivative-160p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-160p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-240p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-240p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-360p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-360p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-480p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-480p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-720p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-720p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-1080p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-1080p.ogv": "{{optional}} A type of media format encoding", + "timedmedia-derivative-160p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-160p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-360p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-360p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-480p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-480p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-720p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-720p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-1080p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-1080p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-2160p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-2160p.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-360p.vp9.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-360p.vp9.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-480p.vp9.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-480p.vp9.webm": "A type of media format encoding", + "timedmedia-derivative-720p.vp9.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-720p.vp9.webm": "A type of media format encoding", + "timedmedia-derivative-1080p.vp9.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-1080p.vp9.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-2160p.vp9.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-2160p.vp9.webm": "{{optional}} A type of media format encoding", + "timedmedia-derivative-320p.mp4": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-320p.mp4": "{{optional}} A type of media format encoding", + "timedmedia-derivative-480p.mp4": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-480p.mp4": "{{optional}} A type of media format encoding", + "timedmedia-derivative-720p.mp4": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-720p.mp4": "{{optional}} A type of media format encoding", + "timedmedia-derivative-1080p.mp4": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-1080p.mp4": "{{optional}} A type of media format encoding", + "timedmedia-derivative-2160p.mp4": "{{optional}} A type of media format encoding", + "timedmedia-derivative-desc-2160p.mp4": "{{optional}} A type of media format encoding", + "timedmedia-derivative-ogg": "{{optional}} Short form of a media format.", + "timedmedia-derivative-desc-ogg": "{{optional}} Media format.", + "timedmedia-derivative-opus": "{{optional}} Short form of a media format.", + "timedmedia-derivative-desc-opus": "{{optional}} Media format.", + "timedmedia-derivative-mp3": "{{optional}} Short form of a media format.", + "timedmedia-derivative-desc-mp3": "{{optional}} Media format.", + "timedmedia-derivative-m4a": "{{optional}} Short form of a media format.", + "timedmedia-derivative-desc-m4a": "{{optional}} Media format.", + "timedmedia-subtitle-new": "Used as page title.", + "timedmedia-subtitle-new-desc": "Refers to {{msg-mw|Timedmedia-subtitle-new-go}}.", + "timedmedia-subtitle-new-go": "Used as label for the Submit button.\n\nPreceded by (and used in) the description {{msg-mw|Timedmedia-subtitle-new-desc}}.\n{{Identical|Go}}", + "timedmedia-subtitle-language": "Subtitle names. Parameters are:\n* $1 - subtitle language\n* $2 - subtitle key\n{{Identical|Subtitle}}", + "timedmedia-subtitle-no-video": "Message for a TimedText page without an associated video file", + "timedmedia-subtitle-no-subtitles": "Parameters:\n* $1 is a language name.", + "timedmedia-subtitle-remote": "Used as page title. Parameters:\n* $1 - the display name of the repository\nThe page body for this page title is:\n* {{msg-mw|timedmedia-subtitle-remote-link}}", + "timedmedia-subtitle-remote-link": "Used as page body. Parameters:\n* $1 - the description URL of the file\n* $2 - the display name of the repository\nThe page title for this message is:\n* {{msg-mw|timedmedia-subtitle-remote}}", + "timedmediahandler": "{{doc-special|TimedMediaHandler}}", + "timedmedia-videos": "Number of videos on [[Special:TimedMediaHandler]]. Parameters:\n* $1 - number of videos\n{{Related|Timedmedia-format-type}}\n{{Identical|Video}}", + "timedmedia-ogg-videos": "Number of Ogg videos on [[Special:TimedMediaHandler]]. Parameters:\n* $1 - number of videos\n{{Related|Timedmedia-format-type}}", + "timedmedia-webm-videos": "Number of WebM videos on [[Special:TimedMediaHandler]]. Parameters:\n* $1 - number of videos\n{{Related|Timedmedia-format-type}}", + "timedmedia-derivative-state-transcodes": "Number of transcodes. Parameters:\n* $1 - number of transcodes", + "timedmedia-derivative-state-active": "currently active transcoes\nParameters are:\n* $1 number of transcodes", + "timedmedia-derivative-state-queued": "queued transcode jobs\nParameters are:\n* $1 number of transcodes", + "timedmedia-derivative-state-failed": "failed transcode jobs\nParameters are:\n* $1 number of transcodes", + "timedmedia-file": "Used as table column header.\n{{Identical|File}}", + "timedmedia-audios": "Number of audio files on [[Special:TimedMediaHandler]]. Parameters:\n* $1 - number of audio files\n{{Related|Timedmedia-format-type}}", + "timedmedia-ogg-audios": "Number of ogg audio files, displayed on [[Special:TimedMediaHandler]]. Parameters:\n* $1 - the number of files\n{{Related|Timedmedia-format-type}}", + "timedmedia-flac-audios": "Number of FLAC audio files, displayed on [[Special:TimedMediaHandler]]. Parameters:\n* $1 - the number of files\n{{Related|Timedmedia-format-type}}", + "timedmedia-wav-audios": "Number of WAV audio files, displayed on [[Special:TimedMediaHandler]]. Parameters:\n* $1 - the number of files\n{{Related|Timedmedia-format-type}}", + "right-transcode-reset": "{{doc-right|transcode-reset}}", + "right-transcode-status": "{{doc-right|transcode-status}}", + "action-transcode-status": "{{doc-action|transcode-status}}", + "orphanedtimedtext": "{{doc-special|OrphanedTimedText}}", + "orphanedtimedtext-summary": "Summary of Special:OrphanedTimedText.", + "orphanedtimedtext-unsupported": "Shown if Special:OrphanedTimedText isn't supported for the current database back-end.", + "orphanedtimedtext-notimedtext": "Shown on Special:OrphanedTimedText if the TimedText namespace is disabled on that wiki (i.e. $wgEnableLocalTimedText is set to false). Note, do not use {{ns:TimedText}} in this message.", + "apihelp-query+transcodestatus-description": "{{doc-apihelp-description|query+transcodestatus}}", + "apihelp-query+transcodestatus-example-1": "{{doc-apihelp-example|query+transcodestatus}}", + "apihelp-query+videoinfo-description": "{{doc-apihelp-description|query+videoinfo}}", + "apihelp-query+videoinfo-param-prop": "{{doc-apihelp-param|query+videoinfo|prop}}", + "apihelp-query+videoinfo-example-1": "{{doc-apihelp-example|query+videoinfo}}", + "apihelp-transcodereset-description": "{{doc-apihelp-description|transcodereset}}", + "apihelp-transcodereset-param-title": "{{doc-apihelp-param|transcodereset|title}}", + "apihelp-transcodereset-param-transcodekey": "{{doc-apihelp-param|transcodereset|transcodekey}}", + "apihelp-transcodereset-example-1": "{{doc-apihelp-example|transcodereset}}", + "apihelp-transcodereset-example-2": "{{doc-apihelp-example|transcodereset}}" +} diff --git a/extensions/TimedMediaHandler/i18n/qu.json b/extensions/TimedMediaHandler/i18n/qu.json new file mode 100644 index 00000000..4942568f --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/qu.json @@ -0,0 +1,14 @@ +{ + "@metadata": { + "authors": [ + "AlimanRuna" + ] + }, + "timedmedia-no-player-js": "Way way, wamp'unaykipiqa JavaScript nisqaman ama nisqam icha q'imisqa purichiq manam kanchu.
\nWidyuta chaqnamuyta icha purichiqta chaqnamuyta atinki widyuta wamp'unaykipi purichinaykipaq.", + "timedmedia-more": "Astawan...", + "timedmedia-dismiss": "Wichq'ay", + "timedmedia-download": "Willañiqita chaqnamuy", + "timedmedia-desc-link": "Kay willañiqimanta", + "timedmedia-source-file": "$1 pukyu", + "timedmedia-source-audio-file-desc": "Qallariy $1 willañiqi ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/ro.json b/extensions/TimedMediaHandler/i18n/ro.json new file mode 100644 index 00000000..4162d861 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ro.json @@ -0,0 +1,93 @@ +{ + "@metadata": { + "authors": [ + "Firilacroco", + "KlaudiuMihaila", + "Mihai", + "Minisarm", + "Stelistcristi", + "ImGelu" + ] + }, + "timedmedia-desc": "Gestionar pentru fișiere audio, video și subtitrări sincronizate, cu suport pentru formatele WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "fișier audio ogg $1, $2", + "timedmedia-ogg-short-video": "fișier video ogg $1, $2", + "timedmedia-ogg-short-general": "fișier media ogg $1, $2", + "timedmedia-ogg-long-audio": "fișier audio ogg $1, lungime $2, $3", + "timedmedia-ogg-long-video": "fișier video ogg $1, lungime $2, $4×$5 pixeli, $3", + "timedmedia-ogg-long-multiplexed": "fișier multiplexat audio/video ogg, $1, lungime $2, $4×$5 pixeli, $3", + "timedmedia-ogg-long-general": "fișier media ogg, lungime $2, $3", + "timedmedia-ogg-long-error": "fișier ogg incorect: $1", + "timedmedia-webm-short-video": "fișier video WebM $1, $2", + "timedmedia-webm-long-video": "fișier audio/video WebM, $1, lungime $2, $4 × $5 pixeli, $3 per ansamblu", + "timedmedia-flac-short-audio": "fișier audio FLAC, $1", + "timedmedia-flac-long-audio": "fișier audio FLAC, lungime $1, $2 per ansmablu", + "timedmedia-mp4-short-video": "fișier video MP4 $1, $2", + "timedmedia-mp4-long-video": "fișier audio/video MP4, $1, lungime $2, $4 × $5 pixeli, $3 per ansamblu", + "timedmedia-no-player-js": "Ne pare rău, însă navigatorul dumneavoastră fie are JavaScript dezactivat, fie nu conține niciun player suportat.
\nPuteți descărca clipul sau puteți descărca un player pentru a-l reda direct în navigator.", + "timedmedia-more": "Mai mult…", + "timedmedia-dismiss": "Închide", + "timedmedia-download": "Descarcă fișierul", + "timedmedia-play-media": "Redă fișierul media", + "timedmedia-desc-link": "Despre acest fișier", + "timedmedia-oggThumb-version": "OggHandler necesită oggThumb, versiunea $1 sau mai recentă.", + "timedmedia-oggThumb-failed": "oggThumb nu a reușit să creeze miniatura.", + "timedmedia-status-header": "Starea codificării", + "timedmedia-update-status": "Actualizează starea codificării", + "timedmedia-status": "Stare", + "timedmedia-status-unknown": "Stare necunoscută", + "timedmedia-transcodebitrate": "Rată de bit", + "timedmedia-transcodeinfo": "Format", + "timedmedia-actions": "Acțiuni", + "timedmedia-direct-link": "Descarcă", + "timedmedia-not-ready": "Nu este gata", + "timedmedia-completed-on": "Finalizat $1", + "timedmedia-error-on": "Eroare la $1", + "timedmedia-started-transcode": "Pornită acum $1. $2", + "timedmedia-percent-done": "Aproximativ $1% finalizat", + "timedmedia-in-job-queue": "Adăugat la șirul de sarcini acum $1", + "timedmedia-unknown-target-size": "Dimensiune finală necunoscută, $1 codificați", + "timedmedia-days": "{{PLURAL:$1|o zi|$1 zile|$1 de zile}}", + "timedmedia-hours": "{{PLURAL:$1|o oră|$1 ore|$1 de ore}}", + "timedmedia-minutes": "{{PLURAL:$1|un minut|$1 minute|$1 de minute}}", + "timedmedia-seconds": "{{PLURAL:$1|o secundă|$1 secunde|$1 de secunde}}", + "timedmedia-reset": "Reinițializează codificarea", + "timedmedia-reset-confirm": "Reinițializarea acestei codificări va elimina orice fișier existent (dacă există) și va readăuga codificarea la șirul de sarcini. Va dura ceva timp pentru a recodifica.

\nSunteți sigur că doriți să continuați?", + "timedmedia-reset-error": "Eroare la reinițializarea codificării.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Sursă $1", + "timedmedia-source-file-desc": "Fișier $1 original, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Fișier $1 original ($2)", + "timedmedia-derivative-desc-160p.ogv": "Videoclip Ogg cu lățime de bandă mică (160P)", + "timedmedia-derivative-desc-360p.ogv": "Videoclip Ogg pentru redare pe web (360P)", + "timedmedia-derivative-desc-480p.ogv": "Videoclip Ogg pentru redare pe web (480P)", + "timedmedia-derivative-desc-720p.ogv": "Videoclip Ogg descărcabil, de înaltă calitate (720P)", + "timedmedia-derivative-desc-160p.webm": "WebM pentru redare pe web (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM pentru redare pe web (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM pentru redare pe web (480P)", + "timedmedia-derivative-desc-720p.webm": "WebM descărcabil, de înaltă calitate (720P)", + "timedmedia-derivative-480p.vp9.webm": "VP9 480P", + "timedmedia-derivative-desc-320p.mp4": "MP4 prietenos cu dispozitivele (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 pentru redare pe web (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 de calitate HD (720P)", + "timedmedia-subtitle-new": "Creare traducere nouă sau modificare una existentă", + "timedmedia-subtitle-new-desc": "Alegeți limba și apăsați butonul '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Du-te", + "timedmedia-subtitle-language": "Subtitrare în $1 ($2)", + "timedmedia-subtitle-no-video": "Nu există niciun videoclip asociat cu pagina curentă de subtitrare", + "timedmedia-subtitle-no-subtitles": "Nu există în acest moment subtitrări în limba $1 pentru acest videoclip. Puteți [{{fullurl:{{FULLPAGENAME}}|action=edit}} modifica această pagină] pentru a le adăuga.", + "timedmedia-subtitle-remote": "Subtitrarea sincronizată a acestui fișier este găzduită la $1", + "timedmedia-subtitle-remote-link": "Puteți [$1 vizualiza pagina descriptivă] a acestui fișier la $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 videoclip|$1 videoclipuri|$1 de videoclipuri}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 videoclip Ogg|$1 videoclipuri Ogg|$1 de videoclipuri Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 videoclip WebM|$1 videoclipuri WebM|$1 de videoclipuri WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 codificare|$1 codificări|$1 de codificări}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 codificare în curs|$1 codificări în curs|$1 de codificări în curs}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 codificare în așteptare|$1 codificări în așteptare|$1 de codificări în așteptare}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 codificare eșuată|$1 codificări eșuate|$1 de codificări eșuate}}", + "timedmedia-file": "Fișier", + "right-transcode-reset": "Reinițializează videoclipuri nereușit codificate pentru ca acestea să fie readăugate la șirul de sarcini.", + "right-transcode-status": "Vizualizează [[Special:TimedMediaHandler|informații despre activitatea curentă de codificare]]", + "action-transcode-status": "vizualizați statutul actual al codificării" +} diff --git a/extensions/TimedMediaHandler/i18n/roa-tara.json b/extensions/TimedMediaHandler/i18n/roa-tara.json new file mode 100644 index 00000000..a972d6b7 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/roa-tara.json @@ -0,0 +1,91 @@ +{ + "@metadata": { + "authors": [ + "Joetaras" + ] + }, + "timedmedia-desc": "Gestore pe le file audio, video e teste temporizzate, cu le formate supportate pe WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "File audie Ogg $1, $2", + "timedmedia-ogg-short-video": "File video Ogg $1, $2", + "timedmedia-ogg-short-general": "File media Ogg $1, $2", + "timedmedia-ogg-long-audio": "File audie Ogg $1, lunghezze $2, $3", + "timedmedia-ogg-long-video": "File video Ogg $1, lunghezze $2, $4 x $5 pixel, $3", + "timedmedia-ogg-long-multiplexed": "File multiplexed audie e video Ogg $1, lunghezze $2, $4 x $5 pixel, $3 in totale", + "timedmedia-ogg-long-general": "File media Ogg, lunghezze $2, $3", + "timedmedia-ogg-long-error": "Ogg file invalide: $1", + "timedmedia-webm-short-video": "WebM $1 file video, $2", + "timedmedia-webm-long-video": "File WebM audio/video, $1, lunghezze $2, $4 × $5 pixel, $3 in totale", + "timedmedia-flac-short-audio": "File audio FLAC, $1", + "timedmedia-flac-long-audio": "File audio FLAC, lunghezze $1, in totale $2", + "timedmedia-wav-short-audio": "File audio WAV, $1", + "timedmedia-wav-long-audio": "File audio WAV, lunghezze $1, in totale $2", + "timedmedia-wav-pcm-required": "Tu puè sulamende carecà WAV de tipe PCM (Codece de Modulazione Pulsate).", + "timedmedia-mp4-short-video": "MP4 $1 file video, $2", + "timedmedia-mp4-long-video": "File MP4 audio/video, $1, lunghezze $2, $4 × $5 pixel, $3 in totale", + "timedmedia-no-player-js": "Ne dispiace, 'u browser tune tène 'u JavaScript disabbilitate o non ge tène 'n'esecutore supportate.
\nTu puè scarecà 'u video o scarecà 'n'esecutore pe 'ndrucà 'u video sus a 'u browser tune.", + "timedmedia-more": "De cchiù...", + "timedmedia-dismiss": "Chiude", + "timedmedia-download": "Scareche stu file", + "timedmedia-play-media": "Esegue 'u media", + "timedmedia-desc-link": "'Mbormaziune sus a stu file", + "timedmedia-oggThumb-version": "OggHandler vole 'a versine de oggThumb $1 o cchiù ierte.", + "timedmedia-oggThumb-failed": "oggThumn ha fallite a ccrejà le miniature.", + "timedmedia-status-header": "State d'a codifeche", + "timedmedia-update-status": "Aggiorne 'u state d'a codifeche", + "timedmedia-status": "State", + "timedmedia-status-unknown": "State scanusciute", + "timedmedia-transcodebitrate": "Bitrate", + "timedmedia-transcodeduration": "Tiembe de codifiche", + "timedmedia-transcodeinfo": "Formate", + "timedmedia-actions": "Aziune", + "timedmedia-direct-link": "Scareche", + "timedmedia-not-ready": "Non g'è pronde", + "timedmedia-completed-on": "Combletate $1", + "timedmedia-error-on": "Errore sus a $1", + "timedmedia-started-transcode": "Accumenzate $1 fà. $2", + "timedmedia-percent-done": "Cchiù o mene $1% fatte", + "timedmedia-in-job-queue": "Aggiunde a 'a Fatìe in code $1 fà", + "timedmedia-unknown-target-size": "Dimenzione d'a destinazione scanusciute, $1 codificate", + "timedmedia-days": "{{PLURAL:$1|1 sciurne|$1 sciurne}}", + "timedmedia-hours": "{{PLURAL:$1|1 ore|$1 ore}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minute|$1 minute}}", + "timedmedia-seconds": "{{PLURAL:$1|1 seconde|$1 seconde}}", + "timedmedia-reset": "Azzere 'a transcodifeche", + "timedmedia-reset-confirm": "Stoche azzere sta transcodifiche ca adda luà ogne file esistende (ce presende), e pò aggiunge 'a transcodifiche jndr'à code d'a fatìe. Cchiù vote accumenze 'a transcodifiche.

\nSì secure ca vuè ccu vvè nnande?", + "timedmedia-reset-error": "Errore mendre ste azzerave 'a fatìe de transcodifiche.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 sorgende", + "timedmedia-source-file-desc": "File origgenale $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "File origgenale $1 ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg video cu banda vasce (160P)", + "timedmedia-derivative-desc-360p.ogv": "Ogg video cu streaming web (360P)", + "timedmedia-derivative-desc-480p.ogv": "Ogg video cu streaming web (480P)", + "timedmedia-derivative-desc-720p.ogv": "Ogg video de qualitate ierte scarecabbele (720P)", + "timedmedia-derivative-desc-160p.webm": "Flusse web WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "Flusse web WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "Flusse web WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "WebM de qualitate ierte scarecabbele (720P)", + "timedmedia-derivative-desc-320p.mp4": "Dispositive-amiche MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "Streaming web MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "Qualitate HD MP4 (720P)", + "timedmedia-subtitle-new": "Ccreje 'na traduziona nove o ne cange une esistende", + "timedmedia-subtitle-new-desc": "Scacchie 'na lènghe e cazze 'u buttone '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Véje", + "timedmedia-subtitle-language": "$1 ($2) sottotitele", + "timedmedia-subtitle-no-video": "Non ge stonne video associate cu 'u sottotitole d'a pàgene de mò", + "timedmedia-subtitle-no-subtitles": "Non ge stonne sottotitole pe mò jndr'à $1 pe stu video, tu puè [{{fullurl:{{FULLPAGENAME}}|action=edit}} cangià sta pàgene] o aggiungerle", + "timedmedia-subtitle-remote": "'U teste temborizzate pe stu file jè ospitate sus a $1", + "timedmedia-subtitle-remote-link": "Tu puè [$1 'ndrucà 'a pàgene de descrizione] pe stu file sus a $2", + "timedmediahandler": "Gestore TimedMedia", + "timedmedia-videos": "{{PLURAL:$1|$1 video}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Video Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 Video WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transcodifiche}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 transcodifiche in esecuzione}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transcodifiche in code}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 transcodifiche fallite}}", + "timedmedia-file": "File", + "right-transcode-reset": "Azzeramende fallite o le video transcodificate onne state sckaffate cchiù de 'na vote jndr'à code d'a fatìe.", + "right-transcode-status": "'Ndruche [[Special:TimedMediaHandler|le 'mbormaziune sus a l'attività de transcodifiche de mò]]", + "action-transcode-status": "'ndruche 'u state de mò d'a transcodifeche" +} diff --git a/extensions/TimedMediaHandler/i18n/ru.json b/extensions/TimedMediaHandler/i18n/ru.json new file mode 100644 index 00000000..d4bf8e64 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ru.json @@ -0,0 +1,111 @@ +{ + "@metadata": { + "authors": [ + "Ahonc", + "Amire80", + "Eleferen", + "Kaganer", + "Kalan", + "Kv75", + "MaxSem", + "Okras", + "Александр Сигачёв", + "NBS" + ] + }, + "timedmedia-desc": "Обработчик для аудио, видео и субтитров, с поддержкой форматов: WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Звуковой файл Ogg $1, $2", + "timedmedia-ogg-short-video": "Видео-файл Ogg $1, $2", + "timedmedia-ogg-short-general": "Медиафайл Ogg $1, $2", + "timedmedia-ogg-long-audio": "Звуковой файл Ogg $1, длительность $2, $3", + "timedmedia-ogg-long-video": "видео-файл Ogg $1, длительность $2, $4 × $5 {{PLURAL:$5|пиксель|пикселя|пикселей}}, $3", + "timedmedia-ogg-long-multiplexed": "мультиплексный аудио/видеофайл Ogg, $1, длительность $2, $4 × $5 {{PLURAL:$5|пиксель|пикселя|пикселей}}, $3 всего", + "timedmedia-ogg-long-general": "медиафайл Ogg, длительность $2, $3", + "timedmedia-ogg-long-error": "неправильный Ogg-файл: $1", + "timedmedia-ogg-long-no-streams": "Мультимедиа-файл ogg. Предупреждение: Ни один из кодеков, используемых в этом файле, не определён.", + "timedmedia-webm-short-video": "WebM $1 видео-файл, $2", + "timedmedia-webm-long-video": "WebM аудио/видео файл, $1, продолжительность $2, $4 × $5 {{PLURAL:$5|пиксель|пикселя|пикселей}}, всего $3", + "timedmedia-flac-short-audio": "Аудиофайл FLAC, $1", + "timedmedia-flac-long-audio": "Аудиофайл FLAC, длина — $1, битрейт — $2", + "timedmedia-wav-short-audio": "Аудиофайл WAV, $1", + "timedmedia-wav-long-audio": "Аудиофайл WAV, длина — $1, битрейт — $2", + "timedmedia-wav-pcm-required": "Вы можете загрузить только PCM (Pulse Code Modulation) WAV.", + "timedmedia-mp4-short-video": "MP4 $1 видео-файл, $2", + "timedmedia-mp4-long-video": "Аудио/видеофайл MP4 $1, длина — $2, $4 × $5 пикселей, битрейт — $3", + "timedmedia-no-player-js": "К сожалению, в вашем браузере отключён JavaScript, или не имеется требуемого проигрывателя.
\nВы можете загрузить ролик или загрузить проигрыватель для воспроизведения ролика в браузере.", + "timedmedia-more": "Больше…", + "timedmedia-dismiss": "Скрыть", + "timedmedia-download": "Загрузить файл", + "timedmedia-play-media": "Воспроизвести медиафайл", + "timedmedia-desc-link": "Информация об этом файле", + "timedmedia-oggThumb-version": "OggHandler требует oggThumb версии $1 или более поздней.", + "timedmedia-oggThumb-failed": "oggThumb не удалось создать миниатюру.", + "timedmedia-status-header": "Состояние декодирования", + "timedmedia-update-status": "Обновить статус декодирования", + "timedmedia-status": "Состояние", + "timedmedia-status-unknown": "Неизвестное состояние", + "timedmedia-transcodeinfo": "Формат", + "timedmedia-actions": "Действия", + "timedmedia-direct-link": "Скачать", + "timedmedia-not-ready": "Не готов", + "timedmedia-completed-on": "Завершено $1", + "timedmedia-error-on": "Ошибка $1", + "timedmedia-started-transcode": "Запущено $1 назад. $2", + "timedmedia-percent-done": "Примерно $1% выполнено", + "timedmedia-in-job-queue": "Добавлено в очередь заданий $1 назад", + "timedmedia-unknown-target-size": "Неизвестный целевой размер, $1 при кодировке", + "timedmedia-days": "{{PLURAL:$1|$1 день|$1 дня|$1 дней}}", + "timedmedia-hours": "{{PLURAL:$1|$1 час|$1 часа|$1 часов}}", + "timedmedia-minutes": "{{PLURAL:$1|$1 минута|$1 минуты|$1 минут}}", + "timedmedia-seconds": "{{PLURAL:$1|$1 секунда|$1 секунды|$1 секунд}}", + "timedmedia-reset": "Перезапуск декодирования", + "timedmedia-reset-confirm": "Перезапуск декодирования удалит существующий файл (если он существует), и декодирование вновь будет добавлено в очередь заданий. Повторное декодирование займет некоторое время.

\nВы уверены, что хотите продолжить?", + "timedmedia-reset-error": "Ошибка при перезапуске декодирования.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Источник $1", + "timedmedia-source-file-desc": "Оригинал $1 файла, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Оригинальный $1-файл ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg-видео низкого качества (160p)", + "timedmedia-derivative-desc-360p.ogv": "Потоковое Ogg-видео (360p)", + "timedmedia-derivative-desc-480p.ogv": "Потоковое Ogg-видео (480p)", + "timedmedia-derivative-desc-720p.ogv": "Ogg-видео высокого качества (720p)", + "timedmedia-derivative-desc-1080p.ogv": "Скачиваемое Ogg-видео в формате Full HD (1080P)", + "timedmedia-derivative-desc-160p.webm": "Потоковое WebM-видео (160p)", + "timedmedia-derivative-desc-360p.webm": "Потоковое WebM-видео (360p)", + "timedmedia-derivative-desc-480p.webm": "Потоковое WebM-видео (480p)", + "timedmedia-derivative-desc-720p.webm": "WebM-видео высокого качества (720p)", + "timedmedia-derivative-desc-1080p.webm": "Скачиваемое WebM-видео в формате Full HD (1080P)", + "timedmedia-derivative-desc-320p.mp4": "Адаптированный для устройства MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "Потоковое MP4-видео (480p)", + "timedmedia-derivative-desc-720p.mp4": "MP4 в HD-качестве (720P)", + "timedmedia-derivative-desc-1080p.mp4": "MP4-видео в формате Full HD (1080P)", + "timedmedia-subtitle-new": "Создать новый перевод или редактировать существующий", + "timedmedia-subtitle-new-desc": "Выберите язык и нажмите кнопку '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Выполнить", + "timedmedia-subtitle-language": "$1 ($2) субтитры", + "timedmedia-subtitle-no-video": "Нет видео, связанного с текущей страницей субтитров", + "timedmedia-subtitle-no-subtitles": "В настоящее время нет субтитров на $1 для этого видео, Вы можете [{{fullurl:{{FULLPAGENAME}}|action=edit}} изменить эту страницу] для их добавления", + "timedmedia-subtitle-remote": "Субтитры для этого файла размещены на $1", + "timedmedia-subtitle-remote-link": "Вы можете [$1 посмотреть страницу описания] этого файла на $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "$1 видео{{PLURAL:$1|файл|файла|файлов}}", + "timedmedia-ogg-videos": "$1 Ogg видео{{PLURAL:$1|файл|файла|файлов}}", + "timedmedia-webm-videos": "$1 WebM видео{{PLURAL:$1|файл|файла|файлов}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 декодирование|$1 декодирования|$1 декодирований}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 запущенное декодирование|$1 запущенных декодирования|$1 запущенных декодирований}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 поставленное в очередь декодирование|$1 поставленных в очередь декодирования|$1 поставленных в очередь декодирований}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 неудавшееся декодирование|$1 неудавшихся декодирования|$1 неудавшихся декодирований}}", + "timedmedia-file": "Файл", + "timedmedia-audios": "{{PLURAL:$1|$1 аудиофайл|$1 аудиофайла|$1 аудиофайлов̟}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 аудиофайл Ogg|$1 аудиофайла Ogg|$1 аудиофайлов Ogg}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 аудиофайл FLAC|$1 аудиофайла FLAC|$1 аудиофайлов FLAC}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 аудиофайл WAV|$1 аудиофайла WAV|$1 аудиофайлов WAV}}", + "right-transcode-reset": "сброс неудавшихся или транскодированных видео, чтобы они встали в очередь заданий снова", + "right-transcode-status": "просмотр [[Special:TimedMediaHandler|информации о текущем ходе перекодирования]]", + "action-transcode-status": "просмотр текущего статуса перекодирования", + "orphanedtimedtext": "Потерянные страницы TimedText", + "orphanedtimedtext-summary": "Список страниц [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]], которые не имеют соответствующего файла.", + "orphanedtimedtext-unsupported": "Это спецстраница поддерживается только для баз данных MySQL.", + "orphanedtimedtext-notimedtext": "TimedText не включен в этой вики.", + "apihelp-transcodereset-param-title": "Заголовок медиафайла." +} diff --git a/extensions/TimedMediaHandler/i18n/rue.json b/extensions/TimedMediaHandler/i18n/rue.json new file mode 100644 index 00000000..66934a9c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/rue.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Gazeb" + ] + }, + "timedmedia-more": "Веце...", + "timedmedia-dismiss": "Заперти", + "timedmedia-file": "Файл" +} diff --git a/extensions/TimedMediaHandler/i18n/sa.json b/extensions/TimedMediaHandler/i18n/sa.json new file mode 100644 index 00000000..f3634735 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sa.json @@ -0,0 +1,19 @@ +{ + "@metadata": { + "authors": [ + "Shubha", + "NehalDaveND" + ] + }, + "timedmedia-more": "अधिकम्.....", + "timedmedia-dismiss": "पिदधातु", + "timedmedia-download": "सञ्चिकायाः अवारोपणम्", + "timedmedia-desc-link": "अस्याः सञ्चिकायाः विषये", + "timedmedia-status": "स्थितिः", + "timedmedia-status-unknown": "अज्ञातस्थितिः", + "timedmedia-actions": "क्रियाः", + "timedmedia-source-file": "$1 स्रोतः", + "timedmedia-source-audio-file-desc": "मूलम् $1 सञ्चिका ($2)", + "timedmedia-subtitle-new-go": "गम्यताम्", + "timedmedia-file": "सञ्चिका" +} diff --git a/extensions/TimedMediaHandler/i18n/sah.json b/extensions/TimedMediaHandler/i18n/sah.json new file mode 100644 index 00000000..0bee4097 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sah.json @@ -0,0 +1,23 @@ +{ + "@metadata": { + "authors": [ + "HalanTul" + ] + }, + "timedmedia-desc": "Обработчик файлов Ogg Theora и Vorbis с использованием JavaScript-проигрывателя", + "timedmedia-ogg-short-audio": "Звуковой файл Ogg $1, $2", + "timedmedia-ogg-short-video": "Видео-файл Ogg $1, $2", + "timedmedia-ogg-short-general": "Медиа-файл Ogg $1, $2", + "timedmedia-ogg-long-audio": "звуковой файл Ogg $1, уһуна $2, $3", + "timedmedia-ogg-long-video": "видео-файл Ogg $1, уһуна $2, $4×$5 пииксэллээх, $3", + "timedmedia-ogg-long-multiplexed": "мультиплексный аудио/видео-файл Ogg, $1, уһуна $2, $4×$5 пииксэллээх, барыта $3", + "timedmedia-ogg-long-general": "медиа-файл Ogg, уһуна $2, $3", + "timedmedia-ogg-long-error": "сыыһа Ogg-файл: $1", + "timedmedia-no-player-js": "Хомойуох иһин, браузергар JavaScript араарылла сылдьар, эбэтэр ону үлэлэтэр тэрил суох эбит.
\nЭн ролигы хачайданан ылан көрүөххүн биитэр оонньотор тэрили хачайданан браузергар көрүөххүн сөп.", + "timedmedia-more": "Өссө...", + "timedmedia-dismiss": "Кистээ/сап", + "timedmedia-download": "Билэни хачайдаа", + "timedmedia-desc-link": "Бу билэ туһунан", + "timedmedia-source-file": "Ылыллыбыт сирэ $1", + "timedmedia-source-audio-file-desc": "Дьиҥнээх $1-билэ ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/scn.json b/extensions/TimedMediaHandler/i18n/scn.json new file mode 100644 index 00000000..77993f87 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/scn.json @@ -0,0 +1,11 @@ +{ + "@metadata": { + "authors": [ + "Pippinu", + "Sarvaturi" + ] + }, + "timedmedia-no-player-js": "Purtroppu lu tò browser o havi lu JavaScript disattivatu o nun havi nuddu litturi suppurtatu.
\nTi poi scarricari lu spizzuni o puru scarricari nu litturi p'arriprudùciri lu spizzuni ntô tò browser.", + "timedmedia-source-file": "Surgenti $1", + "timedmedia-source-audio-file-desc": "File $1 origginali ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/sco.json b/extensions/TimedMediaHandler/i18n/sco.json new file mode 100644 index 00000000..a05c7efb --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sco.json @@ -0,0 +1,16 @@ +{ + "@metadata": { + "authors": [ + "John Reid", + "AmaryllisGardener" + ] + }, + "timedmedia-ogg-long-no-streams": "Ogg media file. Warnishment: Nane o the codecs uised in this file ar recognized.", + "timedmedia-no-player-js": "Sorry, yer brouser either haes JavaScript disabled or daes nae hae ony supportit player.
\nYe can dounload the clip or dounload a player tae play the clip in yer brouser.", + "timedmedia-source-file": "$1 soorce", + "timedmedia-source-audio-file-desc": "Oreeginal $1 file ($2)", + "timedmedia-audios": "{{PLURAL:$1|$1 audio file|$1 audio files}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg audio file|$1 Ogg audio files}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC audio file|$1 FLAC audio files}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV audio file|$1 WAV audio files}}" +} diff --git a/extensions/TimedMediaHandler/i18n/sdh.json b/extensions/TimedMediaHandler/i18n/sdh.json new file mode 100644 index 00000000..d4ff6455 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sdh.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Kurdbuddha" + ] + }, + "timedmedia-file": "پەڕگە" +} diff --git a/extensions/TimedMediaHandler/i18n/sgs.json b/extensions/TimedMediaHandler/i18n/sgs.json new file mode 100644 index 00000000..702050e7 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sgs.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Hugo.arg" + ] + }, + "timedmedia-download": "Atsėsiōstė faila", + "timedmedia-source-file": "$1 kėlmie", + "timedmedia-source-audio-file-desc": "Pėrms $1 skvarmou ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/shn.json b/extensions/TimedMediaHandler/i18n/shn.json new file mode 100644 index 00000000..5e27c963 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/shn.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Saimawnkham" + ] + }, + "timedmedia-source-file": "ငဝ်ႈငႃႇ $1", + "timedmedia-source-audio-file-desc": "ၾၢႆႇငဝ်ႈတိုၼ်း $1 ၾၢႆႇ ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/shy-latn.json b/extensions/TimedMediaHandler/i18n/shy-latn.json new file mode 100644 index 00000000..ec64ab0c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/shy-latn.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Vikoula5" + ] + }, + "timedmedia-days": "{{PLURAL:$1|1 ass|$1 ussan}}" +} diff --git a/extensions/TimedMediaHandler/i18n/si.json b/extensions/TimedMediaHandler/i18n/si.json new file mode 100644 index 00000000..05795ef3 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/si.json @@ -0,0 +1,85 @@ +{ + "@metadata": { + "authors": [ + "නන්දිමිතුරු", + "පසිඳු කාවින්ද", + "Susith Chandira Gts", + "හරිත" + ] + }, + "timedmedia-desc": "Ogg Theora සහ Vorbis ගොනු සඳහා හසුරුවනය, ජාවාස්ක්‍රිප්ට් ප්ලේයර් සමඟ", + "timedmedia-ogg-short-audio": "Ogg $1 ශ්‍රව්‍ය ගොනුව, $2", + "timedmedia-ogg-short-video": "Ogg $1 දෘශ්‍ය ගොනුව, $2", + "timedmedia-ogg-short-general": "Ogg $1 මාධ්‍ය ගොනුව, $2", + "timedmedia-ogg-long-audio": "Ogg $1 ශ්‍රව්‍ය ගොනුව, ප්‍රවර්තනය $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 දෘශ්‍ය ගොනුව, ප්‍රවර්තනය $2, $4×$5 පික්සල්, $3", + "timedmedia-ogg-long-multiplexed": "Ogg බහුපථකාරක ශ්‍රව්‍ය/දෘශ්‍ය ගොනුව, $1, ප්‍රවර්තනය $2, $4×$5 පික්සල්, $3 සමස්ත", + "timedmedia-ogg-long-general": "Ogg මාධ්‍ය ගොනුව, ප්‍රවර්තනය $2, $3", + "timedmedia-ogg-long-error": "අනීතික ogg ගොනුව: $1", + "timedmedia-webm-short-video": "WebM $1 විදෘශ්‍ය ගොනුව, $2", + "timedmedia-webm-long-video": "WebM ශ්‍රව්‍ය/දෘශ්‍ය ගොනුව, $1, දිග $2, $4 × $5 පික්සල්, $3 සමස්ථය", + "timedmedia-mp4-short-video": "MP4 $1 දෘශ්‍ය ගොනුව, $2", + "timedmedia-mp4-long-video": "MP4 ශ්‍රව්‍ය/දෘශ්‍ය ගොනුව, $1, දිග $2, $4 × $5 පික්සල්, $3 සමස්ථය", + "timedmedia-no-player-js": "සමාවන්න, ඔබේ ගවේෂකයේ ජාවාස්ක්‍රිප්ට් අක්‍රීය වී ඇත නැතහොත් කිසිදු අනුග්‍රහය දක්වන ප්ලේයරයක් ඔබ සතුව නැත.
\nඔබේ ගවේෂකයෙහි වීඩියෝව ධාවනය කිරීමට වීඩියෝව බාගත කිරීම හෝ ප්ලේයරය බාගත කිරීම සිදු කළ හැකිය.", + "timedmedia-more": "ඉතිරිය…", + "timedmedia-dismiss": "වසන්න", + "timedmedia-download": "ගොනුව බා ගන්න", + "timedmedia-play-media": "මාධ්‍ය වයන්න", + "timedmedia-desc-link": "මෙම ගොනුව පිළිබඳ", + "timedmedia-oggThumb-version": "OggHandler සඳහා oggThumb $1 සංස්කරණය හෝ අලුත් එකක් අවැසිය.", + "timedmedia-oggThumb-failed": "සංක්ෂිප්තය තැනුමට oggThumb අසමත්විය.", + "timedmedia-status-header": "Transcode තත්වය", + "timedmedia-update-status": "transcode තත්වය යාවත්කාලීන කරන්න", + "timedmedia-status": "තත්වය", + "timedmedia-status-unknown": "නොදන්නා තත්වය", + "timedmedia-transcodeinfo": "Transcode ව්‍යුත්පන්න විස්තරය", + "timedmedia-actions": "කාර්යයන්", + "timedmedia-direct-link": "ව්‍යුත්පන්නය බාගන්න", + "timedmedia-not-ready": "සුදානම් නැත", + "timedmedia-completed-on": "$1 transcode සම්පූර්ණ කරන ලදී", + "timedmedia-error-on": "$1 හී transcode දෝෂයක්.", + "timedmedia-started-transcode": "$1 පෙර Transcode ආරම්භ කරන ලදී. $2", + "timedmedia-percent-done": "$1% පමණ ඉවරයි", + "timedmedia-in-job-queue": "$1 පෙර කෘත්‍ය පෙලට එක් කරන ලදී", + "timedmedia-unknown-target-size": "නොදන්නා ඉලක්කගත ප්‍රමාණය, $1 කේතාංකනය කරන ලදී", + "timedmedia-days": "{{PLURAL:$1|දවස|$1 දවස්}}", + "timedmedia-hours": "{{PLURAL:$1|පැය|$1 පැය}}", + "timedmedia-minutes": "{{PLURAL:$1|මිනිත්තුව|$1 මිනිත්තු}}", + "timedmedia-seconds": "{{PLURAL:$1|තත්පරය|$1 තත්පර}}", + "timedmedia-reset": "transcode යළි සකසන්න", + "timedmedia-reset-error": "transcode කෘත්‍ය යළි සැකසීමේ දෝෂය.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 මූලාශ්‍රය", + "timedmedia-source-file-desc": "නියම $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "නියම $1 ගොනුව ($2)", + "timedmedia-derivative-desc-160p.ogv": "අවම කලාප පළල Ogg විදෘශ්‍ය (160P)", + "timedmedia-derivative-desc-360p.ogv": "ජාල ශ්‍රෝතමය Ogg වීඩියෝව (360P)", + "timedmedia-derivative-desc-480p.ogv": "ජාල ශ්‍රෝතමය Ogg වීඩියෝව (480P)", + "timedmedia-derivative-desc-720p.ogv": "උසස් ගුණත්වයේ බාගතහැකි Ogg වීඩියෝව (720P)", + "timedmedia-derivative-desc-1080p.ogv": "බාගත කළහැකි Full HD Ogg වීඩියෝ පට (1080P)", + "timedmedia-derivative-desc-160p.webm": "ජාල ශ්‍රෝතමය WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "ජාල ශ්‍රෝතමය WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "ජාල ශ්‍රෝතමය WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "උසස් ගුණත්වයේ බාගතහැකි WebM (720P)", + "timedmedia-derivative-desc-1080p.webm": "බාගත කළහැකි Full HD WebM (1080P)", + "timedmedia-derivative-desc-320p.mp4": "උපාංග-හිතකාමී MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "ජාල ශ්‍රෝතමය MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "HD ගුණත්වයේ MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "Full HD තත්වයේ MP4 (1080P)", + "timedmedia-subtitle-new": "නව පරිවර්තනයක් තනන්න හෝ පවත්නා එකක් සංස්කරණය කරන්න", + "timedmedia-subtitle-new-go": "යන්න", + "timedmedia-subtitle-language": "$1 ($2) උපශීර්ෂ", + "timedmedia-subtitle-no-video": "වත්මන් උපසිරුස පිටුව සමඟ කිසිදු වීඩියෝවක් සම්බන්ධ නොවේ.", + "timedmedia-subtitle-remote": "මෙම ගොනුව සඳහා කාලිත පෙළ $1 හී සත්කාර ලබා ඇත", + "timedmedia-subtitle-remote-link": "ඔබට මෙම ගොනුව සඳහා [$1 විස්තර පිටුව] $2 හිදී බලාගත හැක", + "timedmediahandler": "කාලිතමාධ්‍යහසුරවනය", + "timedmedia-videos": "{{PLURAL:$1|වීඩියෝ}} $1 ක්", + "timedmedia-ogg-videos": "{{PLURAL:$1|Ogg වීඩියෝ}} $1 ක්", + "timedmedia-webm-videos": "{{PLURAL:$1|WebM වීඩියෝ}} $1 ක්", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transcode|$1 transcodes}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|ධාවනය වන transcode}} $1", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|පෙළගස්වා ඇති transcode}} $1", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|අසමත් වූ transcode}} $1", + "timedmedia-file": "ගොනුව", + "action-transcode-status": "වත්මන් අනු ලැකීම් තත්වය නරඹන්න" +} diff --git a/extensions/TimedMediaHandler/i18n/sk.json b/extensions/TimedMediaHandler/i18n/sk.json new file mode 100644 index 00000000..0a1480c4 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sk.json @@ -0,0 +1,25 @@ +{ + "@metadata": { + "authors": [ + "Helix84", + "TomášPolonec", + "Wizzard" + ] + }, + "timedmedia-desc": "Obsluha súborov Ogg Theora a Vorbis s JavaScriptovým prehrávačom", + "timedmedia-ogg-short-audio": "Zvukový súbor ogg $1, $2", + "timedmedia-ogg-short-video": "Video súbor ogg $1, $2", + "timedmedia-ogg-short-general": "Multimediálny súbor ogg $1, $2", + "timedmedia-ogg-long-audio": "Zvukový súbor ogg $1, dĺžka $2, $3", + "timedmedia-ogg-long-video": "Video súbor ogg $1, dĺžka $2, $4×$5 pixelov, $3", + "timedmedia-ogg-long-multiplexed": "Multiplexovaný zvukový/video súbor ogg, $1, dĺžka $2, $4×$5 pixelov, $3 celkom", + "timedmedia-ogg-long-general": "Multimediálny súbor ogg, dĺžka $2, $3", + "timedmedia-ogg-long-error": "Neplatný súbor ogg: $1", + "timedmedia-no-player-js": "Je nám ľúto, ale váš prehliadač má buď vypnutý JavaScript, alebo nemáte žiadny podporovaný prehrávač.
\nMôžete si stiahnuť klip alebo si stiahnuť prehrávač, ktorým si klip prehrajete v prehliadači.", + "timedmedia-more": "viac...", + "timedmedia-dismiss": "Zatvoriť", + "timedmedia-download": "Stiahnuť súbor", + "timedmedia-desc-link": "O tomto súbore", + "timedmedia-source-file": "Zdroj $1", + "timedmedia-source-audio-file-desc": "Pôvodný súbor $1 ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/sl.json b/extensions/TimedMediaHandler/i18n/sl.json new file mode 100644 index 00000000..d298dd7a --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sl.json @@ -0,0 +1,96 @@ +{ + "@metadata": { + "authors": [ + "Dbc334", + "Eleassar", + "Rzuwig" + ] + }, + "timedmedia-desc": "Upravljavec zvoka, videa in časovnega besedila s podprtimi oblikami WebM, Ogg Theora, Vorbis in SubRip", + "timedmedia-ogg-short-audio": "avdiodatoteka Ogg $1, $2", + "timedmedia-ogg-short-video": "Videodatoteka Ogg $1, $2", + "timedmedia-ogg-short-general": "Predstavnostna datoteka Ogg $1, $2", + "timedmedia-ogg-long-audio": "avdiodatoteka Ogg $1, dolžine $2, $3", + "timedmedia-ogg-long-video": "videodatoteka ogg $1, dolžine $2, $4 × $5 pik, $3", + "timedmedia-ogg-long-multiplexed": "multipleksna avdio-/videodatoteka Ogg, $1, dolžine $2, $4 × $5 pik, skupaj $3", + "timedmedia-ogg-long-general": "predstavnostna datoteka Ogg, dolžine $2, $3", + "timedmedia-ogg-long-error": "Neveljavna datoteka Ogg: $1", + "timedmedia-webm-short-video": "Videodatoteka WebM $1, $2", + "timedmedia-webm-long-video": "Avdio-/videodatoteka WebM, $1, dolžine $2, $4 × $5 pik, skupaj $3", + "timedmedia-flac-short-audio": "Datoteka z zvokom FLAC, $1", + "timedmedia-flac-long-audio": "Datoteka z zvokom FLAC, dolžine $1, skupno $2", + "timedmedia-wav-short-audio": "Datoteka z zvokom WAV, $1", + "timedmedia-wav-long-audio": "Datoteka z zvokom WAV, dolžine $1, skupno $2", + "timedmedia-wav-pcm-required": "Naložite lahko samo PCM (Pulse Code Modulation) WAV.", + "timedmedia-mp4-short-video": "Videodatoteka MP4 $1, $2", + "timedmedia-mp4-long-video": "Avdio-/videodatoteka MP4, $1, dolžine $2, $4 × $5 pik, skupaj $3", + "timedmedia-no-player-js": "Oprostite, vaš brskalnik ima onemogočen JavaScript ali pa nima nobenega podprtega predvajalnika.
\nPosnetek lahko snamete ali prenesete predvajalnik za predvajanje posnetka v svojem brskalniku.", + "timedmedia-more": "Več ...", + "timedmedia-dismiss": "Zapri", + "timedmedia-download": "Prenesi", + "timedmedia-play-media": "Predvajaj", + "timedmedia-desc-link": "O datoteki", + "timedmedia-oggThumb-version": "OggHandler potrebuje oggThumb različice $1 ali več.", + "timedmedia-oggThumb-failed": "OggThumbu ni uspelo ustvariti predogledne sličice.", + "timedmedia-status-header": "Stanje prekodiranja", + "timedmedia-update-status": "Posodobi stanje prekodiranja", + "timedmedia-status": "Stanje", + "timedmedia-status-unknown": "Neznano stanje", + "timedmedia-transcodeinfo": "Oblika", + "timedmedia-actions": "Dejanja", + "timedmedia-direct-link": "Prenos", + "timedmedia-not-ready": "Ni pripravljeno", + "timedmedia-completed-on": "Končano $1", + "timedmedia-error-on": "Napaka dne $1", + "timedmedia-started-transcode": "Začeto pred $1. $2", + "timedmedia-percent-done": "Končanih približno $1 %", + "timedmedia-in-job-queue": "Dodano v čakalno vrsto pred $1", + "timedmedia-unknown-target-size": "Neznana ciljna velikost, prekodirano $1", + "timedmedia-days": "$1 {{PLURAL:$1|dan|dneva|dnevi|dni}}", + "timedmedia-hours": "$1 {{PLURAL:$1|ura|uri|ure|ur}}", + "timedmedia-minutes": "$1 {{PLURAL:$1|minuta|minuti|minute|minut}}", + "timedmedia-seconds": "$1 {{PLURAL:$1|sekunda|sekundi|sekunde|sekund}}", + "timedmedia-reset": "Ponastavi prekodiranje", + "timedmedia-reset-confirm": "Ob ponastavitvi tega prekodiranja se bodo izbrisale vse obstoječe datoteke (če obstajajo), prekodiranje pa se bo ponovno dodalo v čakalno vrsto. Ponovno prekodiranje bo vzelo nekaj časa.

Ste prepričani, da želite nadaljevati?", + "timedmedia-reset-error": "Napaka pri ponastavljanju prekodiranja.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "vir $1", + "timedmedia-source-file-desc": "Izvirna datoteka $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Izvirna datoteka $1 ($2)", + "timedmedia-derivative-160p.ogv": "Ogg 160P", + "timedmedia-derivative-desc-160p.ogv": "Video Ogg za majhno pasovno širino (160P)", + "timedmedia-derivative-desc-360p.ogv": "Spletni pretočni video Ogg (360P)", + "timedmedia-derivative-desc-480p.ogv": "Spletni pretočni video Ogg (480P)", + "timedmedia-derivative-desc-720p.ogv": "Visokokakovostni prenosni video Ogg (720P)", + "timedmedia-derivative-desc-160p.webm": "Spletni pretočni WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM za pretakanje preko spleta (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM za pretakanje preko spleta (480P)", + "timedmedia-derivative-desc-720p.webm": "Visokokakovostni WebM za prenos (720P)", + "timedmedia-derivative-desc-320p.mp4": "Napravam prijazni MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "Spletni pretočni WebM (160P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 kakovosti HD (720P)", + "timedmedia-subtitle-new": "Ustvarite nov prevod ali uredite obstoječega", + "timedmedia-subtitle-new-desc": "Izberite jezik in pritisnite gumb '''{{int:Timedmedia-subtitle-new-go}}'''.", + "timedmedia-subtitle-new-go": "Pojdi", + "timedmedia-subtitle-language": "podnapisi v jeziku: $1 ($2)", + "timedmedia-subtitle-no-video": "S trenutno podnaslovno stranjo ni povezan noben videoposnetek", + "timedmedia-subtitle-no-subtitles": "V jeziku $1 trenutno ni nobenih podnapisov za ta video; stran lahko [{{fullurl:{{FULLPAGENAME}}|action=edit}} odprete za urejanje] in jih dodate.", + "timedmedia-subtitle-remote": "Časovno besedilo datoteke gostuje na $1", + "timedmedia-subtitle-remote-link": "[$1 Ogledate si lahko opisno stran] datoteke na $2", + "timedmediahandler": "Upravljavec časovne predstavnosti", + "timedmedia-videos": "{{PLURAL:$1|$ video|$ videa|$ videi|$ videov}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 video Ogg|$1 videa Ogg|$1 videi Ogg|$1 videov Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 video WebM|$1 videa WebM|$1 videi WebM|$1 videov WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|eno prekodiranje|dve prekodiranji|$1 prekodiranja|$1 prekodiranj}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|eno tekoče prekodiranje|dve tekoči prekodiranji|$1 prekodiranja|$1 prekodiranj}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|eno čakajoče prekodiranje|dve čakajoči prekodiranji|$1 čakajoča prekodiranja|$1 čakajočih prekodiranj}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|eno neuspelo prekodiranje|dve neuspeli prekodiranji|$1 neuspela prekodiranja|$1 neuspelih prekodiranj}}", + "timedmedia-file": "Datoteka:", + "timedmedia-audios": "$1 {{PLURAL:$1|zvočna datoteka|zvočni datoteki|zvočne datoteke|zvočnih datotek}}", + "timedmedia-ogg-audios": "$1 {{PLURAL:$1|zvočna datoteka|zvočni datoteki|zvočne datoteke|zvočnih datotek}} Ogg", + "timedmedia-flac-audios": "$1 {{PLURAL:$1|zvočna datoteka|zvočni datoteki|zvočne datoteke|zvočnih datotek}} FLAC", + "timedmedia-wav-audios": "$1 {{PLURAL:$1|zvočna datoteka|zvočni datoteki|zvočne datoteke|zvočnih datotek}} WAV", + "right-transcode-reset": "Ponastavitev neuspelih ali pretvorjenih videov, da so ponovno vstavljeni v čakalno vrsto opravil", + "right-transcode-status": "Ogled [[Special:TimedMediaHandler|informacij o trenutni dejavnosti pretvarjanja]]", + "action-transcode-status": "ogled trenutnega stanja pretvorbe" +} diff --git a/extensions/TimedMediaHandler/i18n/sq.json b/extensions/TimedMediaHandler/i18n/sq.json new file mode 100644 index 00000000..bad879c1 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sq.json @@ -0,0 +1,20 @@ +{ + "@metadata": { + "authors": [ + "Dori", + "Liridon" + ] + }, + "timedmedia-ogg-short-audio": "Skedë zanore Ogg $1, $2", + "timedmedia-ogg-short-video": "Skedë pamore Ogg $1, $2", + "timedmedia-ogg-short-general": "Skedë mediatike Ogg $1, $2", + "timedmedia-ogg-long-audio": "Skedë zanore Ogg $1, kohëzgjatja $2, $3", + "timedmedia-ogg-long-video": "Skedë pamore Ogg $1, kohëzgjatja $2, $4×$5 pixel, $3", + "timedmedia-no-player-js": "Na vjen keq, browser-i juaj ose ka të çaktivizuar JavaScript ose nuk ka mbështetje për ndonjë player.
\nJu mund të shkarkoni klipin ose shkarkoni një player për të luajtur klipin në shfletuesin tuaj.", + "timedmedia-more": "Më shumë...", + "timedmedia-dismiss": "Mbylle", + "timedmedia-download": "Shkarko skedën", + "timedmedia-desc-link": "Rreth kësaj skede", + "timedmedia-source-file": "$1 burimi", + "timedmedia-source-audio-file-desc": "Skedari $1 orgjinal ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/sr-ec.json b/extensions/TimedMediaHandler/i18n/sr-ec.json new file mode 100644 index 00000000..b6a3a4df --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sr-ec.json @@ -0,0 +1,55 @@ +{ + "@metadata": { + "authors": [ + "Milicevic01", + "Millosh", + "Rancher", + "Sasa Stefanovic", + "Михајло Анђелковић" + ] + }, + "timedmedia-desc": "Обрађивач за звук, видео и усклађени текст, с подршком за формате WebM, Ogg Theora, Vorbis и SRT", + "timedmedia-ogg-short-audio": "Ogg $1 звучна датотека, $2", + "timedmedia-ogg-short-video": "Ogg $1 видео-снимак, $2.", + "timedmedia-ogg-short-general": "Ogg $1 медијска датотека, $2.", + "timedmedia-ogg-long-audio": "Ogg $1 звучна датотека, трајање $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 видео-снимак, трајање $2, $4 × $5 пиксела, $3.", + "timedmedia-ogg-long-multiplexed": "Мултиплексирани .ogg аудио/видео снимак, $1, трајање $2, $4 × $5 пиксела, $3.", + "timedmedia-ogg-long-general": "Ogg медијска датотека, трајање $2, $3.", + "timedmedia-ogg-long-error": "Неисправна .ogg датотека: $1.", + "timedmedia-no-player-js": "Жао нам је, али вашем прегледачу је онемогућена јаваскрипта или немате одговарајући плејер.
\nМожете преузети клип или преузети плејер да репродукујете клип у вашем прегледачу.", + "timedmedia-more": "Више...", + "timedmedia-dismiss": "Затвори", + "timedmedia-download": "Преузми датотеку", + "timedmedia-desc-link": "Подаци о овој датотеци", + "timedmedia-days": "{{PLURAL:$1|1 дан|$1 дана}}", + "timedmedia-hours": "{{PLURAL:$1|1 сат|$1 сата|$1 сати}}", + "timedmedia-minutes": "{{PLURAL:$1|1 минут|$1 минута}}", + "timedmedia-seconds": "{{PLURAL:$1|1 секунд|$1 секунде|$1 секунди}}", + "timedmedia-reset": "Поништи прекодирање", + "timedmedia-reset-confirm": "Поништавање овог прекодирања ће уклонити постојећу датотеку (ако постоји) и вратити прекодирање на списак задатака. Ова радња може да потраје.

\nЖелите ли да наставите?", + "timedmedia-reset-error": "Грешка при поништавању задатака за прекодирање.", + "timedmedia-ogg": "Ogg", + "timedmedia-webm": "WebM", + "timedmedia-flac": "FLAC", + "timedmedia-source-file": "Изворни $1", + "timedmedia-source-file-desc": "Изворни $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Тип датотеке $1 ($2)", + "timedmedia-derivative-160p.ogv": "Ogg 160п", + "timedmedia-derivative-desc-160p.ogv": "Нископропусни .ogg видео (160п)", + "timedmedia-derivative-360p.ogv": "Ogg 360п", + "timedmedia-derivative-desc-360p.ogv": "Ogg видео за емитовање (360п)", + "timedmedia-derivative-480p.ogv": "Ogg 480п", + "timedmedia-derivative-desc-480p.ogv": "Ogg видео за емитовање (480п)", + "timedmedia-derivative-720p.ogv": "Ogg 720п", + "timedmedia-derivative-desc-720p.ogv": "Висококвалитетни .ogg видео за преузимање (720п)", + "timedmedia-derivative-360p.webm": "WebM 360п", + "timedmedia-derivative-desc-360p.webm": "WebM за емитовање (360п)", + "timedmedia-derivative-480p.webm": "WebM 480п", + "timedmedia-derivative-desc-480p.webm": "WebM за емитовање (480п)", + "timedmedia-derivative-720p.webm": "WebM 720п", + "timedmedia-derivative-desc-720p.webm": "Висококвалитетни WebM за преузимање (720п)", + "timedmedia-subtitle-language": "$1 ($2) титлови", + "timedmedia-subtitle-no-video": "Нема видео-снимака повезаних са текућом страницом с титловима", + "timedmedia-subtitle-no-subtitles": "Тренутно нема $1 титлова за овај видео-снимак. Можете да их додате тако што ћете [{{fullurl:{{FULLPAGENAME}}|action=edit}} уредити страницу]" +} diff --git a/extensions/TimedMediaHandler/i18n/sr-el.json b/extensions/TimedMediaHandler/i18n/sr-el.json new file mode 100644 index 00000000..936e1e92 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sr-el.json @@ -0,0 +1,53 @@ +{ + "@metadata": { + "authors": [ + "Michaello", + "Milicevic01", + "Rancher" + ] + }, + "timedmedia-desc": "Obrađivač za zvuk, video i usklađeni tekst, s podrškom za formate WebM, Ogg Theora, Vorbis i SRT", + "timedmedia-ogg-short-audio": "Ogg $1 zvučna datoteka, $2", + "timedmedia-ogg-short-video": "Ogg $1 video-snimak, $2.", + "timedmedia-ogg-short-general": "Ogg $1 medijska datoteka, $2.", + "timedmedia-ogg-long-audio": "Ogg $1 zvučna datoteka, dužina $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 video-snimak, trajanje $2, $4 × $5 piksela, $3.", + "timedmedia-ogg-long-multiplexed": "Multipleksirani .ogg audio/video snimak, $1, trajanje $2, $4 × $5 piksela, $3.", + "timedmedia-ogg-long-general": "Ogg medijska datoteka, dužina $2, $3.", + "timedmedia-ogg-long-error": "Neispravna .ogg datoteka: $1.", + "timedmedia-no-player-js": "Žao nam je, ali vašem pregledaču je onemogućena javaskripta ili nemate odgovarajući plejer.
\nMožete preuzeti klip ili preuzeti plejer da reprodukujete klip u vašem pregledaču.", + "timedmedia-more": "Više...", + "timedmedia-dismiss": "Zatvori", + "timedmedia-download": "Preuzmi datoteku", + "timedmedia-desc-link": "Podaci o ovoj datoteci", + "timedmedia-days": "{{PLURAL:$1|1 dan|$1 dana}}", + "timedmedia-hours": "{{PLURAL:$1|1 sat|$1 sata|$1 sati}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minut|$1 minuta}}", + "timedmedia-seconds": "{{PLURAL:$1|1 sekund|$1 sekunde|$1 sekundi}}", + "timedmedia-reset": "Poništi prekodiranje", + "timedmedia-reset-confirm": "Poništavanje ovog prekodiranja će ukloniti postojeću datoteku (ako postoji) i vratiti prekodiranje na spisak zadataka. Ova radnja može da potraje.

\nŽelite li da nastavite?", + "timedmedia-reset-error": "Greška pri poništavanju zadataka za prekodiranje.", + "timedmedia-ogg": "Ogg", + "timedmedia-webm": "WebM", + "timedmedia-source-file": "Izvorni $1", + "timedmedia-source-file-desc": "Izvorni $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Tip datoteke $1 ($2)", + "timedmedia-derivative-160p.ogv": "Ogg 160p", + "timedmedia-derivative-desc-160p.ogv": "Niskopropusni .ogg video (160p)", + "timedmedia-derivative-360p.ogv": "Ogg 360p", + "timedmedia-derivative-desc-360p.ogv": "Ogg video za emitovanje (360p)", + "timedmedia-derivative-480p.ogv": "Ogg 480p", + "timedmedia-derivative-desc-480p.ogv": "Ogg video za emitovanje (480p)", + "timedmedia-derivative-720p.ogv": "Ogg 720p", + "timedmedia-derivative-desc-720p.ogv": "Visokokvalitetni .ogg video za preuzimanje (720p)", + "timedmedia-derivative-360p.webm": "WebM 360p", + "timedmedia-derivative-desc-360p.webm": "WebM za emitovanje (360p)", + "timedmedia-derivative-480p.webm": "WebM 480p", + "timedmedia-derivative-desc-480p.webm": "WebM za emitovanje (480p)", + "timedmedia-derivative-720p.webm": "WebM 720p", + "timedmedia-derivative-desc-720p.webm": "Visokokvalitetni WebM za preuzimanje (720p)", + "timedmedia-subtitle-language": "$1 ($2) titlovi", + "timedmedia-subtitle-no-video": "Nema video-snimaka povezanih sa tekućom stranicom s titlovima", + "timedmedia-subtitle-no-subtitles": "Trenutno nema $1 titlova za ovaj video-snimak. Možete da ih dodate tako što ćete [{{fullurl:{{FULLPAGENAME}}|action=edit}} urediti stranicu]", + "timedmedia-file": "Datoteka" +} diff --git a/extensions/TimedMediaHandler/i18n/stq.json b/extensions/TimedMediaHandler/i18n/stq.json new file mode 100644 index 00000000..9657a896 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/stq.json @@ -0,0 +1,20 @@ +{ + "@metadata": { + "authors": [ + "Pyt" + ] + }, + "timedmedia-desc": "Stjuurengsprogramm foar Ogg Theora- un Vorbis-Doatäie, inklusive n JavaScript-Ouspielsoftware", + "timedmedia-ogg-short-audio": "Ogg-$1-Audiodoatäi, $2", + "timedmedia-ogg-short-video": "Ogg-$1-Videodoatäi, $2", + "timedmedia-ogg-short-general": "Ogg-$1-Mediadoatäi, $2", + "timedmedia-ogg-long-audio": "Ogg-$1-Audiodoatäi, Loangte: $2, $3", + "timedmedia-ogg-long-video": "Ogg-$1-Videodoatäi, Loangte: $2, $4×$5 Pixel, $3", + "timedmedia-ogg-long-multiplexed": "Ogg-Audio-/Video-Doatäi, $1, Loangte: $2, $4×$5 Pixel, $3", + "timedmedia-ogg-long-general": "Ogg-Mediadoatäi, Loangte: $2, $3", + "timedmedia-ogg-long-error": "Uungultige Ogg-Doatäi: $1", + "timedmedia-more": "Optione …", + "timedmedia-dismiss": "Sluute", + "timedmedia-download": "Doatäi spiekerje", + "timedmedia-desc-link": "Uur disse Doatäi" +} diff --git a/extensions/TimedMediaHandler/i18n/su.json b/extensions/TimedMediaHandler/i18n/su.json new file mode 100644 index 00000000..2fddd5c2 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/su.json @@ -0,0 +1,19 @@ +{ + "@metadata": { + "authors": [ + "Kandar" + ] + }, + "timedmedia-ogg-short-audio": "Koropak sora $1 ogg, $2", + "timedmedia-ogg-short-video": "Koropak vidéo $1 ogg, $2", + "timedmedia-ogg-short-general": "Koropak média $1 ogg, $2", + "timedmedia-ogg-long-audio": "Koropak sora $1 ogg, lilana $2, $3", + "timedmedia-ogg-long-video": "Koropak vidéo $1 ogg, lilana $2, $4×$5 piksel, $3", + "timedmedia-ogg-long-multiplexed": "Koropak sora/vidéo ogg multipléks, $1, lilana $2, $4×$5 piksel, $3 gembleng", + "timedmedia-ogg-long-general": "Koropak média ogg, lilana $2, $3", + "timedmedia-ogg-long-error": "Koropak ogg teu valid: $1", + "timedmedia-more": "Lianna...", + "timedmedia-dismiss": "Tutup", + "timedmedia-download": "Bedol", + "timedmedia-desc-link": "Ngeunaan ieu koropak" +} diff --git a/extensions/TimedMediaHandler/i18n/sv.json b/extensions/TimedMediaHandler/i18n/sv.json new file mode 100644 index 00000000..dbba4c91 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sv.json @@ -0,0 +1,125 @@ +{ + "@metadata": { + "authors": [ + "Ainali", + "Jon Harald Søby", + "Jopparn", + "Lejonel", + "Lokal Profil", + "Rotsee", + "Skalman", + "WikiPhoenix", + "Abbedabb" + ] + }, + "timedmedia-desc": "Hanterare för ljud-, video- och tidsbestämd text, med stöd för format WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Ogg $1 ljudfil, $2", + "timedmedia-ogg-short-video": "Ogg $1 videofil, $2", + "timedmedia-ogg-short-general": "Ogg $1 mediafil, $2", + "timedmedia-ogg-long-audio": "Ogg $1 ljudfil, längd $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 videofil, längd $2, $4×$5 pixel, $3", + "timedmedia-ogg-long-multiplexed": "Ogg multiplexad ljud/video-fil, $1, längd $2, $4×$5 pixel, $3 totalt", + "timedmedia-ogg-long-general": "Ogg mediafil, längd $2, $3", + "timedmedia-ogg-long-error": "Felaktig Ogg-fil: $1", + "timedmedia-ogg-long-no-streams": "Ogg-mediafil. Varning: Ingen av de omkodare som används i denna fil kunde identifieras.", + "timedmedia-webm-short-video": "WebM $1 videofil, $2", + "timedmedia-webm-long-video": "WebM-fil för ljud/video, $1, längd $2, $4 × $5 pixlar, $3 totalt", + "timedmedia-flac-short-audio": "FLAC-ljudfil, $1", + "timedmedia-flac-long-audio": "FLAC-ljudfil, längd $1, $2 totalt", + "timedmedia-wav-short-audio": "WAV-ljudfil, $1", + "timedmedia-wav-long-audio": "WAV-ljudfil, längd $1, $2 total", + "timedmedia-wav-pcm-required": "Du kan enbart ladda upp PCM (Pulskodsmodulerad) WAV", + "timedmedia-mp4-short-video": "MP4 $1 videofil, $2", + "timedmedia-mp4-long-video": "MP4-fil för ljud/video, $1, längd $2, $4 × $5 pixlar, $3 totalt", + "timedmedia-no-player-js": "Tyvärr, din webbläsare har antingen JavaScript inaktiverat eller inte någon spelare som stöds.
\nDu kan ladda ner klippet eller hämta en spelare för att spela upp klippet i din webbläsare.", + "timedmedia-more": "Mer...", + "timedmedia-dismiss": "Stäng", + "timedmedia-download": "Ladda ner filen", + "timedmedia-play-media": "Spela upp media", + "timedmedia-desc-link": "Om filen", + "timedmedia-oggThumb-version": "OggHandler kräver oggThumb version $1 eller senare.", + "timedmedia-oggThumb-failed": "oggThumb misslyckades med att skapa miniatyrbilden.", + "timedmedia-status-header": "Transkodningsstatus", + "timedmedia-update-status": "Uppdatera transkodningsstatus", + "timedmedia-status": "Status", + "timedmedia-status-unknown": "Okänd status", + "timedmedia-transcodebitrate": "Bithastighet", + "timedmedia-transcodeduration": "Kodningstid", + "timedmedia-transcodeinfo": "Format", + "timedmedia-actions": "Åtgärder", + "timedmedia-direct-link": "Ladda ned", + "timedmedia-not-ready": "Inte redo", + "timedmedia-completed-on": "Slutfördes $1", + "timedmedia-error-on": "Fel $1", + "timedmedia-started-transcode": "Startade $1 sedan. $2", + "timedmedia-percent-done": "Ungefär $1% klar", + "timedmedia-in-job-queue": "Lades till i jobbkön för $1sedan", + "timedmedia-unknown-target-size": "Okänd målstorlek, $1-kodad", + "timedmedia-days": "{{PLURAL:$1|1 dag|$1 dagar}}", + "timedmedia-hours": "{{PLURAL:$1|1 timme|$1 timmar}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minut|$1 minuter}}", + "timedmedia-seconds": "{{PLURAL:$1|1 sekund|$1 sekunder}}", + "timedmedia-reset": "Nollställ transkodningen", + "timedmedia-reset-confirm": "Nollställning av denna transkodning tar bort alla befintliga filer (om närvarande), och lägger åter till transkodningen till jobbkön. Det kommer att ta tid att om-transkoda.

\nÄr du säker på att du vill fortsätta?", + "timedmedia-reset-error": "Fel i nollställningen av transkod-jobbet.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 Källa", + "timedmedia-source-file-desc": "Ursprunglig $1-fil, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Ursprunglig $1-fil ($2)", + "timedmedia-derivative-desc-160p.ogv": "Låg bandbredd Ogg-video (160P)", + "timedmedia-derivative-desc-360p.ogv": "Webbströmningsbar Ogg-video (360P)", + "timedmedia-derivative-desc-480p.ogv": "Webbströmningsbar Ogg-video (480P)", + "timedmedia-derivative-desc-720p.ogv": "Högkvalitets nedladdningsbar Ogg-video (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Nedladdningsbar Ogg-video i full HD (1080P)", + "timedmedia-derivative-desc-160p.webm": "Webbströmningsbar WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "Webbströmningsbar WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "Webbströmningsbar WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "Högkvalitets nedladdningsbar WebM (720P)", + "timedmedia-derivative-desc-1080p.webm": "Nedladdningsbar WebM i full HD (1080P)", + "timedmedia-derivative-360p.vp9.webm": "VP9 360P", + "timedmedia-derivative-480p.vp9.webm": "VP9 480P", + "timedmedia-derivative-720p.vp9.webm": "VP9 720P", + "timedmedia-derivative-1080p.vp9.webm": "VP9 1080P", + "timedmedia-derivative-desc-320p.mp4": "Enhetsvänlig MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "Webbströmningsbar MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "HD-kvalitet MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "MP4 med kvalitet i full HD (1080P)", + "timedmedia-subtitle-new": "Skapa ny översättning eller redigera befintlig", + "timedmedia-subtitle-new-desc": "Välj språk och tryck på knappen '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Gå", + "timedmedia-subtitle-language": "$1 ($2) undertexter", + "timedmedia-subtitle-no-video": "Det finns ingen video som är associerade med den aktuella undertextssidan", + "timedmedia-subtitle-no-subtitles": "Det finns för närvarande ingen textning på $1 för denna video, du kan [{{fullurl:{{FULLPAGENAME}}|action=edit}} redigera denna sida] för att lägga till dem", + "timedmedia-subtitle-remote": "Undertexter (timed text) för denna fil finns på $1", + "timedmedia-subtitle-remote-link": "Du kan [$1 visa beskrivningssidan] för denna fil på $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 videoklipp|$1 videoklipp}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg-video|$1 Ogg-videor}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM-video|$1 WebM-videor}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transkodning|$1 transkodningar}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 aktiv transkodning|$1 aktiva transkodningar}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 köad transkodning|$1 köade transkodningar}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 misslyckad transkodning|$1 misslyckade transkodningar}}", + "timedmedia-file": "Fil", + "timedmedia-audios": "{{PLURAL:$1|$1 ljudfil|$1 ljudfiler}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg-ljudfil|$1 Ogg-ljudfiler}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC-ljudfil|$1 FLAC-ljudfiler}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV-ljudfil|$1 WAV-ljudfiler}}", + "right-transcode-reset": "Återställ misslyckade eller transkodade videor så att de åter infogas i jobbkön.", + "right-transcode-status": "Visa [[Special:TimedMediaHandler|information om den aktuella transkodningsaktivitetet]]", + "action-transcode-status": "visa den aktuella transkodningsstatusen", + "orphanedtimedtext": "Föräldralösa TimedText-sidor", + "orphanedtimedtext-summary": "Lista över [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] sidor som inte har en motsvarande fil.", + "orphanedtimedtext-unsupported": "Denna special-sida har bara stöd på MySQL-databaser.", + "orphanedtimedtext-notimedtext": "TimedText är inte aktiverat på denna wiki.", + "apihelp-query+transcodestatus-description": "Hämta transkodningsstatusen för en viss filsida.", + "apihelp-query+transcodestatus-example-1": "Hämta transkodningsstatusen för [[:File:Clip.webm]]", + "apihelp-query+videoinfo-description": "Utökar imageinfo till att inkludera källinformation för video (derivat)", + "apihelp-query+videoinfo-param-prop": "Vilken videoinformation att hämta:\n;timestamp:Lägger till tidsstämpeln för den uppladdade versionen.\n;user:Lägger till användaren som laddade upp videoversionen.\n;userid:Lägger till användar-ID:et som laddade upp videoversionen.\n;comment:Kommentar för versionen.\n;parsedcomment:Tolka kommentaren för versionen.\n;canonicaltitle:Lägger till den kanoniska titeln för videofilen.\n;url:Ger URL:en till videon och beskrivningssidan.\n;size:Lägger till storleken på videon i bytes, dess höjd och vidd. Sidantal och varaktighet om applicerbart.\n;dimensions:Alias för size.\n;sha1:Lägger till SHA-1-hash för videon.\n;mime:Lägger till MIME-typ för videon.\n;thumbmime:Lägger till MIME-typ för videons miniatyr (kräver url och parametern $1urlwidth).\n;mediatype:Lägger till mediatyp för videon.\n;metadata:Listar Exif-metadata för versionen av videon.\n;commonmetadata:Listar filformatgenerisk metadata för versionen av videon.\n;extmetadata:Listar formaterad metadata kombinerad från flera källor. Resultaten är HTML-formaterade.\n;archivename:Lägger till filnamn för arkivversionen för icke-senaste versioner.\n;bitdepth:Lägger till bitdjup för versionen.\n;uploadwarning:Används av [[Special:Upload]]-sidan för att få information om en existerande fil. Ej avsedd för bruk utanför Mediawiki core.\n;derivatives:Lägger till en lista för de olika format- och kvalitetsversionerna för en audio- eller videofil som är tillgängliga.", + "apihelp-query+videoinfo-example-1": "Hämta information om [[:File:Folgers.ogv]]", + "apihelp-transcodereset-description": "Användare med \"transcode-reset\"-rättigheten kan återställa och köra om ett transkodningsjobb.", + "apihelp-transcodereset-param-title": "Mediefilens titel.", + "apihelp-transcodereset-param-transcodekey": "Den transkodningsnyckel du önskar återställa. Hämta från [[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "Återställ alla transkodningar för [[:File:Clip.webm]]", + "apihelp-transcodereset-example-2": "Återställ \"360_560kbs.webm\"-transkodningsnyckeln för [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/sw.json b/extensions/TimedMediaHandler/i18n/sw.json new file mode 100644 index 00000000..51d86efc --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/sw.json @@ -0,0 +1,14 @@ +{ + "@metadata": { + "authors": [ + "Stephenwanjau" + ] + }, + "timedmedia-download": "Pakua faili", + "timedmedia-desc-link": "Kuhusu faili hii", + "timedmedia-status": "Hali", + "timedmedia-actions": "Vitendo", + "timedmedia-not-ready": "Haiko tayari", + "timedmedia-subtitle-new-go": "Nenda", + "timedmedia-file": "Faili" +} diff --git a/extensions/TimedMediaHandler/i18n/szl.json b/extensions/TimedMediaHandler/i18n/szl.json new file mode 100644 index 00000000..105eeaa9 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/szl.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Krol111" + ] + }, + "timedmedia-source-file": "Zdrzůdło ($1)", + "timedmedia-source-audio-file-desc": "Uoryginalny plik $1 ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/ta.json b/extensions/TimedMediaHandler/i18n/ta.json new file mode 100644 index 00000000..69410ef5 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ta.json @@ -0,0 +1,32 @@ +{ + "@metadata": { + "authors": [ + "Balajijagadesh", + "Karthi.dr", + "Shanmugamp7", + "மதனாஹரன்", + "Jayarathina" + ] + }, + "timedmedia-ogg-long-general": "Ogg ஊடக கோப்பு, நீளம் $2 , $3", + "timedmedia-ogg-long-error": "செல்லாத ogg கோப்பு: $1", + "timedmedia-no-player-js": "மன்னிக்கவும், உங்கள் உலாவியில் ஜாவாஸ்கிரிப்ட் முடக்கப்பட்டிருக்கின்றது அல்லது தகுந்த இயக்கி இல்லை.
\nநீங்கள் இக்கோப்பினை தரவிரக்கம் செய்யலாம் அல்லது தகுந்த இயக்கியினை தரவிரக்கம் செய்து இதனை உங்கள் உலாவியில் ஓட்டலாம்.", + "timedmedia-more": "மேலும்...", + "timedmedia-dismiss": "மூடுக", + "timedmedia-download": "கோப்பை பதிவிறக்கம் செய்", + "timedmedia-play-media": "ஊடகத்தை ஓடவிடு", + "timedmedia-desc-link": "இந்தக் கோப்பைப் பற்றி", + "timedmedia-status": "நிலை", + "timedmedia-status-unknown": "அறியப்படாத நிலை", + "timedmedia-actions": "செயல்கள்", + "timedmedia-not-ready": "தயாராக இல்லை", + "timedmedia-percent-done": "ஏறத்தாழ $1% முடிந்தது", + "timedmedia-mp4": "எம்பி4", + "timedmedia-source-file": "$1 மூலம்", + "timedmedia-source-file-desc": "மூல $1, $2 × $3 ($4)", + "timedmedia-subtitle-new": "புதிய மொழிபெயர்ப்பை உருவாக்கவும் அல்லது உள்ளதைத் தொகுக்கவும்", + "timedmedia-subtitle-new-go": "செல்", + "timedmedia-subtitle-language": "$1 ($2) துணைத்தலைப்புக்கள்", + "timedmedia-videos": "{{PLURAL:$1|$1 காணொளி|$1 காணொளிகள்}}", + "timedmedia-file": "கோப்பு" +} diff --git a/extensions/TimedMediaHandler/i18n/tcy.json b/extensions/TimedMediaHandler/i18n/tcy.json new file mode 100644 index 00000000..622447e7 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/tcy.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "VASANTH S.N." + ] + }, + "timedmedia-source-file": "$1 ಮೂಲ", + "timedmedia-source-audio-file-desc": "ಮೂಲ $1 ಕಡತ ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/te.json b/extensions/TimedMediaHandler/i18n/te.json new file mode 100644 index 00000000..96cc2e73 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/te.json @@ -0,0 +1,37 @@ +{ + "@metadata": { + "authors": [ + "Kiranmayee", + "Veeven", + "వైజాసత్య", + "రహ్మానుద్దీన్" + ] + }, + "timedmedia-ogg-short-audio": "Ogg $1 శ్రావ్యక ఫైలు, $2", + "timedmedia-ogg-short-video": "Ogg $1 వీడియో ఫైలు, $2", + "timedmedia-ogg-short-general": "Ogg $1 మీడియా ఫైలు, $2", + "timedmedia-ogg-long-audio": "Ogg $1 శ్రవణ ఫైలు, నిడివి $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 వీడియో ఫైలు, నిడివి $2, $4×$5 పిక్సెళ్ళు, $3", + "timedmedia-ogg-long-multiplexed": "ఓగ్ మల్టిప్లెక్సుడ్ శ్రవణ/దృశ్యక ఫైలు, $1, నిడివి $2, $4×$5 పిక్సెళ్ళు, $3 మొత్తం", + "timedmedia-ogg-long-general": "Ogg మీడియా ఫైలు, నిడివి $2, $3", + "timedmedia-ogg-long-error": "తప్పుడు ogg ఫైలు: $1", + "timedmedia-no-player-js": "క్షమించాలి, మీ విహారిణిలో జావాస్క్రిప్ట్ అచేతనంగా ఉంది లేదా మీ విహారిణికి తగిన దృశ్యచాలకం అందుబాటులో లేదు.
\nమీరు మీరు వీడియో క్లిప్ ని దించుకోవచ్చు లేదా దృశ్యచాలకాన్ని దింపుకొని ఈ వీడియో క్లిప్ ను మీ విహారిణిలో చూడవచ్చు.", + "timedmedia-more": "మరిన్ని...", + "timedmedia-dismiss": "మూసివేయి", + "timedmedia-download": "ఫైలుని దిగుమతి చేసుకోండి", + "timedmedia-desc-link": "ఈ ఫైలు గురించి", + "timedmedia-status": "స్థితి", + "timedmedia-status-unknown": "తెలియని స్థితి", + "timedmedia-actions": "చర్యలు", + "timedmedia-days": "{{PLURAL:$1|1 రోజు|$1 రోజులు}}", + "timedmedia-hours": "{{PLURAL:$1|1 గంట|$1 గంటలు}}", + "timedmedia-minutes": "{{PLURAL:$1|1 నిమిషం|$1 నిమిషాలు}}", + "timedmedia-seconds": "{{PLURAL:$1|1 క్షణం|$1 క్షణాలు}}", + "timedmedia-source-file": "$1 మూలం", + "timedmedia-source-file-desc": "అసలు $1 దస్త్రం, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "అసలు $1 దస్త్రం ($2)", + "timedmedia-subtitle-new-go": "వెళ్ళు", + "timedmedia-subtitle-language": "$1 ($2) ఉపశీర్షికలు", + "timedmedia-videos": "{{PLURAL:$1|$1 వీడియో|$1 వీడియోలు}}", + "timedmedia-file": "దస్త్రం" +} diff --git a/extensions/TimedMediaHandler/i18n/tg-cyrl.json b/extensions/TimedMediaHandler/i18n/tg-cyrl.json new file mode 100644 index 00000000..abfda498 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/tg-cyrl.json @@ -0,0 +1,20 @@ +{ + "@metadata": { + "authors": [ + "Ibrahim" + ] + }, + "timedmedia-desc": "Ба дастгирандае барои парвандаҳои Ogg Theora ва Vorbis, бо пахшкунандаи JavaScript", + "timedmedia-ogg-short-audio": "Ogg $1 парвандаи савтӣ, $2", + "timedmedia-ogg-short-video": "Ogg $1 парвандаи наворӣ, $2", + "timedmedia-ogg-short-general": "Ogg $1 парвандаи расона, $2", + "timedmedia-ogg-long-audio": "Ogg $1 парвандаи савтӣ, тӯл $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 парвандаи наворӣ, тӯл $2, $4×$5 пикселҳо, $3", + "timedmedia-ogg-long-multiplexed": "Парвандаи Ogg савтӣ/наворӣ печида, $1, тӯл $2, $4×$5 пикселҳо, дар маҷмӯъ $3", + "timedmedia-ogg-long-general": "Парвандаи расонаи Ogg, тӯл $2, $3", + "timedmedia-ogg-long-error": "Парвандаи ғайримиҷози ogg: $1", + "timedmedia-more": "Бештар...", + "timedmedia-dismiss": "Бастан", + "timedmedia-download": "Боргирии парванда", + "timedmedia-desc-link": "Дар бораи ин парванда" +} diff --git a/extensions/TimedMediaHandler/i18n/tg-latn.json b/extensions/TimedMediaHandler/i18n/tg-latn.json new file mode 100644 index 00000000..b32a59aa --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/tg-latn.json @@ -0,0 +1,20 @@ +{ + "@metadata": { + "authors": [ + "Liangent" + ] + }, + "timedmedia-desc": "Ba dastgirandae baroi parvandahoi Ogg Theora va Vorbis, bo paxşkunandai JavaScript", + "timedmedia-ogg-short-audio": "Ogg $1 parvandai savtī, $2", + "timedmedia-ogg-short-video": "Ogg $1 parvandai navorī, $2", + "timedmedia-ogg-short-general": "Ogg $1 parvandai rasona, $2", + "timedmedia-ogg-long-audio": "Ogg $1 parvandai savtī, tūl $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 parvandai navorī, tūl $2, $4×$5 pikselho, $3", + "timedmedia-ogg-long-multiplexed": "Parvandai Ogg savtī/navorī pecida, $1, tūl $2, $4×$5 pikselho, dar maçmū' $3", + "timedmedia-ogg-long-general": "Parvandai rasonai Ogg, tūl $2, $3", + "timedmedia-ogg-long-error": "Parvandai ƣajrimiçozi ogg: $1", + "timedmedia-more": "Beştar...", + "timedmedia-dismiss": "Bastan", + "timedmedia-download": "Borgiriji parvanda", + "timedmedia-desc-link": "Dar borai in parvanda" +} diff --git a/extensions/TimedMediaHandler/i18n/tk.json b/extensions/TimedMediaHandler/i18n/tk.json new file mode 100644 index 00000000..7b0d49a8 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/tk.json @@ -0,0 +1,20 @@ +{ + "@metadata": { + "authors": [ + "Hanberke" + ] + }, + "timedmedia-desc": "Ogg Theora we Vorbis faýllary üçin işleýji, JavaScript pleýeri bilen bilelikde", + "timedmedia-ogg-short-audio": "Ogg $1 ses faýly, $2", + "timedmedia-ogg-short-video": "Ogg $1 wideo faýly, $2", + "timedmedia-ogg-short-general": "Ogg $1 media faýly, $2", + "timedmedia-ogg-long-audio": "Ogg $1 ses faýly, uzynlyk $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 wideo faýly, uzynlyk $2, $4×$5 piksel, $3", + "timedmedia-ogg-long-multiplexed": "Ogg multipleks audio/wideo faýly, $1, uzynlyk $2, $4×$5 piksel, $3 jemi", + "timedmedia-ogg-long-general": "Ogg media faýly, uzynlyk $2, $3", + "timedmedia-ogg-long-error": "Nädogry ogg faýly: $1", + "timedmedia-more": "Has köp...", + "timedmedia-dismiss": "Ýap", + "timedmedia-download": "Faýl düşür", + "timedmedia-desc-link": "Bu faýl hakda" +} diff --git a/extensions/TimedMediaHandler/i18n/tl.json b/extensions/TimedMediaHandler/i18n/tl.json new file mode 100644 index 00000000..9aa38812 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/tl.json @@ -0,0 +1,84 @@ +{ + "@metadata": { + "authors": [ + "AnakngAraw", + "Sky Harbor" + ] + }, + "timedmedia-desc": "Tagahawak para sa audio, bidyo at inoorasang teksto, na may pagtangkilik ng anyo para sa WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "$1 na talaksang pangtunog ng Ogg, $2", + "timedmedia-ogg-short-video": "$1 talaksang pampalabas (''video'') ng Ogg, $2", + "timedmedia-ogg-short-general": "$1 talaksang pangmidya ng Ogg, $2", + "timedmedia-ogg-long-audio": "$1 talaksang pantunog ng Ogg, haba $2, $3", + "timedmedia-ogg-long-video": "$1 talaksan ng palabas ng Ogg, haba $2, $4×$5 mga piksel, $3", + "timedmedia-ogg-long-multiplexed": "magkasanib at nagsasabayang talaksang nadirinig o audio/palabas ng Ogg, $1, haba $2, $4×$5 mga piksel, $3 sa kalahatan", + "timedmedia-ogg-long-general": "Talaksang pangmidya ng ''Ogg'', haba $2, $3", + "timedmedia-ogg-long-error": "Hindi tanggap na talaksang ''ogg'': $1", + "timedmedia-webm-short-video": "$1 na talaksan ng bidyo ng WebM, $2", + "timedmedia-webm-long-video": "Talaksang pamparinig/bidyo ng WebM, $1, haba $2, $4 × $5 mga piksel, $3 lahat-lahat", + "timedmedia-no-player-js": "Paumahin, ang pantingin-tingin mo ay maaaring may hindi gumaganang JavaScript o walang anumang tinatangkilik na pampaandar.
\nMaaari kang magkargang pababa ng kaputol o magkargang pababa ng isang pampaandar upang mapaandar ang kaputol sa loob ng iyong pantingin-tingin.", + "timedmedia-more": "Marami pa…", + "timedmedia-dismiss": "Isara", + "timedmedia-download": "Ikarga ang talaksan", + "timedmedia-play-media": "Paandarin ang midya", + "timedmedia-desc-link": "Tungkol sa talaksang ito", + "timedmedia-oggThumb-version": "Nangangailangan ang OggHandler ng bersyong $1 o mas luma ng oggThumb.", + "timedmedia-oggThumb-failed": "Nabigong lumikha ang oggThumb ng munting larawan.", + "timedmedia-status-header": "Katayuan ng transkodigo", + "timedmedia-update-status": "Isapanahon ang katayuan ng transkodigo", + "timedmedia-status": "Katayuan", + "timedmedia-status-unknown": "Hindi alam na katayuan", + "timedmedia-transcodeinfo": "Paglalarawan sa hinangong transkodigo", + "timedmedia-actions": "Mga kilos", + "timedmedia-direct-link": "Ikargang paibaba ang hinango", + "timedmedia-not-ready": "Hindi nakahanda", + "timedmedia-completed-on": "Nabuo ang transkodigo noong $1", + "timedmedia-error-on": "Kamalian sa transkodigo noong $1.", + "timedmedia-started-transcode": "Nagsimula ang transkodigo noong $1 na ang nakalilipas. $2", + "timedmedia-percent-done": "Halos $1% na ang natatapos", + "timedmedia-in-job-queue": "Idinagdag sa pila ng Gawain noong $1 na ang nakalilipas", + "timedmedia-unknown-target-size": "Hindi nalalaman na puntiryang sukat, $1 naisakodigo", + "timedmedia-days": "{{PLURAL:$1|araw|$1 mga araw}}", + "timedmedia-hours": "{{PLURAL:$1|horas|$1 mga oras}}", + "timedmedia-minutes": "{{PLURAL:$1|minuto|$1 mga minuto}}", + "timedmedia-seconds": "{{PLURAL:$1|segundo|$1 mga segundo}}", + "timedmedia-reset": "Itakdang muli ang transkodigo", + "timedmedia-reset-confirm": "Ang muling pagtatakda ng transkodigong ito ay magtatanggal ng anumang umiiral na talaksan (kung naroroon), at muling nitong idaragdag ang transkodigo sa pila ng trabaho. Gugugol ito ng ilang panahon upang muling maitranskodigo.

\nNakatitiyak ka ba na nais mong magpatuloy?", + "timedmedia-reset-error": "Kamalian sa gawain ng muling pagtatakda ng transkodigo.", + "timedmedia-ogg": "Ogg", + "timedmedia-webm": "WebM", + "timedmedia-source-file": "$1 pinagkukunan", + "timedmedia-source-file-desc": "Orihinal na $1, $2 × $3 ( $4)", + "timedmedia-source-audio-file-desc": "Orihinal na talaksang $1 ($2)", + "timedmedia-derivative-160p.ogv": "Ogg 160P", + "timedmedia-derivative-desc-160p.ogv": "Mababang lapad ng paha ng bidyo ng Ogg (160P)", + "timedmedia-derivative-360p.ogv": "Ogg 360P", + "timedmedia-derivative-desc-360p.ogv": "Mapapaanod sa Web na bidyo ng Ogg (360P)", + "timedmedia-derivative-480p.ogv": "Ogg 480P", + "timedmedia-derivative-desc-480p.ogv": "Mapapaanod sa Web na bidyo ng Ogg (480P)", + "timedmedia-derivative-720p.ogv": "Ogg 720P", + "timedmedia-derivative-desc-720p.ogv": "Mataas na uri ng bidyo ng Ogg na maikakargang paibaba (720P)", + "timedmedia-derivative-160p.webm": "WebM 160P", + "timedmedia-derivative-desc-160p.webm": "Mapapaanod sa Web na WebM (160P)", + "timedmedia-derivative-360p.webm": "WebM 360P", + "timedmedia-derivative-desc-360p.webm": "Mapapaanod sa Web na WebM (360P)", + "timedmedia-derivative-480p.webm": "WebM 480P", + "timedmedia-derivative-desc-480p.webm": "Mapapaanod sa Web na WebM (480P)", + "timedmedia-derivative-720p.webm": "WebM 720P", + "timedmedia-derivative-desc-720p.webm": "Mataas na uri ng WebM na maikakargang paibaba (720P)", + "timedmedia-subtitle-new": "Lumikha ng bagong salinwika o baguhin ang umiiral na", + "timedmedia-subtitle-new-desc": "Palitan ang bahaging '''$1''' ng iyong [[:en:ISO 639|kodigo ng wika]] at pindutin ang pindutang '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Gawin", + "timedmedia-subtitle-language": "$1 ($2) kabahaging mga pamagat", + "timedmedia-subtitle-no-video": "Walang bidyo na may kaugnayan sa pangkasalukuyang pahina ng kabahaging pamagat", + "timedmedia-subtitle-no-subtitles": "Kasalukuyang walang mga kabahaging pamagat sa loob ng $1 para sa bidyong ito, maaari mong [{{fullurl:{{FULLPAGENAME}}|action=edit}} baguhin ang pahinang ito] upang maidagdag ang mga ito", + "timedmediahandler": "Inoorasang Panghawak ng Midya", + "timedmedia-videos": "{{PLURAL:$1|$1 bidyo|$1 mga bidyo}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 bidyo ng Ogg|$1 mga bidyo ng Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 bidyo ng WebM|$1 mga bidyo ng WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transkodigo|$1 mga transkodigo}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 tumatakbong transkodigo|$1 tumatakbong mga transkodigo}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 nakapilang transkodigo|$1 nakapilang mga transkodigo}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 nabigong transkodigo|$1 nabigong mga transkodigo}}", + "timedmedia-file": "Talaksan" +} diff --git a/extensions/TimedMediaHandler/i18n/tly.json b/extensions/TimedMediaHandler/i18n/tly.json new file mode 100644 index 00000000..8123d4cc --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/tly.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Erdemaslancan" + ] + }, + "timedmedia-subtitle-new-go": "Давардеј" +} diff --git a/extensions/TimedMediaHandler/i18n/tr.json b/extensions/TimedMediaHandler/i18n/tr.json new file mode 100644 index 00000000..e3e00c8f --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/tr.json @@ -0,0 +1,80 @@ +{ + "@metadata": { + "authors": [ + "Erkan Yilmaz", + "Incelemeelemani", + "Joseph", + "Mach", + "Runningfridgesrule", + "Srhat", + "Sayginer", + "Stultiwikia", + "Mavrikant" + ] + }, + "timedmedia-desc": "Ses işleyicisi, video ve zamanlanmış metin formatları için WebM, Ogg Theora, Vorbis, srt desteği", + "timedmedia-ogg-short-audio": "Ogg $1 ses dosyası, $2", + "timedmedia-ogg-short-video": "Ogg $1 video dosyası, $2", + "timedmedia-ogg-short-general": "Ogg $1 medya dosyası, $2", + "timedmedia-ogg-long-audio": "Ogg $1 ses dosyası, süre $2, $3", + "timedmedia-ogg-long-video": "Ogg $1 video dosyası, süre $2, $4×$5 piksel, $3", + "timedmedia-ogg-long-multiplexed": "Ogg çok düzeyli ses/video dosyası, $1, süre $2, $4×$5 piksel, $3 genelde", + "timedmedia-ogg-long-general": "Ogg medya dosyası, süre $2, $3", + "timedmedia-ogg-long-error": "Geçersiz ogg dosyası: $1", + "timedmedia-webm-short-video": "WebM $1 video dosyası, $2", + "timedmedia-webm-long-video": "WebM ses/video dosyası, $1, süre $2, $4 × $5 piksel, $3 tümü", + "timedmedia-flac-short-audio": "FLAC ses dosyası, $1", + "timedmedia-flac-long-audio": "FLAC ses dosyası, süre $1, $2 tümü", + "timedmedia-wav-short-audio": "WAV ses dosyası, $1", + "timedmedia-wav-long-audio": "WAV ses dosyası, süre $1, $2 tümü", + "timedmedia-wav-pcm-required": "Yalnızca PCM (Darbeli Kod Modülasyonu) WAV yükleyebilirsiniz.", + "timedmedia-mp4-short-video": "MP4 $1 video dosyası, $2", + "timedmedia-mp4-long-video": "MP4 ses/video dosyası, $1, süre $2, $4 × $5 piksel, $3 tümü", + "timedmedia-no-player-js": "Üzgünüz, tarayıcınızda JavaScript devredışı veya yüklü bir oynatıcı yok.
\nİsterseniz klibi indirebilir veya tarayıcınız için oynatıcıyı buradan indirebilirsiniz.", + "timedmedia-more": "Daha...", + "timedmedia-dismiss": "Kapat", + "timedmedia-download": "Dosya indir", + "timedmedia-play-media": "Medyayı oynat", + "timedmedia-desc-link": "Bu dosya hakkında", + "timedmedia-oggThumb-version": "OggHandler $1 veya daha yeni sürüm oggThumb gerektirir.", + "timedmedia-oggThumb-failed": "oggThumb küçük resim oluşturamadı.", + "timedmedia-status-header": "Transkodu durumu", + "timedmedia-update-status": "Transkodu durumunu güncelle", + "timedmedia-status": "Durum", + "timedmedia-status-unknown": "Bilinmeyen durum", + "timedmedia-transcodeinfo": "Format", + "timedmedia-actions": "İşlemler", + "timedmedia-direct-link": "Türevi indir", + "timedmedia-not-ready": "Hazır değil", + "timedmedia-completed-on": "Tamamlanan transkodu $1", + "timedmedia-error-on": "$1 üzerinde çapraz kodlama hatası", + "timedmedia-started-transcode": "$1 öncesinde transkod başlangıcı. $2", + "timedmedia-percent-done": "Tamamlanan $1% hakkında", + "timedmedia-in-job-queue": "$1 önce iş kuyruğuna eklendi", + "timedmedia-unknown-target-size": "$1 kodlamasında, bilinmeyen hedef boyutu", + "timedmedia-days": "{{PLURAL:$1|1 gün|$1 gün}}", + "timedmedia-hours": "{{PLURAL:$1|1 saat|$1 saat}}", + "timedmedia-minutes": "{{PLURAL:$1|1 dakika|$1 dakika}}", + "timedmedia-seconds": "{{PLURAL:$1|1 saniye|$1 saniye}}", + "timedmedia-reset": "Transkodu sıfırla", + "timedmedia-reset-confirm": "Bu transkodu sıfırladığınız takdirde (eğer varsa), mevcut dosyalar kaldırılacak ve yeniden iş kuyruğuna alınacaktır. Bu yeniden kodlama biraz zaman alacaktır.

\nDevam etmek istediğinize emin misiniz?", + "timedmedia-reset-error": "Transkodlama işinde sıfırlama hatası.", + "timedmedia-source-file": "$1 kaynağı", + "timedmedia-source-file-desc": "Orijinal $1 dosyası, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Orijinal $1 dosyası ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg videosu düşük bant genişliği (160P)", + "timedmedia-derivative-desc-1080p.ogv": "Full HD indirilebilir Ogg video (1080P)", + "timedmedia-derivative-desc-1080p.webm": "Full HD indirilebilir WebM (1080P)", + "timedmedia-derivative-desc-1080p.mp4": "Full HD kalitede MP4 (1080P)", + "timedmedia-subtitle-new-go": "Git", + "timedmedia-videos": "{{PLURAL:$1|$1 video|$1 video}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg videosu|$1 Ogg videosu}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM videosu|$1 WebM videosu}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transkod|$1 transkod}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 çalışan transkod|$1 çalışan transkod}}", + "timedmedia-file": "Dosya", + "timedmedia-audios": "{{PLURAL:$1|$1 ses dosyası|$1 ses dosyası}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 Ogg ses dosyası|$1 Ogg ses dosyası}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 FLAC ses dosyası|$1 FLAC ses dosyası}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 WAV ses dosyası|$1 WAV ses dosyası}}" +} diff --git a/extensions/TimedMediaHandler/i18n/ts.json b/extensions/TimedMediaHandler/i18n/ts.json new file mode 100644 index 00000000..240bfe4c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ts.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "Thuvack" + ] + }, + "timedmedia-more": "Swinwana…", + "timedmedia-dismiss": "Pfala" +} diff --git a/extensions/TimedMediaHandler/i18n/tt-cyrl.json b/extensions/TimedMediaHandler/i18n/tt-cyrl.json new file mode 100644 index 00000000..e9224c6c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/tt-cyrl.json @@ -0,0 +1,17 @@ +{ + "@metadata": { + "authors": [ + "Ильнар", + "Derslek" + ] + }, + "timedmedia-no-player-js": "Кызганычка каршы сезнең браузерда JavaScript сүндерелгән, яисә кирәкле уйнаткыч юк.
\nСез роликны йөкли яисә аны браузерда уйнатыр өченуйнаткыч кушымта йөкли аласыз.", + "timedmedia-more": "Тулырак...", + "timedmedia-dismiss": "Ябу", + "timedmedia-download": "Файлны алу", + "timedmedia-desc-link": "Файл турында мәгълүмат", + "timedmedia-oggThumb-version": "OggHandler $1 юрамасыннан да югарырак oggThumb тәэминатын сорый.", + "timedmedia-oggThumb-failed": "oggThumb нигезендә миниатюраны ясап булмады.", + "timedmedia-source-file": "$1-нче чыганак", + "timedmedia-source-audio-file-desc": "Оригиналь $1-файл ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/tzm.json b/extensions/TimedMediaHandler/i18n/tzm.json new file mode 100644 index 00000000..cf77a584 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/tzm.json @@ -0,0 +1,8 @@ +{ + "@metadata": { + "authors": [ + "Tifinaghes" + ] + }, + "timedmedia-mp4": "MP4" +} diff --git a/extensions/TimedMediaHandler/i18n/ug-arab.json b/extensions/TimedMediaHandler/i18n/ug-arab.json new file mode 100644 index 00000000..05e95334 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ug-arab.json @@ -0,0 +1,13 @@ +{ + "@metadata": { + "authors": [ + "Sahran" + ] + }, + "timedmedia-dismiss": "ياپ", + "timedmedia-status": "ھالەت", + "timedmedia-actions": "مەشغۇلات", + "timedmedia-not-ready": "تەييار ئەمەس", + "timedmedia-subtitle-new-go": "يۆتكەل", + "timedmedia-file": "ھۆججەت" +} diff --git a/extensions/TimedMediaHandler/i18n/uk.json b/extensions/TimedMediaHandler/i18n/uk.json new file mode 100644 index 00000000..a70bd282 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/uk.json @@ -0,0 +1,134 @@ +{ + "@metadata": { + "authors": [ + "AS", + "Ahonc", + "Andriykopanytsia", + "AtUkr", + "Base", + "NickK", + "Olvin", + "Prima klasy4na", + "Steve.rusyn", + "SteveR", + "Тест", + "Ата", + "Piramidion", + "Dars" + ] + }, + "timedmedia-desc": "Оброблювач для аудіо, відео і субтитрів з підтримкою форматів WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "Звуковий файл Ogg $1, $2", + "timedmedia-ogg-short-video": "Відео-файл Ogg $1, $2", + "timedmedia-ogg-short-general": "Файл Ogg $1, $2", + "timedmedia-ogg-long-audio": "звуковий файл Ogg $1, довжина $2, $3", + "timedmedia-ogg-long-video": "відео-файл Ogg $1, довжина $2, $4×$5 пікселів, $3", + "timedmedia-ogg-long-multiplexed": "мультиплексний аудіо/відео-файл ogg, $1, довжина $2, $4×$5 пікселів, $3 усього", + "timedmedia-ogg-long-general": "медіа-файл Ogg, довжина $2, $3", + "timedmedia-ogg-long-error": "Неправильний Ogg-файл: $1", + "timedmedia-ogg-long-no-streams": "Медіафайл Ogg. Застереження: Не розпізнано жоден з кодеків, використаних у цьому файлі.", + "timedmedia-webm-short-video": "WebM $1 відео-файл, $2", + "timedmedia-webm-long-video": "WebM аудіо/відео файл, $1, тривалість $2, $4 × $5 {{PLURAL:$5|піксель|пікселі|пікселів}}, всього $3", + "timedmedia-flac-short-audio": "FLAC аудіо-файл,$1", + "timedmedia-flac-long-audio": "FLAC аудіо-файл, тривалість $1, $2 швидкість", + "timedmedia-wav-short-audio": "Аудіо-файл WAV, $1", + "timedmedia-wav-long-audio": "Аудіо-файл WAV, тривалість $1, $2 швидкість", + "timedmedia-wav-pcm-required": "Ви можете тільки завантажити PCM (імпульсно-кодова модуляція) WAV.", + "timedmedia-mp4-short-video": "MP4 $1 відео-файл, $2", + "timedmedia-mp4-long-video": "MP4 аудіо/відео файл, $1, тривалість $2, $4 × $5 {{PLURAL:$5|піксель|пікселі|пікселів}}, всього $3", + "timedmedia-no-player-js": "На жаль, у Вашому браузері вимкнено JavaScript і не підтримується жодного програвача.
\nВи можете завантажити ролик або завантажити програвач для відтворенні ролику у браузері.", + "timedmedia-more": "Більше…", + "timedmedia-dismiss": "Закрити", + "timedmedia-download": "Завантажити файл", + "timedmedia-play-media": "Відтворити", + "timedmedia-desc-link": "Інформація про цей файл", + "timedmedia-oggThumb-version": "OggHandler вимагає oggThumb версії $1 або більш пізньої.", + "timedmedia-oggThumb-failed": "oggThumb не вдалось створити мініатюру.", + "timedmedia-status-header": "Статус декодування", + "timedmedia-update-status": "Оновити статус декодування", + "timedmedia-status": "Статус", + "timedmedia-status-unknown": "Невідомий статус", + "timedmedia-transcodebitrate": "Бітрейт", + "timedmedia-transcodeduration": "Час кодування", + "timedmedia-transcodeinfo": "Формат", + "timedmedia-actions": "Дії", + "timedmedia-direct-link": "Завантажити", + "timedmedia-not-ready": "Не готово", + "timedmedia-completed-on": "Завершено $1", + "timedmedia-error-on": "Помилка на $1.", + "timedmedia-started-transcode": "Розпочато $1 назад. $2", + "timedmedia-percent-done": "Приблизно $1% готово", + "timedmedia-in-job-queue": "Додано до черги завдань $1 назад", + "timedmedia-unknown-target-size": "Невідомий цільовий розмір, $1 при кодуванні", + "timedmedia-days": "$1 {{PLURAL:$1|день|дні|днів}}", + "timedmedia-hours": "$1 {{PLURAL:$1|гадину|години|годин}}", + "timedmedia-minutes": "$1 {{PLURAL:$1|хвилину|хвилини|хвилин}}", + "timedmedia-seconds": "$1 {{PLURAL:$1|секунду|секунди|секунд}}", + "timedmedia-reset": "Перезапуск декодування", + "timedmedia-reset-confirm": "Перезапуск декодування видалить наявний файл (якщо він існує), і декодування знов буде додано в чергу завдань. Повторне декодування займе якийсь час.

\nВи упевнені, що хочете продовжити?", + "timedmedia-reset-error": "Помилка при перезапуску декодування.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Джерело $1", + "timedmedia-source-file-desc": "Оригінальний $1 файл, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "Оригінальний $1 файл ($2)", + "timedmedia-derivative-desc-160p.ogv": "Ogg-відео низької якості (160P)", + "timedmedia-derivative-desc-240p.ogv": "Потокове Ogg-відео (240P)", + "timedmedia-derivative-desc-360p.ogv": "Потокове Ogg-відео (360P)", + "timedmedia-derivative-desc-480p.ogv": "Потокове Ogg-відео (480P)", + "timedmedia-derivative-desc-720p.ogv": "Високоякісне Ogg-відео (720P) для завантаження", + "timedmedia-derivative-desc-1080p.ogv": "Скачуване Ogg-відео у форматі Full HD (1080P)", + "timedmedia-derivative-desc-160p.webm": "Потокове WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "Потокове WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "Потокове WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "Високоякісне WebM (720P) для завантаження", + "timedmedia-derivative-desc-1080p.webm": "Скачуване WebM-відео у форматі Full HD (1080P)", + "timedmedia-derivative-desc-2160p.webm": "Скачуване WebM-відео у форматі Full 4K (2160P)", + "timedmedia-derivative-desc-360p.vp9.webm": "Потокове WebM VP9 (360P)", + "timedmedia-derivative-desc-480p.vp9.webm": "Потокове WebM VP9 (480P)", + "timedmedia-derivative-desc-720p.vp9.webm": "Потокове WebM VP9 у форматі HD (720P)", + "timedmedia-derivative-desc-1080p.vp9.webm": "Потокове WebM VP9 у форматі Full HD (1080P)", + "timedmedia-derivative-desc-2160p.vp9.webm": "Потокове WebM-відео у форматі Full 4K (2160P)", + "timedmedia-derivative-desc-320p.mp4": "Сумісний з MP$ (320P) пристрій", + "timedmedia-derivative-desc-480p.mp4": "Веб-потокове MP4 (480 Р)", + "timedmedia-derivative-desc-720p.mp4": "MP4 якості HD (720P)", + "timedmedia-derivative-desc-1080p.mp4": "MP4-відео у форматі Full HD (1080P)", + "timedmedia-derivative-desc-2160p.mp4": "MP4-відео у форматі Full 4K (2160P)", + "timedmedia-subtitle-new": "Створити новий переклад або відредагувати існуючий", + "timedmedia-subtitle-new-desc": "Виберіть мову та натисніть '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Перейти", + "timedmedia-subtitle-language": "$1 ($2) субтитри", + "timedmedia-subtitle-no-video": "Немає відео, пов'язаного з поточною сторінкою субтитрів", + "timedmedia-subtitle-no-subtitles": "В даний час немає субтитрів на $1 для цього відео, Ви можете [{{fullurl:{{FULLPAGENAME}}|action=edit}} змінити цю сторінку] для їх додавання", + "timedmedia-subtitle-remote": "Текст до цього файлу розміщений на $1", + "timedmedia-subtitle-remote-link": "Ви можете [$1 переглянути опис сторінки] для цього файлу на $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 відео}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 Ogg відео}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 WebM відео}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|1=$1 перекодування|$1 перекодувань}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|1=$1 працюючі перекодування|$1 працюючих перекодувань}}", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|1=$1 перекодування в черзі|$1 перекодувань в черзі}}", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|1=$1 невдалі перекодування|$1 невдалих перекодувань}}", + "timedmedia-file": "Файл", + "timedmedia-audios": "{{PLURAL:$1|$1 аудіо-файл|$1 аудіо-файлів̟|$1 аудіо-файли}}", + "timedmedia-ogg-audios": "{{PLURAL:$1|$1 аудіо-файл Ogg|$1 аудіо-файлів Ogg|$1 аудіо-файли Ogg}}", + "timedmedia-flac-audios": "{{PLURAL:$1|$1 аудіо-файл FLAC|$1 аудіо-файлів FLAC|$1 аудіо-файли FLAC}}", + "timedmedia-wav-audios": "{{PLURAL:$1|$1 аудіо-файл WAV|$1 аудіо-файлів WAV|$1 аудіо-файли WAV}}", + "right-transcode-reset": "скидання невдалих або перекодованих відео із повторним занесенням їх в чергу завдань", + "right-transcode-status": "перегляд [[Special:TimedMediaHandler|інформації про поточну діяльність з перекодування]]", + "action-transcode-status": "Перегляд поточного стану перекодування", + "orphanedtimedtext": "Сторінки-сироти TimedText", + "orphanedtimedtext-summary": "Список сторінок [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]], які не мають відповідного файлу.", + "orphanedtimedtext-unsupported": "Ця спеціальна сторінка підтримується лише базами даних MySQL.", + "orphanedtimedtext-notimedtext": "TimedText не увімкнений у цій вікі.", + "apihelp-query+transcodestatus-description": "Отримати транскод-статус для даної сторінки файлу.", + "apihelp-query+transcodestatus-example-1": "Отримати транскод-статус для [[:File:Clip.webm]]", + "apihelp-query+videoinfo-description": "Розширює imageinfo і включає інформацію джерела відео (похідні)", + "apihelp-query+videoinfo-param-prop": "Яку інформацію про відео отримувати:\n;timestamp:Додає часову мітку завантаженої версії.\n;user:Додає користувача, який завантажив версію відео.\n;userid:Додає ID користувача, який завантажив версію відео.\n;comment:Коментар до версії.\n;parsedcomment:Парсинг коментаря до версії.\n;canonicaltitle:Додає канонічний заголовок відеофайлу.\n;url:Дає URL на відео та сторінку опису.\n;size:Додає розміри відео у байтах, його висоту і ширину. Номер сторінки і тривалість додаються за можливості.\n;dimensions:Аліас розміру.\n;sha1:Додає геш SHA-1 відео.\n;mime:Додає MIME-тип відео.\n;thumbmime:Додає MIME-тип мініатюри відео (вимагає url і параметр $1urlwidth).\n;mediatype:Додає медіатип відео.\n;metadata:Виводить список Exif-метаданих версії відео.\n;commonmetadata:Виводить список метаданих формату файлу версії відео.\n;extmetadata:Виводить список форматованих метаданих, скомбінованих з кількох джерел. Результати відформатовані в HTML.\n;archivename:Додає назву файлу архівованої версії для не останніх версій.\n;bitdepth:Додає бітову глибину версії.\n;uploadwarning:Використовується сторінкою [[Special:Upload]] для отримання інформації про наявний файл. Не призначено для використання поза ядром MediaWiki.\n;derivatives:Додає масив доступних версій іншого формату і якості аудіо- чи відеофайлу.", + "apihelp-query+videoinfo-example-1": "Витяг інформації про [[:File:Folgers.ogv]]", + "apihelp-transcodereset-description": "Користувачі з правом «transcode-reset» можуть скинути і повторити задачу перекодування.", + "apihelp-transcodereset-param-title": "Заголовок медіафайлу.", + "apihelp-transcodereset-param-transcodekey": "Перекодувати ключ, який Ви хочете скинути. Вибірка з [[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]].", + "apihelp-transcodereset-example-1": "Скинути усі перекодування для [[:File:Clip.webm]]", + "apihelp-transcodereset-example-2": "Скинути ключ перекодування «360_560kbs.webm» для [[:File:Clip.webm]]" +} diff --git a/extensions/TimedMediaHandler/i18n/ur.json b/extensions/TimedMediaHandler/i18n/ur.json new file mode 100644 index 00000000..7b58606a --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/ur.json @@ -0,0 +1,21 @@ +{ + "@metadata": { + "authors": [ + "පසිඳු කාවින්ද", + "Muhammad Shuaib", + "عثمان خان شاہ" + ] + }, + "timedmedia-no-player-js": "معذرت، آپ کے براؤزر میں جاواسکرپٹ فعال نہیں یا کوئی معاون پلیئر شامل نہیں ہے۔
\nاس کلپ کو اپنے براؤزر میں چلانے کے لیے آپ کلپ کو ڈاؤنلوڈ کرلیں یا or پلیئر ڈاؤنلوڈ کرلیں۔", + "timedmedia-more": "مزید...", + "timedmedia-dismiss": "بند", + "timedmedia-download": "فائل ڈاؤن لوڈ ، اتارنا", + "timedmedia-play-media": "کھیلنے کے میڈیا", + "timedmedia-desc-link": "اس فائل کے بارے میں", + "timedmedia-status": "حیثیت", + "timedmedia-status-unknown": "نامعلوم مقام", + "timedmedia-actions": "اعمال", + "timedmedia-source-file": "$1 مسودہ", + "timedmedia-source-audio-file-desc": "اصلی $1 ملف ($2)", + "timedmedia-file": "فائل" +} diff --git a/extensions/TimedMediaHandler/i18n/vec.json b/extensions/TimedMediaHandler/i18n/vec.json new file mode 100644 index 00000000..c567687b --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/vec.json @@ -0,0 +1,85 @@ +{ + "@metadata": { + "authors": [ + "Candalua", + "GatoSelvadego" + ] + }, + "timedmedia-desc": "Gestor pa' i file audio, video e có sototitołi; formati suportai: WebM, Ogg Theora, Vorbis, srt", + "timedmedia-ogg-short-audio": "File audio Ogg $1, $2", + "timedmedia-ogg-short-video": "File video Ogg $1, $2", + "timedmedia-ogg-short-general": "File multimedial Ogg $1, $2", + "timedmedia-ogg-long-audio": "File audio Ogg $1, durata $2, $3", + "timedmedia-ogg-long-video": "File video Ogg $1, durata $2, dimensioni $4×$5 pixel, $3", + "timedmedia-ogg-long-multiplexed": "File audio/video multiplexed Ogg $1, durata $2, dimensioni $4×$5 pixel, conplessivamente $3", + "timedmedia-ogg-long-general": "File multimedial Ogg, durata $2, $3", + "timedmedia-ogg-long-error": "File ogg mìa valido: $1", + "timedmedia-webm-short-video": "File video WebM $1, $2", + "timedmedia-webm-long-video": "File audio/video WebM $1, durata $2, dimension $4×$5 pixel, conplesivamente $3", + "timedmedia-mp4-short-video": "File video MP4 $1, $2", + "timedmedia-mp4-long-video": "File audio/video MP4 $1, durata $2, dimension $4×$5 pixel, conplesivamente $3", + "timedmedia-no-player-js": "El to browser el gà JavaScript destacà, opure no'l gà nissun riprodutor suportà.
\nTe pol descargar el file multimediale o un riprodutor par farlo 'ndar sul to browser.", + "timedmedia-more": "Altro...", + "timedmedia-dismiss": "Sara", + "timedmedia-download": "Descarga el file", + "timedmedia-play-media": "Riproduxi el file multimediałe", + "timedmedia-desc-link": "Informazion su sto file", + "timedmedia-oggThumb-version": "Par OggHandler ghe vole oggThumb version $1 o sucessiva.", + "timedmedia-oggThumb-failed": "oggThumb no'l xe stà bon de crear la miniadura.", + "timedmedia-status-header": "Stato transcodifega", + "timedmedia-update-status": "Axorna stato transcodifega", + "timedmedia-status": "Stato", + "timedmedia-status-unknown": "Stato sconosùo", + "timedmedia-transcodeinfo": "Descrision derivà dal Transcodexe", + "timedmedia-actions": "Asion", + "timedmedia-direct-link": "Descarga derivà", + "timedmedia-not-ready": "Nó pronto", + "timedmedia-completed-on": "Transodifega conpletà $1", + "timedmedia-error-on": "Eror inte ła transcodifega so $1.", + "timedmedia-started-transcode": "Transcodifega tacà $1 fa. $2", + "timedmedia-percent-done": "$1% sirca conpletà", + "timedmedia-in-job-queue": "Xontà a ła coa de laoro $1 fa", + "timedmedia-unknown-target-size": "Dimension de destinasion sconosùa, $1 codifegà", + "timedmedia-days": "{{PLURAL:$1|1 xorno|$1 xorni}}", + "timedmedia-hours": "{{PLURAL:$1|1 ora|$1 ore}}", + "timedmedia-minutes": "{{PLURAL:$1|1 minuto|$1 minuti}}", + "timedmedia-seconds": "{{PLURAL:$1|1 secondo|$1 secondi}}", + "timedmedia-reset": "Rexeta transcodifega", + "timedmedia-reset-confirm": "Resetando sta transcodifica tuti i file esistenti (se presenti) i sarà scancelà e la transcodifica la sarà de novo zontà a la coa de laoro. Ghe vorà un po' de tenpo par rifar la transcodifica.

\nVuto 'ndar vanti?", + "timedmedia-reset-error": "Eror inte'l resetajo de ła transcodifega.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Fonte $1", + "timedmedia-source-file-desc": "File orixenałe $1, $2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "File originale $1 ($2)", + "timedmedia-derivative-desc-160p.ogv": "Video Ogg a basa larghesa de banda (160P)", + "timedmedia-derivative-desc-360p.ogv": "Video Ogg trasmetibiłe in streaming via Web (360P)", + "timedmedia-derivative-desc-480p.ogv": "Video Ogg trasmetibiłe in streaming via Web (480P)", + "timedmedia-derivative-desc-720p.ogv": "Video Ogg descargabiłe in alta cuałità (720P)", + "timedmedia-derivative-desc-160p.webm": "WebM trasmetibiłe in streaming via Web (160P)", + "timedmedia-derivative-desc-360p.webm": "WebM trasmetibiłe in streaming via Web (360P)", + "timedmedia-derivative-desc-480p.webm": "WebM trasmetibiłe in streaming via Web (480P)", + "timedmedia-derivative-desc-720p.webm": "WebM descargabiłe in alta cuałità (720P)", + "timedmedia-derivative-desc-320p.mp4": "MP4 par dispoxitivi conpatibiłi (320P)", + "timedmedia-derivative-desc-480p.mp4": "MP4 trasmetibiłe in streaming via Web (480P)", + "timedmedia-derivative-desc-720p.mp4": "MP4 in cuałità HD (720P)", + "timedmedia-subtitle-new": "Crear na nova tradusion o canbiar una de chełe che ghe xe xà", + "timedmedia-subtitle-new-desc": "Siegli la lengua e struca el boton '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Và", + "timedmedia-subtitle-language": "$1 ($2) sototitołi", + "timedmedia-subtitle-no-video": "Nó ghe xe nisun video asocià a l'atuałe pàjina de i sototitołi", + "timedmedia-subtitle-no-subtitles": "Nó ghe xe al momento sototitołi in $1 par sto video, xe posibiłe [{{fullurl:{{FULLPAGENAME}}|action=edit}} canbiar sta pàjina] par xontarli", + "timedmedia-subtitle-remote": "I sototitołi par sto file i se cata so $1", + "timedmedia-subtitle-remote-link": "Te poi vardar ła [$1 pàjina de descrision] par sto file so $2", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "{{PLURAL:$1|$1 video}}", + "timedmedia-ogg-videos": "{{PLURAL:$1|$1 video Ogg}}", + "timedmedia-webm-videos": "{{PLURAL:$1|$1 video WebM}}", + "timedmedia-derivative-state-transcodes": "{{PLURAL:$1|$1 transcodifega|$1 transcodifeghe}}", + "timedmedia-derivative-state-active": "{{PLURAL:$1|$1 transcodifega|$1 transcodifeghe}} in execusion", + "timedmedia-derivative-state-queued": "{{PLURAL:$1|$1 transcodifega|$1 transcodifeghe}} in coa", + "timedmedia-derivative-state-failed": "{{PLURAL:$1|$1 transcodifega nó riusia|$1 transcodifeghe nó riusie}}", + "timedmedia-file": "File", + "right-transcode-reset": "Reinposta i video difetoxi o transcodifegai cusì che i posa esar incluxi de novo in coa de laoro.", + "right-transcode-status": "Vixuałixa [[Special:TimedMediaHandler|informasion so l'atuałe atività de transcodifega]]", + "action-transcode-status": "mostrar l'atuałe stato de ła transcodifega" +} diff --git a/extensions/TimedMediaHandler/i18n/vep.json b/extensions/TimedMediaHandler/i18n/vep.json new file mode 100644 index 00000000..9046204e --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/vep.json @@ -0,0 +1,11 @@ +{ + "@metadata": { + "authors": [ + "Игорь Бродский" + ] + }, + "timedmedia-more": "Enamba...", + "timedmedia-dismiss": "Peitta", + "timedmedia-download": "Jügutoitta fail", + "timedmedia-desc-link": "Informacii neciš failas" +} diff --git a/extensions/TimedMediaHandler/i18n/vi.json b/extensions/TimedMediaHandler/i18n/vi.json new file mode 100644 index 00000000..63187717 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/vi.json @@ -0,0 +1,130 @@ +{ + "@metadata": { + "authors": [ + "Minh Nguyen", + "Vinhtantran", + "පසිඳු කාවින්ද", + "Trần Nguyễn Minh Huy", + "Max20091" + ] + }, + "timedmedia-desc": "Bộ trình bày các tập tin âm thanh, video, và phụ đề hỗ trợ WebM, Ogg Theora, Vorbis, và srt", + "timedmedia-ogg-short-audio": "Tập tin âm thanh Ogg $1, $2", + "timedmedia-ogg-short-video": "tập tin video Ogg $1, $2", + "timedmedia-ogg-short-general": "tập tin Ogg $1, $2", + "timedmedia-ogg-long-audio": "tập tin âm thanh Ogg $1, dài $2, $3", + "timedmedia-ogg-long-video": "tập tin video Ogg $1, dài $2, $4×$5 điểm ảnh, $3", + "timedmedia-ogg-long-multiplexed": "tập tin Ogg có âm thanh và video ghép kênh, $1, dài $2, $4×$5 điểm ảnh, $3 tất cả", + "timedmedia-ogg-long-general": "tập tin phương tiện Ogg, dài $2, $3", + "timedmedia-ogg-long-error": "Tập tin Ogg có lỗi: $1", + "timedmedia-ogg-long-no-streams": "Tập tin phương tiện Ogg. Chú ý: Không có công nhận codec nào trong tập tin này.", + "timedmedia-webm-short-video": "tập tin video WebM $1, $2", + "timedmedia-webm-long-video": "tập tin WebM có âm thanh và video ghép kênh, $1, dài $2, $4×$5 điểm ảnh, $3 tất cả", + "timedmedia-flac-short-audio": "tập tin âm thanh FLAC, $1", + "timedmedia-flac-long-audio": "tập tin âm thanh FLAC, dài $1, $2 tất cả", + "timedmedia-wav-short-audio": "tập tin âm thanh WAV, $1", + "timedmedia-wav-long-audio": "tập tin âm thanh WAV, dài $1, $2 tất cả", + "timedmedia-wav-pcm-required": "Bạn chỉ có thể tải lên các tập tin WAV PCM (Pulse Code Modulation).", + "timedmedia-mp4-short-video": "tập tin video MP4 $1, $2", + "timedmedia-mp4-long-video": "tập tin âm thanh/video MP4, $1, dài $2, $4×$5 điểm ảnh, $3 tất cả", + "timedmedia-no-player-js": "Rất tiếc, hoặc JavaScript bị tắt trong trình duyệt của bạn hoặc trình duyệt không có trình chơi nào được hỗ trợ.
\nBạn có thể tải về đoạn âm hoặc đoạn video hay tải về một trình chơi để chơi đoạn âm hoặc đoạn video trong trình duyệt.", + "timedmedia-more": "Thêm nữa…", + "timedmedia-dismiss": "Đóng", + "timedmedia-download": "Tải tập tin xuống", + "timedmedia-play-media": "Chơi phương tiện", + "timedmedia-desc-link": "Chi tiết của tập tin này", + "timedmedia-oggThumb-version": "OggHandler cần oggThumb, phiên bản $1 trở lên.", + "timedmedia-oggThumb-failed": "oggThumb bị thất bại trong việc tạo hình thu nhỏ.", + "timedmedia-status-header": "Trạng thái chuyển mã", + "timedmedia-update-status": "Cập nhật trạng thái chuyển mã", + "timedmedia-status": "Trạng thái", + "timedmedia-status-unknown": "Trạng thái không rõ", + "timedmedia-transcodebitrate": "Tốc độ bit", + "timedmedia-transcodeduration": "Thời gian mã hóa", + "timedmedia-transcodeinfo": "Định dạng", + "timedmedia-actions": "Tác vụ", + "timedmedia-direct-link": "Tải về", + "timedmedia-not-ready": "Chưa sẵn", + "timedmedia-completed-on": "Hoàn tất vào $1", + "timedmedia-error-on": "Lỗi vào $1", + "timedmedia-started-transcode": "Bắt đầu cách đây $1. $2", + "timedmedia-percent-done": "Hoàn tất khoảng $1%", + "timedmedia-in-job-queue": "Đã thêm vào hàng đợi việc cách đây $1", + "timedmedia-unknown-target-size": "Tập tin đích có kích thước không rõ, đã chuyển mã $1", + "timedmedia-days": "$1 ngày", + "timedmedia-hours": "$1 giờ", + "timedmedia-minutes": "$1 phút", + "timedmedia-seconds": "$1 giây", + "timedmedia-reset": "Đặt lại chuyển mã", + "timedmedia-reset-confirm": "Việc đặt lại chuyển mã sẽ xóa tập tin nào hiện có, nếu có, và sẽ lại thêm việc chuyển mã vào hàng đợi việc. Việc chuyển mã lại sẽ mất một chút thì giờ.

\nBạn có chắc chắn muốn tiếp tục?", + "timedmedia-reset-error": "Lỗi khi đặt lại việc chuyển mã.", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "Tập tin $1 gốc", + "timedmedia-source-file-desc": "tập tin $1 gốc, $2×$3 ($4)", + "timedmedia-source-audio-file-desc": "Tập tin $1 gốc ($2)", + "timedmedia-derivative-160p.ogv": "Ogg 160p", + "timedmedia-derivative-desc-160p.ogv": "Video Ogg băng thông thấp (160p)", + "timedmedia-derivative-desc-240p.ogv": "Video Ogg có thể truyền dòng trên Web (240p)", + "timedmedia-derivative-360p.ogv": "Ogg 360p", + "timedmedia-derivative-desc-360p.ogv": "Video Ogg có thể truyền dòng trên Web (360p)", + "timedmedia-derivative-480p.ogv": "Ogg 480p", + "timedmedia-derivative-desc-480p.ogv": "Video Ogg có thể truyền dòng trên Web (480p)", + "timedmedia-derivative-720p.ogv": "Ogg 720p", + "timedmedia-derivative-desc-720p.ogv": "Video Ogg chất lượng cao có thể tải về (720p)", + "timedmedia-derivative-1080p.ogv": "Ogg 1080p", + "timedmedia-derivative-desc-1080p.ogv": "Video Ogg HD đầy đủ có thể tải về (1080p)", + "timedmedia-derivative-160p.webm": "WebM 160p", + "timedmedia-derivative-desc-160p.webm": "WebM có thể truyền dòng trên Web (160p)", + "timedmedia-derivative-360p.webm": "WebM 360p", + "timedmedia-derivative-desc-360p.webm": "WebM có thể truyền dòng trên Web (360p)", + "timedmedia-derivative-480p.webm": "WebM 480p", + "timedmedia-derivative-desc-480p.webm": "WebM có thể truyền dòng trên Web (480p)", + "timedmedia-derivative-720p.webm": "WebM 720p", + "timedmedia-derivative-desc-720p.webm": "WebM chất lượng cao có thể tải về (720p)", + "timedmedia-derivative-1080p.webm": "WebM 1080p", + "timedmedia-derivative-desc-1080p.webm": "WebM HD đầy đủ có thể tải về (1080p)", + "timedmedia-derivative-desc-2160p.webm": "WebM 4K đầy đủ có thể tải về (2160p)", + "timedmedia-derivative-desc-360p.vp9.webm": "WebM VP9 có thể truyền dòng trên Web (360p)", + "timedmedia-derivative-desc-480p.vp9.webm": "WebM VP9 có thể truyền dòng trên Web (480p)", + "timedmedia-derivative-desc-720p.vp9.webm": "WebM HD có thể truyền dòng trên Web (720p)", + "timedmedia-derivative-desc-1080p.vp9.webm": "WebM VP9 HD đầy đủ có thể truyền dòng (1080p)", + "timedmedia-derivative-desc-2160p.vp9.webm": "WebM VP9 4K đầy đủ có thể truyền dòng (2160p)", + "timedmedia-derivative-320p.mp4": "H.264 320p", + "timedmedia-derivative-desc-320p.mp4": "MP4 thích hợp với thiết bị (320p)", + "timedmedia-derivative-480p.mp4": "H.264 480p", + "timedmedia-derivative-desc-480p.mp4": "MP4 có thể truyền dòng trên Web (480p)", + "timedmedia-derivative-720p.mp4": "H.264 720p", + "timedmedia-derivative-desc-720p.mp4": "MP4 chất lượng HD (720p)", + "timedmedia-derivative-1080p.mp4": "H.264 1080p", + "timedmedia-derivative-desc-1080p.mp4": "MP4 HD đầy đủ (1080p)", + "timedmedia-derivative-desc-2160p.mp4": "MP4 4K đầy đủ (2160p)", + "timedmedia-subtitle-new": "Tạo bản dịch mới hoặc sửa đổi bản dịch đã tồn tại", + "timedmedia-subtitle-new-desc": "Chọn ngôn ngữ và bấm nút '''{{int:Timedmedia-subtitle-new-go}}'''", + "timedmedia-subtitle-new-go": "Tạo", + "timedmedia-subtitle-language": "Tiểu đề $1 ($2)", + "timedmedia-subtitle-no-video": "Không có video nào ứng với trang tiểu đề này", + "timedmedia-subtitle-no-subtitles": "Hiện không có tiểu đề nào cho video này bằng $1. Bạn có thể [{{fullurl:{{FULLPAGENAME}}|action=edit}} sửa đổi trang này] để thêm tiểu đề.", + "timedmedia-subtitle-remote": "Văn bản đồng bộ của tập tin này được cung cấp từ $1", + "timedmedia-subtitle-remote-link": "Bạn có thể [$1 xem trang miêu tả] tập tin này trên $2", + "timedmediahandler": "Bộ xử lý phương tiện đồng bộ", + "timedmedia-videos": "$1 video", + "timedmedia-ogg-videos": "$1 video Ogg", + "timedmedia-webm-videos": "$1 video WebM", + "timedmedia-derivative-state-transcodes": "$1 tập tin chuyển mã", + "timedmedia-derivative-state-active": "$1 tập tin đang chuyển mã", + "timedmedia-derivative-state-queued": "$1 tập tin chuyển mã trong hàng đợi", + "timedmedia-derivative-state-failed": "$1 tập tin chuyển mã thất bại", + "timedmedia-file": "Tập tin", + "timedmedia-audios": "$1 tập tin âm thanh", + "timedmedia-ogg-audios": "$1 tập tin âm thanh Ogg", + "timedmedia-flac-audios": "$1 tập tin âm thanh FLAC", + "timedmedia-wav-audios": "$1 tập tin âm thanh WAV", + "right-transcode-reset": "Đặt lại các video hỏng hoặc đã chuyển mã để chèn chúng nó lại vào hàng đợi việc.", + "right-transcode-status": "Xem [[Special:TimedMediaHandler|thông tin về hoạt động chuyển mã đang xảy ra]]", + "action-transcode-status": "xem trạng thái chuyển mã hiện tại", + "orphanedtimedtext": "Các trang TimedText mồ côi", + "orphanedtimedtext-summary": "Danh sách trang [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] không có tập tin tương ứng.", + "orphanedtimedtext-unsupported": "Trang đặc biệt này chỉ được hỗ trợ trên cơ sở dữ liệu MySQL.", + "orphanedtimedtext-notimedtext": "TimedText không được kích hoạt trên wiki này.", + "apihelp-transcodereset-param-title": "Tên tập tin phương tiện." +} diff --git a/extensions/TimedMediaHandler/i18n/vo.json b/extensions/TimedMediaHandler/i18n/vo.json new file mode 100644 index 00000000..b20db88e --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/vo.json @@ -0,0 +1,14 @@ +{ + "@metadata": { + "authors": [ + "Malafaya", + "Smeira" + ] + }, + "timedmedia-more": "Pluikos...", + "timedmedia-dismiss": "Färmükön", + "timedmedia-download": "Donükön ragivi", + "timedmedia-desc-link": "Tefü ragiv at", + "timedmedia-mp4": "MP4", + "timedmedia-source-audio-file-desc": "Ragiv: $1 rigik ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/vro.json b/extensions/TimedMediaHandler/i18n/vro.json new file mode 100644 index 00000000..2222475c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/vro.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Võrok" + ] + }, + "timedmedia-no-player-js": "Kah'os om suq võrgokaejan JavaScript keelet vai olõ-õi sul tarvilist näütämistarkvarra.
\nSaat lõigu alla laatiq vai laatiq alla tarkvara, minka lõiku võrgokaejan näüdädäq.", + "timedmedia-source-file": "Läteq $1", + "timedmedia-source-audio-file-desc": "Alguperäne $1-fail ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/wa.json b/extensions/TimedMediaHandler/i18n/wa.json new file mode 100644 index 00000000..81ee357d --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/wa.json @@ -0,0 +1,4 @@ +{ + "@metadata": [], + "timedmedia-dismiss": "Clôre" +} diff --git a/extensions/TimedMediaHandler/i18n/war.json b/extensions/TimedMediaHandler/i18n/war.json new file mode 100644 index 00000000..2b28e939 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/war.json @@ -0,0 +1,9 @@ +{ + "@metadata": { + "authors": [ + "JinJian" + ] + }, + "timedmedia-source-file": "$1 tinitikangan", + "timedmedia-source-audio-file-desc": "Orihinal $1 file ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/wuu.json b/extensions/TimedMediaHandler/i18n/wuu.json new file mode 100644 index 00000000..f61881e2 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/wuu.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Poiuyt" + ] + }, + "timedmedia-no-player-js": "对弗起,你个浏览器禁用仔JavaScript或呒不任何支持个播放器。
\n你可以下载箇只片段下载一只播放器好勒浏览器里播放片段。", + "timedmedia-source-file": "$1原始文件", + "timedmedia-source-audio-file-desc": "原始$1文件 ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/xmf.json b/extensions/TimedMediaHandler/i18n/xmf.json new file mode 100644 index 00000000..208dab64 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/xmf.json @@ -0,0 +1,10 @@ +{ + "@metadata": { + "authors": [ + "Silovan" + ] + }, + "timedmedia-no-player-js": "თქვან ბრაუზერს გოთიშილ რე JavaScript ვარდა ვა რე ოხვილური პროგრამა.
\nთქვა შეგილებუნა გეგნოჭარათ კლიპი ვარდა გეგნოჭარათ პროგრამა კლიპიშ ბრაუზერს ორჩქილეთ/ოძირაფალო.", + "timedmedia-source-file": "წყუ $1", + "timedmedia-source-audio-file-desc": "ორიგინალი $1 ფაილი ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/yi.json b/extensions/TimedMediaHandler/i18n/yi.json new file mode 100644 index 00000000..693e8639 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/yi.json @@ -0,0 +1,15 @@ +{ + "@metadata": { + "authors": [ + "פוילישער", + "පසිඳු කාවින්ද" + ] + }, + "timedmedia-no-player-js": "אנטשולדיגט, אײַער בלעטערער אדער האט JavaScript אומאקטיווירט אדער האט נישט קיין געשטיצטן שפילער.
\nאיר קענט אראפלאדן דעם פילמקליפ אדער אראפלאדן א שפילער צו שפילן דעם קליפ אין אײַער בלעטערער.", + "timedmedia-dismiss": "שליסן", + "timedmedia-download": "אראָפלאָדן טעקע", + "timedmedia-status": "סטאַטוס", + "timedmedia-source-file": "$1 מקור", + "timedmedia-source-audio-file-desc": "אריגינעלע $1 טעקע ($2)", + "timedmedia-file": "טעקע" +} diff --git a/extensions/TimedMediaHandler/i18n/yue.json b/extensions/TimedMediaHandler/i18n/yue.json new file mode 100644 index 00000000..9cb1e97c --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/yue.json @@ -0,0 +1,24 @@ +{ + "@metadata": { + "authors": [ + "Yueman", + "CRCHF" + ] + }, + "timedmedia-desc": "Ogg Theora 同 Vorbis 檔案嘅處理器,加埋 JavaScript 播放器", + "timedmedia-ogg-short-audio": "Ogg $1 聲檔,$2", + "timedmedia-ogg-short-video": "Ogg $1 畫檔,$2", + "timedmedia-ogg-short-general": "Ogg $1 媒檔,$2", + "timedmedia-ogg-long-audio": "Ogg $1 聲檔,長度$2,$3", + "timedmedia-ogg-long-video": "Ogg $1 畫檔,長度$2,$4×$5像素,$3", + "timedmedia-ogg-long-multiplexed": "Ogg 多工聲/畫檔,$1,長度$2,$4×$5像素,總共$3", + "timedmedia-ogg-long-general": "Ogg 媒檔,長度$2,$3", + "timedmedia-ogg-long-error": "無效嘅ogg檔: $1", + "timedmedia-no-player-js": "對唔住,你個瀏覽器無開到 JavaScript 或者無支援嘅播放程式。
\n你可以 下載條片 或者 下載播放程式 等你個瀏覽器都可以播。", + "timedmedia-more": "更多...", + "timedmedia-dismiss": "閂", + "timedmedia-download": "下載檔案", + "timedmedia-desc-link": "關於呢個檔案", + "timedmedia-source-file": "$1 來源檔", + "timedmedia-source-audio-file-desc": "原本 $1 檔案 ($2)" +} diff --git a/extensions/TimedMediaHandler/i18n/zh-hans.json b/extensions/TimedMediaHandler/i18n/zh-hans.json new file mode 100644 index 00000000..cd324e67 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/zh-hans.json @@ -0,0 +1,137 @@ +{ + "@metadata": { + "authors": [ + "Byfserag", + "Gaoxuewei", + "Hzy980512", + "Liangent", + "Liuxinyu970226", + "Shizhao", + "Xiaomingyan", + "Yfdyh000", + "乌拉跨氪" + ] + }, + "extensionname-timedmedia": "TimedMediaHandler", + "timedmedia-desc": "音频、视频和字幕的处理程序,支持WebM,Ogg Theora、Vorbis,srt格式", + "timedmedia-ogg-short-audio": "Ogg $1声音文件,$2", + "timedmedia-ogg-short-video": "Ogg $1视频文件,$2", + "timedmedia-ogg-short-general": "Ogg $1媒体文件,$2", + "timedmedia-ogg-long-audio": "Ogg $1声音文件,长度$2,$3", + "timedmedia-ogg-long-video": "Ogg $1视频文件,长度$2,$4 × $5像素,$3", + "timedmedia-ogg-long-multiplexed": "Ogg多工声音/视频文件,$1,长度$2,$4 × $5像素,共$3", + "timedmedia-ogg-long-general": "Ogg媒体文件,长度$2,$3", + "timedmedia-ogg-long-error": "无效的ogg文件:$1", + "timedmedia-ogg-long-no-streams": "Ogg媒体文件。警告:没有认可用于此文件的编解码器。", + "timedmedia-webm-short-video": "WebM $1视频文件,$2", + "timedmedia-webm-long-video": "WebM音频/视频文件,$1,长度$2,$4 × $5像素,共$3", + "timedmedia-flac-short-audio": "FLAC音频文件,$1", + "timedmedia-flac-long-audio": "FLAC音频文件,长度$1,码率$2", + "timedmedia-wav-short-audio": "WAV音频文件,$1", + "timedmedia-wav-long-audio": "WAV音频文件,总共长$1,码率$2", + "timedmedia-wav-pcm-required": "您只能上传脉冲编码调制(PCM)WAV。", + "timedmedia-mp4-short-video": "MP4 $1视频文件,$2", + "timedmedia-mp4-long-video": "MP4音频/视频文件,$1,长度$2,$4 × $5像素,$3 overall", + "timedmedia-no-player-js": "抱歉,您的浏览器禁用了JavaScript或没有任何可支持的播放器。
\n您可以下载这个片段下载一个播放器以在浏览器中播放片段。", + "timedmedia-more": "更多...", + "timedmedia-dismiss": "关闭", + "timedmedia-download": "下载文件", + "timedmedia-play-media": "播放媒体", + "timedmedia-desc-link": "关于该文件", + "timedmedia-oggThumb-version": "OggHandler需要oggThumb $1或者之后的版本。", + "timedmedia-oggThumb-failed": "oggThumb未能创建缩略图。", + "timedmedia-status-header": "转码状态", + "timedmedia-update-status": "更新转码状态", + "timedmedia-status": "状态", + "timedmedia-status-unknown": "未知状态", + "timedmedia-transcodebitrate": "比特率", + "timedmedia-transcodeduration": "编码时间", + "timedmedia-transcodeinfo": "格式", + "timedmedia-actions": "操作", + "timedmedia-direct-link": "下载", + "timedmedia-not-ready": "未就绪", + "timedmedia-completed-on": "已完成$1", + "timedmedia-error-on": "于$1错误", + "timedmedia-started-transcode": "于$1前开始。$2", + "timedmedia-percent-done": "大约$1%已完成", + "timedmedia-in-job-queue": "添加到工作队列在$1前", + "timedmedia-unknown-target-size": "未知目标大小,$1已编码", + "timedmedia-days": "$1天", + "timedmedia-hours": "{{PLURAL:$1|$1小时}}", + "timedmedia-minutes": "$1分", + "timedmedia-seconds": "$1秒", + "timedmedia-reset": "重置转码", + "timedmedia-reset-confirm": "重置转码将会尽可能移除所有现有文件,并将其重新加入到转码工作队列。这将需要一些时间来重新转码。

\n您确定要继续吗?", + "timedmedia-reset-error": "重置转码工作时发生错误。", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1原始文件", + "timedmedia-source-file-desc": "原始$1文件,$2 × $3($4)", + "timedmedia-source-audio-file-desc": "原始$1文件 ($2)", + "timedmedia-derivative-desc-160p.ogv": "低带宽Ogg视频(160P)", + "timedmedia-derivative-desc-240p.ogv": "Web流媒体Ogg视频(240P)", + "timedmedia-derivative-desc-360p.ogv": "Web流媒体Ogg视频(360P)", + "timedmedia-derivative-desc-480p.ogv": "Web流媒体Ogg视频(480P)", + "timedmedia-derivative-desc-720p.ogv": "高清可下载Ogg视频(720P)", + "timedmedia-derivative-desc-1080p.ogv": "全高清可下载 Ogg 视频 (1080P)", + "timedmedia-derivative-desc-160p.webm": "Web流媒体WebM(160P)", + "timedmedia-derivative-desc-360p.webm": "Web流媒体WebM(360P)", + "timedmedia-derivative-desc-480p.webm": "Web流媒体WebM(480P)", + "timedmedia-derivative-desc-720p.webm": "高清可下载WebM视频(720P)", + "timedmedia-derivative-desc-1080p.webm": "全高清可下载 WebM (1080P)", + "timedmedia-derivative-desc-2160p.webm": "全高清可下载 WebM (2160P)", + "timedmedia-derivative-360p.vp9.webm": "VP9 360P", + "timedmedia-derivative-desc-360p.vp9.webm": "Web流媒体WebM VP9(360P)", + "timedmedia-derivative-480p.vp9.webm": "VP9 480P", + "timedmedia-derivative-desc-480p.vp9.webm": "Web流媒体WebM VP9(480P)", + "timedmedia-derivative-720p.vp9.webm": "VP9 720P", + "timedmedia-derivative-desc-720p.vp9.webm": "高清流媒体WebM VP9(720P)", + "timedmedia-derivative-1080p.vp9.webm": "VP9 1080P", + "timedmedia-derivative-desc-1080p.vp9.webm": "全高清流媒体WebM VP9(1080P)", + "timedmedia-derivative-desc-2160p.vp9.webm": "全高清流媒体WebM VP9(2160P)", + "timedmedia-derivative-desc-320p.mp4": "设备友好MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "网页流媒体MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "高清MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "全高清画质 MP4 (1080P)", + "timedmedia-derivative-desc-2160p.mp4": "全高清画质 MP4 (2160P)", + "timedmedia-derivative-ogg": "Ogg Vorbis", + "timedmedia-derivative-desc-ogg": "Ogg Vorbis", + "timedmedia-derivative-desc-mp3": "MP3", + "timedmedia-subtitle-new": "创建新翻译或编辑现有翻译", + "timedmedia-subtitle-new-desc": "选择语言然后按'''{{int:Timedmedia-subtitle-new-go}}'''按钮", + "timedmedia-subtitle-new-go": "提交", + "timedmedia-subtitle-language": "$1($2)字幕", + "timedmedia-subtitle-no-video": "当前字幕页面没有关联的视频", + "timedmedia-subtitle-no-subtitles": "当前没有这个视频的$1字幕,您可以[{{fullurl:{{FULLPAGENAME}}|action=edit}} 编辑这个页面]以添加", + "timedmedia-subtitle-remote": "本文件的字幕文件位于$1", + "timedmedia-subtitle-remote-link": "你可以在$2查看该文件的[$1 说明页面]", + "timedmediahandler": "定时媒体处理程序", + "timedmedia-videos": "$1个视频", + "timedmedia-ogg-videos": "$1个Ogg视频", + "timedmedia-webm-videos": "$1个WebM视频", + "timedmedia-derivative-state-transcodes": "$1次转码", + "timedmedia-derivative-state-active": "$1个正在运行的转码任务", + "timedmedia-derivative-state-queued": "$1个队列中的转码任务", + "timedmedia-derivative-state-failed": "$1次转码失败", + "timedmedia-file": "文件", + "timedmedia-audios": "$1个音频文件", + "timedmedia-ogg-audios": "$1个Ogg音频文件", + "timedmedia-flac-audios": "$1个FLAC音频文件", + "timedmedia-wav-audios": "$1个WAV音频文件", + "right-transcode-reset": "重置已失败或已转码的视频将其再次加入到作业队列中。", + "right-transcode-status": "查看[[Special:TimedMediaHandler|当前转码活动的信息]]", + "action-transcode-status": "查看当前的转码状态", + "orphanedtimedtext": "孤立的TimedText页面", + "orphanedtimedtext-summary": "没有对应文件的[[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]]页面的列表。", + "orphanedtimedtext-unsupported": "此特殊页面只支持MySQL数据库。", + "orphanedtimedtext-notimedtext": "TimedText在此wiki未启用。", + "apihelp-query+transcodestatus-description": "获取指定文件页面的转码状态。", + "apihelp-query+transcodestatus-example-1": "获得[[:File:Clip.webm]]的译码状态", + "apihelp-query+videoinfo-description": "扩展图像信息以包含视频源(图像来源)信息", + "apihelp-query+videoinfo-param-prop": "要获取的视频信息:\n;timestamp:为上传版本添加时间戳。\n;user:添加上传视频版本的用户。\n;userid:添加上传视频版本的用户ID。\n;comment:对版本的注释。\n;parsedcomment:解析对版本的注释。\n;canonicaltitle:添加视频文件的规范标题。\n;url:提供至视频的URL及其说明页面。\n;size:添加视频大小(字节)、高度和宽度。如有需要会添加页面计数和持续时间。\n;dimensions:大小别名。\n;sha1:为视频添加SHA-1哈希值。\n;mime:添加视频的MIME类型。\n;thumbmime:添加视频缩略图的MIME类型(需要url和参数$1urlwidth)。\n;mediatype:添加视频媒体类型。\n;metadata:列举视频版本的EXIF元数据。\n;commonmetadata:列举特定文件格式的视频版本的元数据。\n;extmetadata:列举格式化元数据\n;archivename:为非最新版本添加存档版本的文件名。\n;bitdepth:添加版本的位深度。\n;uploadwarning:被[[Special:Upload]]页面使用以获取有关现有文件的信息。不适用于MediaWiki核心以外代码。\n;derivatives:添加不同格式的阵列与音频或视频文件的可用高质量版本。", + "apihelp-query+videoinfo-example-1": "检索有关[[:File:Folgers.ogv]]的信息", + "apihelp-transcodereset-description": "具有“transcode-reset”权限的用户可以重置和重新运行转码任务。", + "apihelp-transcodereset-param-title": "媒体文件标题。", + "apihelp-transcodereset-param-transcodekey": "您希望重置的转码关键字。从[[Special:ApiHelp/query+transcodestatus|action=query&prop=transcodestatus]]获取。", + "apihelp-transcodereset-example-1": "重置[[:File:Clip.webm]]的所有译码", + "apihelp-transcodereset-example-2": "重置[[:File:Clip.webm]]的“360_560kbs.webm”转码关键字" +} diff --git a/extensions/TimedMediaHandler/i18n/zh-hant.json b/extensions/TimedMediaHandler/i18n/zh-hant.json new file mode 100644 index 00000000..c80a3438 --- /dev/null +++ b/extensions/TimedMediaHandler/i18n/zh-hant.json @@ -0,0 +1,119 @@ +{ + "@metadata": { + "authors": [ + "Gaoxuewei", + "Justincheng12345", + "Liuxinyu970226", + "Mark85296341", + "Simon Shek", + "LNDDYL", + "Cwlin0416" + ] + }, + "timedmedia-desc": "音訊、影片與字幕處理程式,支援的格式包含 WebM,Ogg Theora、Vorbis,srt", + "timedmedia-ogg-short-audio": "Ogg $1 聲音檔案,$2", + "timedmedia-ogg-short-video": "Ogg $1 影片檔,$2", + "timedmedia-ogg-short-general": "Ogg $1 媒體檔案,$2", + "timedmedia-ogg-long-audio": "Ogg $1 聲音檔案,長度 $2,$3", + "timedmedia-ogg-long-video": "Ogg $1 影片檔案,長度 $2,$4 × $5 像素,$3", + "timedmedia-ogg-long-multiplexed": "Ogg 多工音訊/影片檔案,$1,長度 $2,$4 × $5 畫素,共 $3", + "timedmedia-ogg-long-general": "Ogg 媒體檔案,長度 $2,$3", + "timedmedia-ogg-long-error": "無效的 Ogg 檔案:$1", + "timedmedia-ogg-long-no-streams": "Ogg 媒體檔案。 警告:無法辨識此檔案中使用的編碼。", + "timedmedia-webm-short-video": "WebM $1 影片檔,$2", + "timedmedia-webm-long-video": "WebM 音訊/影片檔案,$1,長度 $2,$4 × $5 畫素,共 $3", + "timedmedia-flac-short-audio": "FLAC 音訊檔案,$1", + "timedmedia-flac-long-audio": "FLAC 音頻檔案,長度 $1,共 $2", + "timedmedia-wav-short-audio": "WAV 音訊檔案,$1", + "timedmedia-wav-long-audio": "WAV 音訊檔案,長度 $1,共 $2", + "timedmedia-wav-pcm-required": "您僅可上傳 PCM (Pulse Code Modulation) WAV。", + "timedmedia-mp4-short-video": "MP4 $1 影片檔,$2", + "timedmedia-mp4-long-video": "MP4 音訊/影片檔案,$1,長度 $2,$4 × $5 畫素,共 $3", + "timedmedia-no-player-js": "抱歉,您的瀏覽器未開啟 JavaScript 或沒有支援的播放程式。
\n您可 下載該片段下載播放器 以在您的瀏覽器中播放。", + "timedmedia-more": "更多...", + "timedmedia-dismiss": "關閉", + "timedmedia-download": "下載檔案", + "timedmedia-play-media": "播放媒體", + "timedmedia-desc-link": "關於這個檔案", + "timedmedia-oggThumb-version": "OggHandler 需要使用 oggThumb 版本 $1 或更新的版本。", + "timedmedia-oggThumb-failed": "oggThumb 建立縮圖失敗。", + "timedmedia-status-header": "轉碼狀態", + "timedmedia-update-status": "更新轉碼狀態", + "timedmedia-status": "狀態", + "timedmedia-status-unknown": "不明狀態", + "timedmedia-transcodebitrate": "位元率", + "timedmedia-transcodeduration": "編碼時間", + "timedmedia-transcodeinfo": "格式", + "timedmedia-actions": "動作", + "timedmedia-direct-link": "下載", + "timedmedia-not-ready": "尚未就緒", + "timedmedia-completed-on": "已完成 $1", + "timedmedia-error-on": "於 $1 發生錯誤。", + "timedmedia-started-transcode": "已於 $1 前開始。$2", + "timedmedia-percent-done": "大約已完成 $1%", + "timedmedia-in-job-queue": "已於 $1 前新增至工作序列", + "timedmedia-unknown-target-size": "不明目標大小,已編碼 $1", + "timedmedia-days": "$1 天", + "timedmedia-hours": "$1 小時", + "timedmedia-minutes": "$1 分", + "timedmedia-seconds": "$1 秒", + "timedmedia-reset": "重設轉碼", + "timedmedia-reset-confirm": "重設轉碼會移除所有已存在的檔案,並重新將檔案加入轉碼工作序列。這需要花費一些時間來重新轉碼。

\n您確定要繼續嗎?", + "timedmedia-reset-error": "重設轉碼工作時發生錯誤。", + "timedmedia-mp4": "MP4", + "timedmedia-source-file": "$1 來源檔案", + "timedmedia-source-file-desc": "$1 原始檔案,$2 × $3 ($4)", + "timedmedia-source-audio-file-desc": "原始 $1 檔案 ($2)", + "timedmedia-derivative-desc-160p.ogv": "低頻寬 Ogg 影片 (160P)", + "timedmedia-derivative-desc-240p.ogv": "網站可串流 Ogg 影片 (240P)", + "timedmedia-derivative-desc-360p.ogv": "網站可串流 Ogg 影片 (360P)", + "timedmedia-derivative-desc-480p.ogv": "網站可串流 Ogg 影片 (480P)", + "timedmedia-derivative-desc-720p.ogv": "高畫質可下載 Ogg 影片 (720P)", + "timedmedia-derivative-desc-1080p.ogv": "Full HD 可下載 Ogg 影像 (1080P)", + "timedmedia-derivative-desc-160p.webm": "網站可串流 WebM (160P)", + "timedmedia-derivative-desc-360p.webm": "網站可串流 WebM (360P)", + "timedmedia-derivative-desc-480p.webm": "網站可串流 WebM (480P)", + "timedmedia-derivative-desc-720p.webm": "高畫質可下載 WebM (720P)", + "timedmedia-derivative-desc-1080p.webm": "Full HD 可下載 WebM (1080P)", + "timedmedia-derivative-desc-2160p.webm": "Full HD 可下載 WebM (2160P)", + "timedmedia-derivative-desc-360p.vp9.webm": "網站可串流 WebM VP9 (360P)", + "timedmedia-derivative-desc-480p.vp9.webm": "網站可串流 WebM VP9 (480P)", + "timedmedia-derivative-desc-720p.vp9.webm": "HD 網站可串流 WebM VP9 (720P)", + "timedmedia-derivative-desc-1080p.vp9.webm": "Full HD 網站可串流 WebM VP9 (1080P)", + "timedmedia-derivative-desc-2160p.vp9.webm": "Full 4K 網站可串流 WebM VP9 (2160P)", + "timedmedia-derivative-desc-320p.mp4": "行動裝置易用 MP4 (320P)", + "timedmedia-derivative-desc-480p.mp4": "網站可串流 MP4 (480P)", + "timedmedia-derivative-desc-720p.mp4": "高清品質 MP4 (720P)", + "timedmedia-derivative-desc-1080p.mp4": "Full HD 品質 MP4 (1080P)", + "timedmedia-derivative-desc-2160p.mp4": "Full 4K 品質 MP4 (2160P)", + "timedmedia-subtitle-new": "建立新翻譯或編輯現有翻譯", + "timedmedia-subtitle-new-desc": "請選擇語言後點選 '''{{int:Timedmedia-subtitle-new-go}}''' 按鈕", + "timedmedia-subtitle-new-go": "執行", + "timedmedia-subtitle-language": "$1 ($2) 字幕", + "timedmedia-subtitle-no-video": "目前沒有與字幕頁面相關的影片。", + "timedmedia-subtitle-no-subtitles": "此影片目前沒有 $1 的字幕,您可 [{{fullurl:{{FULLPAGENAME}}|action=edit}} 編輯此頁面] 來增加字幕。", + "timedmedia-subtitle-remote": "此檔案的字幕位於 $1", + "timedmedia-subtitle-remote-link": "您可於 $2 檢視此檔案的 [$1 描述頁面]", + "timedmediahandler": "TimedMediaHandler", + "timedmedia-videos": "$1 個影片", + "timedmedia-ogg-videos": "$1 個 Ogg 影片", + "timedmedia-webm-videos": "$1 個 WebM 影片", + "timedmedia-derivative-state-transcodes": "$1 個轉碼工作", + "timedmedia-derivative-state-active": "$1 個正在執行的轉碼工作", + "timedmedia-derivative-state-queued": "$1 個在序列中的轉碼工作", + "timedmedia-derivative-state-failed": "$1 次轉碼失敗", + "timedmedia-file": "檔案", + "timedmedia-audios": "$1 個音訊檔案", + "timedmedia-ogg-audios": "$1 個 Ogg 音訊檔案", + "timedmedia-flac-audios": "$1 個 FLAC 音訊檔案", + "timedmedia-wav-audios": "$1 個 WAV 音訊檔案", + "right-transcode-reset": "重設已失敗或已輚碼的影片,將其重新排入序列中。", + "right-transcode-status": "檢視 [[Special:TimedMediaHandler|目前轉碼活動的資訊]]", + "action-transcode-status": "檢視目前轉碼狀態", + "orphanedtimedtext": "孤立的 TimedText 頁面", + "orphanedtimedtext-summary": "未關聯任何檔案的 [[{{#special:AllPages/TimedText:}}|{{ns:TimedText}}]] 頁面清單。", + "orphanedtimedtext-unsupported": "此特殊頁面僅支援使用 MySQL 資料庫。", + "orphanedtimedtext-notimedtext": "此 Wiki 未開啟 TimedText 功能。", + "apihelp-query+transcodestatus-description": "取得指定檔案頁面的轉碼狀態。", + "apihelp-query+transcodestatus-example-1": "取得 [[:File:Clip.webm]] 的轉碼狀態" +} diff --git a/extensions/TimedMediaHandler/libs/getid3/getid3.lib.php b/extensions/TimedMediaHandler/libs/getid3/getid3.lib.php new file mode 100644 index 00000000..16d5318a --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/getid3.lib.php @@ -0,0 +1,1346 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// getid3.lib.php - part of getID3() // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_lib +{ + + public static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') { + $returnstring = ''; + for ($i = 0; $i < strlen($string); $i++) { + if ($hex) { + $returnstring .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT); + } else { + $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string{$i}) ? $string{$i} : ''); + } + if ($spaces) { + $returnstring .= ' '; + } + } + if (!empty($htmlencoding)) { + if ($htmlencoding === true) { + $htmlencoding = 'UTF-8'; // prior to getID3 v1.9.0 the function's 4th parameter was boolean + } + $returnstring = htmlentities($returnstring, ENT_QUOTES, $htmlencoding); + } + return $returnstring; + } + + public static function trunc($floatnumber) { + // truncates a floating-point number at the decimal point + // returns int (if possible, otherwise float) + if ($floatnumber >= 1) { + $truncatednumber = floor($floatnumber); + } elseif ($floatnumber <= -1) { + $truncatednumber = ceil($floatnumber); + } else { + $truncatednumber = 0; + } + if (self::intValueSupported($truncatednumber)) { + $truncatednumber = (int) $truncatednumber; + } + return $truncatednumber; + } + + + public static function safe_inc(&$variable, $increment=1) { + if (isset($variable)) { + $variable += $increment; + } else { + $variable = $increment; + } + return true; + } + + public static function CastAsInt($floatnum) { + // convert to float if not already + $floatnum = (float) $floatnum; + + // convert a float to type int, only if possible + if (self::trunc($floatnum) == $floatnum) { + // it's not floating point + if (self::intValueSupported($floatnum)) { + // it's within int range + $floatnum = (int) $floatnum; + } + } + return $floatnum; + } + + public static function intValueSupported($num) { + // check if integers are 64-bit + static $hasINT64 = null; + if ($hasINT64 === null) { // 10x faster than is_null() + $hasINT64 = is_int(pow(2, 31)); // 32-bit int are limited to (2^31)-1 + if (!$hasINT64 && !defined('PHP_INT_MIN')) { + define('PHP_INT_MIN', ~PHP_INT_MAX); + } + } + // if integers are 64-bit - no other check required + if ($hasINT64 || (($num <= PHP_INT_MAX) && ($num >= PHP_INT_MIN))) { + return true; + } + return false; + } + + public static function DecimalizeFraction($fraction) { + list($numerator, $denominator) = explode('/', $fraction); + return $numerator / ($denominator ? $denominator : 1); + } + + + public static function DecimalBinary2Float($binarynumerator) { + $numerator = self::Bin2Dec($binarynumerator); + $denominator = self::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator))); + return ($numerator / $denominator); + } + + + public static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html + if (strpos($binarypointnumber, '.') === false) { + $binarypointnumber = '0.'.$binarypointnumber; + } elseif ($binarypointnumber{0} == '.') { + $binarypointnumber = '0'.$binarypointnumber; + } + $exponent = 0; + while (($binarypointnumber{0} != '1') || (substr($binarypointnumber, 1, 1) != '.')) { + if (substr($binarypointnumber, 1, 1) == '.') { + $exponent--; + $binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3); + } else { + $pointpos = strpos($binarypointnumber, '.'); + $exponent += ($pointpos - 1); + $binarypointnumber = str_replace('.', '', $binarypointnumber); + $binarypointnumber = $binarypointnumber{0}.'.'.substr($binarypointnumber, 1); + } + } + $binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT); + return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent); + } + + + public static function Float2BinaryDecimal($floatvalue) { + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html + $maxbits = 128; // to how many bits of precision should the calculations be taken? + $intpart = self::trunc($floatvalue); + $floatpart = abs($floatvalue - $intpart); + $pointbitstring = ''; + while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) { + $floatpart *= 2; + $pointbitstring .= (string) self::trunc($floatpart); + $floatpart -= self::trunc($floatpart); + } + $binarypointnumber = decbin($intpart).'.'.$pointbitstring; + return $binarypointnumber; + } + + + public static function Float2String($floatvalue, $bits) { + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html + switch ($bits) { + case 32: + $exponentbits = 8; + $fractionbits = 23; + break; + + case 64: + $exponentbits = 11; + $fractionbits = 52; + break; + + default: + return false; + break; + } + if ($floatvalue >= 0) { + $signbit = '0'; + } else { + $signbit = '1'; + } + $normalizedbinary = self::NormalizeBinaryPoint(self::Float2BinaryDecimal($floatvalue), $fractionbits); + $biasedexponent = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent + $exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT); + $fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT); + + return self::BigEndian2String(self::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false); + } + + + public static function LittleEndian2Float($byteword) { + return self::BigEndian2Float(strrev($byteword)); + } + + + public static function BigEndian2Float($byteword) { + // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic + // http://www.psc.edu/general/software/packages/ieee/ieee.html + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html + + $bitword = self::BigEndian2Bin($byteword); + if (!$bitword) { + return 0; + } + $signbit = $bitword{0}; + + switch (strlen($byteword) * 8) { + case 32: + $exponentbits = 8; + $fractionbits = 23; + break; + + case 64: + $exponentbits = 11; + $fractionbits = 52; + break; + + case 80: + // 80-bit Apple SANE format + // http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ + $exponentstring = substr($bitword, 1, 15); + $isnormalized = intval($bitword{16}); + $fractionstring = substr($bitword, 17, 63); + $exponent = pow(2, self::Bin2Dec($exponentstring) - 16383); + $fraction = $isnormalized + self::DecimalBinary2Float($fractionstring); + $floatvalue = $exponent * $fraction; + if ($signbit == '1') { + $floatvalue *= -1; + } + return $floatvalue; + break; + + default: + return false; + break; + } + $exponentstring = substr($bitword, 1, $exponentbits); + $fractionstring = substr($bitword, $exponentbits + 1, $fractionbits); + $exponent = self::Bin2Dec($exponentstring); + $fraction = self::Bin2Dec($fractionstring); + + if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) { + // Not a Number + $floatvalue = false; + } elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) { + if ($signbit == '1') { + $floatvalue = '-infinity'; + } else { + $floatvalue = '+infinity'; + } + } elseif (($exponent == 0) && ($fraction == 0)) { + if ($signbit == '1') { + $floatvalue = -0; + } else { + $floatvalue = 0; + } + $floatvalue = ($signbit ? 0 : -0); + } elseif (($exponent == 0) && ($fraction != 0)) { + // These are 'unnormalized' values + $floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * self::DecimalBinary2Float($fractionstring); + if ($signbit == '1') { + $floatvalue *= -1; + } + } elseif ($exponent != 0) { + $floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + self::DecimalBinary2Float($fractionstring)); + if ($signbit == '1') { + $floatvalue *= -1; + } + } + return (float) $floatvalue; + } + + + public static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { + $intvalue = 0; + $bytewordlen = strlen($byteword); + if ($bytewordlen == 0) { + return false; + } + for ($i = 0; $i < $bytewordlen; $i++) { + if ($synchsafe) { // disregard MSB, effectively 7-bit bytes + //$intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); // faster, but runs into problems past 2^31 on 32-bit systems + $intvalue += (ord($byteword{$i}) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7); + } else { + $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i)); + } + } + if ($signed && !$synchsafe) { + // synchsafe ints are not allowed to be signed + if ($bytewordlen <= PHP_INT_SIZE) { + $signMaskBit = 0x80 << (8 * ($bytewordlen - 1)); + if ($intvalue & $signMaskBit) { + $intvalue = 0 - ($intvalue & ($signMaskBit - 1)); + } + } else { + throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits ('.strlen($byteword).') in self::BigEndian2Int()'); + break; + } + } + return self::CastAsInt($intvalue); + } + + + public static function LittleEndian2Int($byteword, $signed=false) { + return self::BigEndian2Int(strrev($byteword), false, $signed); + } + + + public static function BigEndian2Bin($byteword) { + $binvalue = ''; + $bytewordlen = strlen($byteword); + for ($i = 0; $i < $bytewordlen; $i++) { + $binvalue .= str_pad(decbin(ord($byteword{$i})), 8, '0', STR_PAD_LEFT); + } + return $binvalue; + } + + + public static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { + if ($number < 0) { + throw new Exception('ERROR: self::BigEndian2String() does not support negative numbers'); + } + $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF); + $intstring = ''; + if ($signed) { + if ($minbytes > PHP_INT_SIZE) { + throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits in self::BigEndian2String()'); + } + $number = $number & (0x80 << (8 * ($minbytes - 1))); + } + while ($number != 0) { + $quotient = ($number / ($maskbyte + 1)); + $intstring = chr(ceil(($quotient - floor($quotient)) * $maskbyte)).$intstring; + $number = floor($quotient); + } + return str_pad($intstring, $minbytes, "\x00", STR_PAD_LEFT); + } + + + public static function Dec2Bin($number) { + while ($number >= 256) { + $bytes[] = (($number / 256) - (floor($number / 256))) * 256; + $number = floor($number / 256); + } + $bytes[] = $number; + $binstring = ''; + for ($i = 0; $i < count($bytes); $i++) { + $binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring; + } + return $binstring; + } + + + public static function Bin2Dec($binstring, $signed=false) { + $signmult = 1; + if ($signed) { + if ($binstring{0} == '1') { + $signmult = -1; + } + $binstring = substr($binstring, 1); + } + $decvalue = 0; + for ($i = 0; $i < strlen($binstring); $i++) { + $decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i); + } + return self::CastAsInt($decvalue * $signmult); + } + + + public static function Bin2String($binstring) { + // return 'hi' for input of '0110100001101001' + $string = ''; + $binstringreversed = strrev($binstring); + for ($i = 0; $i < strlen($binstringreversed); $i += 8) { + $string = chr(self::Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string; + } + return $string; + } + + + public static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { + $intstring = ''; + while ($number > 0) { + if ($synchsafe) { + $intstring = $intstring.chr($number & 127); + $number >>= 7; + } else { + $intstring = $intstring.chr($number & 255); + $number >>= 8; + } + } + return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT); + } + + + public static function array_merge_clobber($array1, $array2) { + // written by kchireability*com + // taken from http://www.php.net/manual/en/function.array-merge-recursive.php + if (!is_array($array1) || !is_array($array2)) { + return false; + } + $newarray = $array1; + foreach ($array2 as $key => $val) { + if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { + $newarray[$key] = self::array_merge_clobber($newarray[$key], $val); + } else { + $newarray[$key] = $val; + } + } + return $newarray; + } + + + public static function array_merge_noclobber($array1, $array2) { + if (!is_array($array1) || !is_array($array2)) { + return false; + } + $newarray = $array1; + foreach ($array2 as $key => $val) { + if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { + $newarray[$key] = self::array_merge_noclobber($newarray[$key], $val); + } elseif (!isset($newarray[$key])) { + $newarray[$key] = $val; + } + } + return $newarray; + } + + + public static function ksort_recursive(&$theArray) { + ksort($theArray); + foreach ($theArray as $key => $value) { + if (is_array($value)) { + self::ksort_recursive($theArray[$key]); + } + } + return true; + } + + public static function fileextension($filename, $numextensions=1) { + if (strstr($filename, '.')) { + $reversedfilename = strrev($filename); + $offset = 0; + for ($i = 0; $i < $numextensions; $i++) { + $offset = strpos($reversedfilename, '.', $offset + 1); + if ($offset === false) { + return ''; + } + } + return strrev(substr($reversedfilename, 0, $offset)); + } + return ''; + } + + + public static function PlaytimeString($seconds) { + $sign = (($seconds < 0) ? '-' : ''); + $seconds = round(abs($seconds)); + $H = (int) floor( $seconds / 3600); + $M = (int) floor(($seconds - (3600 * $H) ) / 60); + $S = (int) round( $seconds - (3600 * $H) - (60 * $M) ); + return $sign.($H ? $H.':' : '').($H ? str_pad($M, 2, '0', STR_PAD_LEFT) : intval($M)).':'.str_pad($S, 2, 0, STR_PAD_LEFT); + } + + + public static function DateMac2Unix($macdate) { + // Macintosh timestamp: seconds since 00:00h January 1, 1904 + // UNIX timestamp: seconds since 00:00h January 1, 1970 + return self::CastAsInt($macdate - 2082844800); + } + + + public static function FixedPoint8_8($rawdata) { + return self::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (self::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8)); + } + + + public static function FixedPoint16_16($rawdata) { + return self::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (self::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16)); + } + + + public static function FixedPoint2_30($rawdata) { + $binarystring = self::BigEndian2Bin($rawdata); + return self::Bin2Dec(substr($binarystring, 0, 2)) + (float) (self::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30)); + } + + + public static function CreateDeepArray($ArrayPath, $Separator, $Value) { + // assigns $Value to a nested array path: + // $foo = self::CreateDeepArray('/path/to/my', '/', 'file.txt') + // is the same as: + // $foo = array('path'=>array('to'=>'array('my'=>array('file.txt')))); + // or + // $foo['path']['to']['my'] = 'file.txt'; + $ArrayPath = ltrim($ArrayPath, $Separator); + if (($pos = strpos($ArrayPath, $Separator)) !== false) { + $ReturnedArray[substr($ArrayPath, 0, $pos)] = self::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value); + } else { + $ReturnedArray[$ArrayPath] = $Value; + } + return $ReturnedArray; + } + + public static function array_max($arraydata, $returnkey=false) { + $maxvalue = false; + $maxkey = false; + foreach ($arraydata as $key => $value) { + if (!is_array($value)) { + if ($value > $maxvalue) { + $maxvalue = $value; + $maxkey = $key; + } + } + } + return ($returnkey ? $maxkey : $maxvalue); + } + + public static function array_min($arraydata, $returnkey=false) { + $minvalue = false; + $minkey = false; + foreach ($arraydata as $key => $value) { + if (!is_array($value)) { + if ($value > $minvalue) { + $minvalue = $value; + $minkey = $key; + } + } + } + return ($returnkey ? $minkey : $minvalue); + } + + public static function XML2array($XMLstring) { + if (function_exists('simplexml_load_string') + && function_exists('get_object_vars') + && function_exists('libxml_disable_entity_loader') + ) { + $loader = libxml_disable_entity_loader(true); + $XMLobject = simplexml_load_string($XMLstring); + $return = self::SimpleXMLelement2array($XMLobject); + libxml_disable_entity_loader($loader); + return $return; + } + return false; + } + + public static function SimpleXMLelement2array($XMLobject) { + if (!is_object($XMLobject) && !is_array($XMLobject)) { + return $XMLobject; + } + $XMLarray = (is_object($XMLobject) ? get_object_vars($XMLobject) : $XMLobject); + foreach ($XMLarray as $key => $value) { + $XMLarray[$key] = self::SimpleXMLelement2array($value); + } + return $XMLarray; + } + + + // Allan Hansen + // self::md5_data() - returns md5sum for a file from startuing position to absolute end position + public static function hash_data($file, $offset, $end, $algorithm) { + static $tempdir = ''; + if (!self::intValueSupported($end)) { + return false; + } + switch ($algorithm) { + case 'md5': + $hash_function = 'md5_file'; + $unix_call = 'md5sum'; + $windows_call = 'md5sum.exe'; + $hash_length = 32; + break; + + case 'sha1': + $hash_function = 'sha1_file'; + $unix_call = 'sha1sum'; + $windows_call = 'sha1sum.exe'; + $hash_length = 40; + break; + + default: + throw new Exception('Invalid algorithm ('.$algorithm.') in self::hash_data()'); + break; + } + $size = $end - $offset; + while (true) { + if (GETID3_OS_ISWINDOWS) { + + // It seems that sha1sum.exe for Windows only works on physical files, does not accept piped data + // Fall back to create-temp-file method: + if ($algorithm == 'sha1') { + break; + } + + $RequiredFiles = array('cygwin1.dll', 'head.exe', 'tail.exe', $windows_call); + foreach ($RequiredFiles as $required_file) { + if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { + // helper apps not available - fall back to old method + break 2; + } + } + $commandline = GETID3_HELPERAPPSDIR.'head.exe -c '.$end.' '.escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $file)).' | '; + $commandline .= GETID3_HELPERAPPSDIR.'tail.exe -c '.$size.' | '; + $commandline .= GETID3_HELPERAPPSDIR.$windows_call; + + } else { + + $commandline = 'head -c'.$end.' '.escapeshellarg($file).' | '; + $commandline .= 'tail -c'.$size.' | '; + $commandline .= $unix_call; + + } + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + //throw new Exception('PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm'); + break; + } + return substr(`$commandline`, 0, $hash_length); + } + + if (empty($tempdir)) { + // yes this is ugly, feel free to suggest a better way + require_once(dirname(__FILE__).'/getid3.php'); + $getid3_temp = new getID3(); + $tempdir = $getid3_temp->tempdir; + unset($getid3_temp); + } + // try to create a temporary file in the system temp directory - invalid dirname should force to system temp dir + if (($data_filename = tempnam($tempdir, 'gI3')) === false) { + // can't find anywhere to create a temp file, just fail + return false; + } + + // Init + $result = false; + + // copy parts of file + try { + self::CopyFileParts($file, $data_filename, $offset, $end - $offset); + $result = $hash_function($data_filename); + } catch (Exception $e) { + throw new Exception('self::CopyFileParts() failed in getid_lib::hash_data(): '.$e->getMessage()); + } + unlink($data_filename); + return $result; + } + + public static function CopyFileParts($filename_source, $filename_dest, $offset, $length) { + if (!self::intValueSupported($offset + $length)) { + throw new Exception('cannot copy file portion, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit'); + } + if (is_readable($filename_source) && is_file($filename_source) && ($fp_src = fopen($filename_source, 'rb'))) { + if (($fp_dest = fopen($filename_dest, 'wb'))) { + if (fseek($fp_src, $offset, SEEK_SET) == 0) { + $byteslefttowrite = $length; + while (($byteslefttowrite > 0) && ($buffer = fread($fp_src, min($byteslefttowrite, getID3::FREAD_BUFFER_SIZE)))) { + $byteswritten = fwrite($fp_dest, $buffer, $byteslefttowrite); + $byteslefttowrite -= $byteswritten; + } + return true; + } else { + throw new Exception('failed to seek to offset '.$offset.' in '.$filename_source); + } + fclose($fp_dest); + } else { + throw new Exception('failed to create file for writing '.$filename_dest); + } + fclose($fp_src); + } else { + throw new Exception('failed to open file for reading '.$filename_source); + } + return false; + } + + public static function iconv_fallback_int_utf8($charval) { + if ($charval < 128) { + // 0bbbbbbb + $newcharstring = chr($charval); + } elseif ($charval < 2048) { + // 110bbbbb 10bbbbbb + $newcharstring = chr(($charval >> 6) | 0xC0); + $newcharstring .= chr(($charval & 0x3F) | 0x80); + } elseif ($charval < 65536) { + // 1110bbbb 10bbbbbb 10bbbbbb + $newcharstring = chr(($charval >> 12) | 0xE0); + $newcharstring .= chr(($charval >> 6) | 0xC0); + $newcharstring .= chr(($charval & 0x3F) | 0x80); + } else { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $newcharstring = chr(($charval >> 18) | 0xF0); + $newcharstring .= chr(($charval >> 12) | 0xC0); + $newcharstring .= chr(($charval >> 6) | 0xC0); + $newcharstring .= chr(($charval & 0x3F) | 0x80); + } + return $newcharstring; + } + + // ISO-8859-1 => UTF-8 + public static function iconv_fallback_iso88591_utf8($string, $bom=false) { + if (function_exists('utf8_encode')) { + return utf8_encode($string); + } + // utf8_encode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support) + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xEF\xBB\xBF"; + } + for ($i = 0; $i < strlen($string); $i++) { + $charval = ord($string{$i}); + $newcharstring .= self::iconv_fallback_int_utf8($charval); + } + return $newcharstring; + } + + // ISO-8859-1 => UTF-16BE + public static function iconv_fallback_iso88591_utf16be($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFE\xFF"; + } + for ($i = 0; $i < strlen($string); $i++) { + $newcharstring .= "\x00".$string{$i}; + } + return $newcharstring; + } + + // ISO-8859-1 => UTF-16LE + public static function iconv_fallback_iso88591_utf16le($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFF\xFE"; + } + for ($i = 0; $i < strlen($string); $i++) { + $newcharstring .= $string{$i}."\x00"; + } + return $newcharstring; + } + + // ISO-8859-1 => UTF-16LE (BOM) + public static function iconv_fallback_iso88591_utf16($string) { + return self::iconv_fallback_iso88591_utf16le($string, true); + } + + // UTF-8 => ISO-8859-1 + public static function iconv_fallback_utf8_iso88591($string) { + if (function_exists('utf8_decode')) { + return utf8_decode($string); + } + // utf8_decode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support) + $newcharstring = ''; + $offset = 0; + $stringlength = strlen($string); + while ($offset < $stringlength) { + if ((ord($string{$offset}) | 0x07) == 0xF7) { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); + $offset += 4; + } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { + // 1110bbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); + $offset += 3; + } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { + // 110bbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & + (ord($string{($offset + 1)}) & 0x3F); + $offset += 2; + } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { + // 0bbbbbbb + $charval = ord($string{$offset}); + $offset += 1; + } else { + // error? throw some kind of warning here? + $charval = false; + $offset += 1; + } + if ($charval !== false) { + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); + } + } + return $newcharstring; + } + + // UTF-8 => UTF-16BE + public static function iconv_fallback_utf8_utf16be($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFE\xFF"; + } + $offset = 0; + $stringlength = strlen($string); + while ($offset < $stringlength) { + if ((ord($string{$offset}) | 0x07) == 0xF7) { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); + $offset += 4; + } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { + // 1110bbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); + $offset += 3; + } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { + // 110bbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & + (ord($string{($offset + 1)}) & 0x3F); + $offset += 2; + } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { + // 0bbbbbbb + $charval = ord($string{$offset}); + $offset += 1; + } else { + // error? throw some kind of warning here? + $charval = false; + $offset += 1; + } + if ($charval !== false) { + $newcharstring .= (($charval < 65536) ? self::BigEndian2String($charval, 2) : "\x00".'?'); + } + } + return $newcharstring; + } + + // UTF-8 => UTF-16LE + public static function iconv_fallback_utf8_utf16le($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFF\xFE"; + } + $offset = 0; + $stringlength = strlen($string); + while ($offset < $stringlength) { + if ((ord($string{$offset}) | 0x07) == 0xF7) { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); + $offset += 4; + } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) { + // 1110bbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); + $offset += 3; + } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) { + // 110bbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & + (ord($string{($offset + 1)}) & 0x3F); + $offset += 2; + } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) { + // 0bbbbbbb + $charval = ord($string{$offset}); + $offset += 1; + } else { + // error? maybe throw some warning here? + $charval = false; + $offset += 1; + } + if ($charval !== false) { + $newcharstring .= (($charval < 65536) ? self::LittleEndian2String($charval, 2) : '?'."\x00"); + } + } + return $newcharstring; + } + + // UTF-8 => UTF-16LE (BOM) + public static function iconv_fallback_utf8_utf16($string) { + return self::iconv_fallback_utf8_utf16le($string, true); + } + + // UTF-16BE => UTF-8 + public static function iconv_fallback_utf16be_utf8($string) { + if (substr($string, 0, 2) == "\xFE\xFF") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::BigEndian2Int(substr($string, $i, 2)); + $newcharstring .= self::iconv_fallback_int_utf8($charval); + } + return $newcharstring; + } + + // UTF-16LE => UTF-8 + public static function iconv_fallback_utf16le_utf8($string) { + if (substr($string, 0, 2) == "\xFF\xFE") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::LittleEndian2Int(substr($string, $i, 2)); + $newcharstring .= self::iconv_fallback_int_utf8($charval); + } + return $newcharstring; + } + + // UTF-16BE => ISO-8859-1 + public static function iconv_fallback_utf16be_iso88591($string) { + if (substr($string, 0, 2) == "\xFE\xFF") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::BigEndian2Int(substr($string, $i, 2)); + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); + } + return $newcharstring; + } + + // UTF-16LE => ISO-8859-1 + public static function iconv_fallback_utf16le_iso88591($string) { + if (substr($string, 0, 2) == "\xFF\xFE") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::LittleEndian2Int(substr($string, $i, 2)); + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); + } + return $newcharstring; + } + + // UTF-16 (BOM) => ISO-8859-1 + public static function iconv_fallback_utf16_iso88591($string) { + $bom = substr($string, 0, 2); + if ($bom == "\xFE\xFF") { + return self::iconv_fallback_utf16be_iso88591(substr($string, 2)); + } elseif ($bom == "\xFF\xFE") { + return self::iconv_fallback_utf16le_iso88591(substr($string, 2)); + } + return $string; + } + + // UTF-16 (BOM) => UTF-8 + public static function iconv_fallback_utf16_utf8($string) { + $bom = substr($string, 0, 2); + if ($bom == "\xFE\xFF") { + return self::iconv_fallback_utf16be_utf8(substr($string, 2)); + } elseif ($bom == "\xFF\xFE") { + return self::iconv_fallback_utf16le_utf8(substr($string, 2)); + } + return $string; + } + + public static function iconv_fallback($in_charset, $out_charset, $string) { + + if ($in_charset == $out_charset) { + return $string; + } + + // iconv() availble + if (function_exists('iconv')) { + if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) { + switch ($out_charset) { + case 'ISO-8859-1': + $converted_string = rtrim($converted_string, "\x00"); + break; + } + return $converted_string; + } + + // iconv() may sometimes fail with "illegal character in input string" error message + // and return an empty string, but returning the unconverted string is more useful + return $string; + } + + + // iconv() not available + static $ConversionFunctionList = array(); + if (empty($ConversionFunctionList)) { + $ConversionFunctionList['ISO-8859-1']['UTF-8'] = 'iconv_fallback_iso88591_utf8'; + $ConversionFunctionList['ISO-8859-1']['UTF-16'] = 'iconv_fallback_iso88591_utf16'; + $ConversionFunctionList['ISO-8859-1']['UTF-16BE'] = 'iconv_fallback_iso88591_utf16be'; + $ConversionFunctionList['ISO-8859-1']['UTF-16LE'] = 'iconv_fallback_iso88591_utf16le'; + $ConversionFunctionList['UTF-8']['ISO-8859-1'] = 'iconv_fallback_utf8_iso88591'; + $ConversionFunctionList['UTF-8']['UTF-16'] = 'iconv_fallback_utf8_utf16'; + $ConversionFunctionList['UTF-8']['UTF-16BE'] = 'iconv_fallback_utf8_utf16be'; + $ConversionFunctionList['UTF-8']['UTF-16LE'] = 'iconv_fallback_utf8_utf16le'; + $ConversionFunctionList['UTF-16']['ISO-8859-1'] = 'iconv_fallback_utf16_iso88591'; + $ConversionFunctionList['UTF-16']['UTF-8'] = 'iconv_fallback_utf16_utf8'; + $ConversionFunctionList['UTF-16LE']['ISO-8859-1'] = 'iconv_fallback_utf16le_iso88591'; + $ConversionFunctionList['UTF-16LE']['UTF-8'] = 'iconv_fallback_utf16le_utf8'; + $ConversionFunctionList['UTF-16BE']['ISO-8859-1'] = 'iconv_fallback_utf16be_iso88591'; + $ConversionFunctionList['UTF-16BE']['UTF-8'] = 'iconv_fallback_utf16be_utf8'; + } + if (isset($ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)])) { + $ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)]; + return self::$ConversionFunction($string); + } + throw new Exception('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset); + } + + + public static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') { + $string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string + $HTMLstring = ''; + + switch ($charset) { + case '1251': + case '1252': + case '866': + case '932': + case '936': + case '950': + case 'BIG5': + case 'BIG5-HKSCS': + case 'cp1251': + case 'cp1252': + case 'cp866': + case 'EUC-JP': + case 'EUCJP': + case 'GB2312': + case 'ibm866': + case 'ISO-8859-1': + case 'ISO-8859-15': + case 'ISO8859-1': + case 'ISO8859-15': + case 'KOI8-R': + case 'koi8-ru': + case 'koi8r': + case 'Shift_JIS': + case 'SJIS': + case 'win-1251': + case 'Windows-1251': + case 'Windows-1252': + $HTMLstring = htmlentities($string, ENT_COMPAT, $charset); + break; + + case 'UTF-8': + $strlen = strlen($string); + for ($i = 0; $i < $strlen; $i++) { + $char_ord_val = ord($string{$i}); + $charval = 0; + if ($char_ord_val < 0x80) { + $charval = $char_ord_val; + } elseif ((($char_ord_val & 0xF0) >> 4) == 0x0F && $i+3 < $strlen) { + $charval = (($char_ord_val & 0x07) << 18); + $charval += ((ord($string{++$i}) & 0x3F) << 12); + $charval += ((ord($string{++$i}) & 0x3F) << 6); + $charval += (ord($string{++$i}) & 0x3F); + } elseif ((($char_ord_val & 0xE0) >> 5) == 0x07 && $i+2 < $strlen) { + $charval = (($char_ord_val & 0x0F) << 12); + $charval += ((ord($string{++$i}) & 0x3F) << 6); + $charval += (ord($string{++$i}) & 0x3F); + } elseif ((($char_ord_val & 0xC0) >> 6) == 0x03 && $i+1 < $strlen) { + $charval = (($char_ord_val & 0x1F) << 6); + $charval += (ord($string{++$i}) & 0x3F); + } + if (($charval >= 32) && ($charval <= 127)) { + $HTMLstring .= htmlentities(chr($charval)); + } else { + $HTMLstring .= '&#'.$charval.';'; + } + } + break; + + case 'UTF-16LE': + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::LittleEndian2Int(substr($string, $i, 2)); + if (($charval >= 32) && ($charval <= 127)) { + $HTMLstring .= chr($charval); + } else { + $HTMLstring .= '&#'.$charval.';'; + } + } + break; + + case 'UTF-16BE': + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = self::BigEndian2Int(substr($string, $i, 2)); + if (($charval >= 32) && ($charval <= 127)) { + $HTMLstring .= chr($charval); + } else { + $HTMLstring .= '&#'.$charval.';'; + } + } + break; + + default: + $HTMLstring = 'ERROR: Character set "'.$charset.'" not supported in MultiByteCharString2HTML()'; + break; + } + return $HTMLstring; + } + + + + public static function RGADnameLookup($namecode) { + static $RGADname = array(); + if (empty($RGADname)) { + $RGADname[0] = 'not set'; + $RGADname[1] = 'Track Gain Adjustment'; + $RGADname[2] = 'Album Gain Adjustment'; + } + + return (isset($RGADname[$namecode]) ? $RGADname[$namecode] : ''); + } + + + public static function RGADoriginatorLookup($originatorcode) { + static $RGADoriginator = array(); + if (empty($RGADoriginator)) { + $RGADoriginator[0] = 'unspecified'; + $RGADoriginator[1] = 'pre-set by artist/producer/mastering engineer'; + $RGADoriginator[2] = 'set by user'; + $RGADoriginator[3] = 'determined automatically'; + } + + return (isset($RGADoriginator[$originatorcode]) ? $RGADoriginator[$originatorcode] : ''); + } + + + public static function RGADadjustmentLookup($rawadjustment, $signbit) { + $adjustment = $rawadjustment / 10; + if ($signbit == 1) { + $adjustment *= -1; + } + return (float) $adjustment; + } + + + public static function RGADgainString($namecode, $originatorcode, $replaygain) { + if ($replaygain < 0) { + $signbit = '1'; + } else { + $signbit = '0'; + } + $storedreplaygain = intval(round($replaygain * 10)); + $gainstring = str_pad(decbin($namecode), 3, '0', STR_PAD_LEFT); + $gainstring .= str_pad(decbin($originatorcode), 3, '0', STR_PAD_LEFT); + $gainstring .= $signbit; + $gainstring .= str_pad(decbin($storedreplaygain), 9, '0', STR_PAD_LEFT); + + return $gainstring; + } + + public static function RGADamplitude2dB($amplitude) { + return 20 * log10($amplitude); + } + + + public static function GetDataImageSize($imgData, &$imageinfo=array()) { + static $tempdir = ''; + if (empty($tempdir)) { + // yes this is ugly, feel free to suggest a better way + require_once(dirname(__FILE__).'/getid3.php'); + $getid3_temp = new getID3(); + $tempdir = $getid3_temp->tempdir; + unset($getid3_temp); + } + $GetDataImageSize = false; + if ($tempfilename = tempnam($tempdir, 'gI3')) { + if (is_writable($tempfilename) && is_file($tempfilename) && ($tmp = fopen($tempfilename, 'wb'))) { + fwrite($tmp, $imgData); + fclose($tmp); + $GetDataImageSize = @getimagesize($tempfilename, $imageinfo); + } + unlink($tempfilename); + } + return $GetDataImageSize; + } + + public static function ImageExtFromMime($mime_type) { + // temporary way, works OK for now, but should be reworked in the future + return str_replace(array('image/', 'x-', 'jpeg'), array('', '', 'jpg'), $mime_type); + } + + public static function ImageTypesLookup($imagetypeid) { + static $ImageTypesLookup = array(); + if (empty($ImageTypesLookup)) { + $ImageTypesLookup[1] = 'gif'; + $ImageTypesLookup[2] = 'jpeg'; + $ImageTypesLookup[3] = 'png'; + $ImageTypesLookup[4] = 'swf'; + $ImageTypesLookup[5] = 'psd'; + $ImageTypesLookup[6] = 'bmp'; + $ImageTypesLookup[7] = 'tiff (little-endian)'; + $ImageTypesLookup[8] = 'tiff (big-endian)'; + $ImageTypesLookup[9] = 'jpc'; + $ImageTypesLookup[10] = 'jp2'; + $ImageTypesLookup[11] = 'jpx'; + $ImageTypesLookup[12] = 'jb2'; + $ImageTypesLookup[13] = 'swc'; + $ImageTypesLookup[14] = 'iff'; + } + return (isset($ImageTypesLookup[$imagetypeid]) ? $ImageTypesLookup[$imagetypeid] : ''); + } + + public static function CopyTagsToComments(&$ThisFileInfo) { + + // Copy all entries from ['tags'] into common ['comments'] + if (!empty($ThisFileInfo['tags'])) { + foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) { + foreach ($tagarray as $tagname => $tagdata) { + foreach ($tagdata as $key => $value) { + if (!empty($value)) { + if (empty($ThisFileInfo['comments'][$tagname])) { + + // fall through and append value + + } elseif ($tagtype == 'id3v1') { + + $newvaluelength = strlen(trim($value)); + foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { + $oldvaluelength = strlen(trim($existingvalue)); + if (($newvaluelength <= $oldvaluelength) && (substr($existingvalue, 0, $newvaluelength) == trim($value))) { + // new value is identical but shorter-than (or equal-length to) one already in comments - skip + break 2; + } + } + + } elseif (!is_array($value)) { + + $newvaluelength = strlen(trim($value)); + foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { + $oldvaluelength = strlen(trim($existingvalue)); + if (($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) { + $ThisFileInfo['comments'][$tagname][$existingkey] = trim($value); + break 2; + } + } + + } + if (is_array($value) || empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) { + $value = (is_string($value) ? trim($value) : $value); + $ThisFileInfo['comments'][$tagname][] = $value; + } + } + } + } + } + + // Copy to ['comments_html'] + foreach ($ThisFileInfo['comments'] as $field => $values) { + if ($field == 'picture') { + // pictures can take up a lot of space, and we don't need multiple copies of them + // let there be a single copy in [comments][picture], and not elsewhere + continue; + } + foreach ($values as $index => $value) { + if (is_array($value)) { + $ThisFileInfo['comments_html'][$field][$index] = $value; + } else { + $ThisFileInfo['comments_html'][$field][$index] = str_replace('�', '', self::MultiByteCharString2HTML($value, $ThisFileInfo['encoding'])); + } + } + } + } + return true; + } + + + public static function EmbeddedLookup($key, $begin, $end, $file, $name) { + + // Cached + static $cache; + if (isset($cache[$file][$name])) { + return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); + } + + // Init + $keylength = strlen($key); + $line_count = $end - $begin - 7; + + // Open php file + $fp = fopen($file, 'r'); + + // Discard $begin lines + for ($i = 0; $i < ($begin + 3); $i++) { + fgets($fp, 1024); + } + + // Loop thru line + while (0 < $line_count--) { + + // Read line + $line = ltrim(fgets($fp, 1024), "\t "); + + // METHOD A: only cache the matching key - less memory but slower on next lookup of not-previously-looked-up key + //$keycheck = substr($line, 0, $keylength); + //if ($key == $keycheck) { + // $cache[$file][$name][$keycheck] = substr($line, $keylength + 1); + // break; + //} + + // METHOD B: cache all keys in this lookup - more memory but faster on next lookup of not-previously-looked-up key + //$cache[$file][$name][substr($line, 0, $keylength)] = trim(substr($line, $keylength + 1)); + $explodedLine = explode("\t", $line, 2); + $ThisKey = (isset($explodedLine[0]) ? $explodedLine[0] : ''); + $ThisValue = (isset($explodedLine[1]) ? $explodedLine[1] : ''); + $cache[$file][$name][$ThisKey] = trim($ThisValue); + } + + // Close and return + fclose($fp); + return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : ''); + } + + public static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { + global $GETID3_ERRORARRAY; + + if (file_exists($filename)) { + if (include_once($filename)) { + return true; + } else { + $diemessage = basename($sourcefile).' depends on '.$filename.', which has errors'; + } + } else { + $diemessage = basename($sourcefile).' depends on '.$filename.', which is missing'; + } + if ($DieOnFailure) { + throw new Exception($diemessage); + } else { + $GETID3_ERRORARRAY[] = $diemessage; + } + return false; + } + + public static function trimNullByte($string) { + return trim($string, "\x00"); + } + + public static function getFileSizeSyscall($path) { + $filesize = false; + + if (GETID3_OS_ISWINDOWS) { + if (class_exists('COM')) { // From PHP 5.3.15 and 5.4.5, COM and DOTNET is no longer built into the php core.you have to add COM support in php.ini: + $filesystem = new COM('Scripting.FileSystemObject'); + $file = $filesystem->GetFile($path); + $filesize = $file->Size(); + unset($filesystem, $file); + } else { + $commandline = 'for %I in ('.escapeshellarg($path).') do @echo %~zI'; + } + } else { + $commandline = 'ls -l '.escapeshellarg($path).' | awk \'{print $5}\''; + } + if (isset($commandline)) { + $output = trim(`$commandline`); + if (ctype_digit($output)) { + $filesize = (float) $output; + } + } + return $filesize; + } + +} diff --git a/extensions/TimedMediaHandler/libs/getid3/getid3.php b/extensions/TimedMediaHandler/libs/getid3/getid3.php new file mode 100644 index 00000000..be0dd5f0 --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/getid3.php @@ -0,0 +1,1776 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// Please see readme.txt for more information // +// /// +///////////////////////////////////////////////////////////////// + +// define a constant rather than looking up every time it is needed +if (!defined('GETID3_OS_ISWINDOWS')) { + define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0)); +} +// Get base path of getID3() - ONCE +if (!defined('GETID3_INCLUDEPATH')) { + define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR); +} + +// attempt to define temp dir as something flexible but reliable +$temp_dir = ini_get('upload_tmp_dir'); +if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) { + $temp_dir = ''; +} +if (!$temp_dir && function_exists('sys_get_temp_dir')) { + // PHP v5.2.1+ + // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts + $temp_dir = sys_get_temp_dir(); +} +$temp_dir = realpath($temp_dir); +$open_basedir = ini_get('open_basedir'); +if ($open_basedir) { + // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/" + $temp_dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir); + $open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir); + if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) { + $temp_dir .= DIRECTORY_SEPARATOR; + } + $found_valid_tempdir = false; + $open_basedirs = explode(PATH_SEPARATOR, $open_basedir); + foreach ($open_basedirs as $basedir) { + if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) { + $basedir .= DIRECTORY_SEPARATOR; + } + if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) { + $found_valid_tempdir = true; + break; + } + } + if (!$found_valid_tempdir) { + $temp_dir = ''; + } + unset($open_basedirs, $found_valid_tempdir, $basedir); +} +if (!$temp_dir) { + $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir +} +// $temp_dir = '/something/else/'; // feel free to override temp dir here if it works better for your system +define('GETID3_TEMP_DIR', $temp_dir); +unset($open_basedir, $temp_dir); + +// End: Defines + + +class getID3 +{ + // public: Settings + public $encoding = 'UTF-8'; // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE + public $encoding_id3v1 = 'ISO-8859-1'; // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252' + + // public: Optional tag checks - disable for speed. + public $option_tag_id3v1 = true; // Read and process ID3v1 tags + public $option_tag_id3v2 = true; // Read and process ID3v2 tags + public $option_tag_lyrics3 = true; // Read and process Lyrics3 tags + public $option_tag_apetag = true; // Read and process APE tags + public $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding + public $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities + + // public: Optional tag/comment calucations + public $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc + + // public: Optional handling of embedded attachments (e.g. images) + public $option_save_attachments = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility + + // public: Optional calculations + public $option_md5_data = false; // Get MD5 sum of data part - slow + public $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG + public $option_sha1_data = false; // Get SHA1 sum of data part - slow + public $option_max_2gb_check = null; // Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on PHP_INT_MAX) + + // public: Read buffer size in bytes + public $option_fread_buffer_size = 32768; + + // Public variables + public $filename; // Filename of file being analysed. + public $fp; // Filepointer to file being analysed. + public $info; // Result array. + public $tempdir = GETID3_TEMP_DIR; + + // Protected variables + protected $startup_error = ''; + protected $startup_warning = ''; + protected $memory_limit = 0; + + const VERSION = '1.9.6-20130603'; + const FREAD_BUFFER_SIZE = 32768; + + const ATTACHMENTS_NONE = false; + const ATTACHMENTS_INLINE = true; + + // public: constructor + public function __construct() { + + // Check for PHP version + $required_php_version = '5.0.5'; + if (version_compare(PHP_VERSION, $required_php_version, '<')) { + $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION; + return false; + } + + // Check memory + $this->memory_limit = ini_get('memory_limit'); + if (preg_match('#([0-9]+)M#i', $this->memory_limit, $matches)) { + // could be stored as "16M" rather than 16777216 for example + $this->memory_limit = $matches[1] * 1048576; + } elseif (preg_match('#([0-9]+)G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0 + // could be stored as "2G" rather than 2147483648 for example + $this->memory_limit = $matches[1] * 1073741824; + } + if ($this->memory_limit <= 0) { + // memory limits probably disabled + } elseif ($this->memory_limit <= 4194304) { + $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'; + } elseif ($this->memory_limit <= 12582912) { + $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'; + } + + // Check safe_mode off + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); + } + + if (intval(ini_get('mbstring.func_overload')) > 0) { + $this->warning('WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", this may break things.'); + } + + // Check for magic_quotes_runtime + if (function_exists('get_magic_quotes_runtime')) { + if (get_magic_quotes_runtime()) { + return $this->startup_error('magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'); + } + } + + // Check for magic_quotes_gpc + if (function_exists('magic_quotes_gpc')) { + if (get_magic_quotes_gpc()) { + return $this->startup_error('magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'); + } + } + + // Load support library + if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { + $this->startup_error .= 'getid3.lib.php is missing or corrupt'; + } + + if ($this->option_max_2gb_check === null) { + $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647); + } + + + // Needed for Windows only: + // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC + // as well as other helper functions such as head, tail, md5sum, etc + // This path cannot contain spaces, but the below code will attempt to get the + // 8.3-equivalent path automatically + // IMPORTANT: This path must include the trailing slash + if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) { + + $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path + + if (!is_dir($helperappsdir)) { + $this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'; + } elseif (strpos(realpath($helperappsdir), ' ') !== false) { + $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir)); + $path_so_far = array(); + foreach ($DirPieces as $key => $value) { + if (strpos($value, ' ') !== false) { + if (!empty($path_so_far)) { + $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far)); + $dir_listing = `$commandline`; + $lines = explode("\n", $dir_listing); + foreach ($lines as $line) { + $line = trim($line); + if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) { + list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches; + if ((strtoupper($filesize) == '') && (strtolower($filename) == strtolower($value))) { + $value = $shortname; + } + } + } + } else { + $this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'; + } + } + $path_so_far[] = $value; + } + $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far); + } + define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR); + } + + return true; + } + + public function version() { + return self::VERSION; + } + + public function fread_buffer_size() { + return $this->option_fread_buffer_size; + } + + + // public: setOption + public function setOption($optArray) { + if (!is_array($optArray) || empty($optArray)) { + return false; + } + foreach ($optArray as $opt => $val) { + if (isset($this->$opt) === false) { + continue; + } + $this->$opt = $val; + } + return true; + } + + + public function openfile($filename) { + try { + if (!empty($this->startup_error)) { + throw new getid3_exception($this->startup_error); + } + if (!empty($this->startup_warning)) { + $this->warning($this->startup_warning); + } + + // init result array and set parameters + $this->filename = $filename; + $this->info = array(); + $this->info['GETID3_VERSION'] = $this->version(); + $this->info['php_memory_limit'] = $this->memory_limit; + + // remote files not supported + if (preg_match('/^(ht|f)tp:\/\//', $filename)) { + throw new getid3_exception('Remote files are not supported - please copy the file locally first'); + } + + $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); + $filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#U', '\1'.DIRECTORY_SEPARATOR, $filename); + + // open local file + if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { + // great + } else { + throw new getid3_exception('Could not open "'.$filename.'" (does not exist, or is not a file)'); + } + + $this->info['filesize'] = filesize($filename); + // set redundant parameters - might be needed in some include file + $this->info['filename'] = basename($filename); + $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); + $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; + + + // option_max_2gb_check + if ($this->option_max_2gb_check) { + // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB) + // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize + // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer + $fseek = fseek($this->fp, 0, SEEK_END); + if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) || + ($this->info['filesize'] < 0) || + (ftell($this->fp) < 0)) { + $real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']); + + if ($real_filesize === false) { + unset($this->info['filesize']); + fclose($this->fp); + throw new getid3_exception('Unable to determine actual filesize. File is most likely larger than '.round(PHP_INT_MAX / 1073741824).'GB and is not supported by PHP.'); + } elseif (getid3_lib::intValueSupported($real_filesize)) { + unset($this->info['filesize']); + fclose($this->fp); + throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize, 3).'GB, please report to info@getid3.org'); + } + $this->info['filesize'] = $real_filesize; + $this->warning('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.'); + } + } + + // set more parameters + $this->info['avdataoffset'] = 0; + $this->info['avdataend'] = $this->info['filesize']; + $this->info['fileformat'] = ''; // filled in later + $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used + $this->info['video']['dataformat'] = ''; // filled in later, unset if not used + $this->info['tags'] = array(); // filled in later, unset if not used + $this->info['error'] = array(); // filled in later, unset if not used + $this->info['warning'] = array(); // filled in later, unset if not used + $this->info['comments'] = array(); // filled in later, unset if not used + $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired + + return true; + + } catch (Exception $e) { + $this->error($e->getMessage()); + } + return false; + } + + // public: analyze file + public function analyze($filename) { + try { + if (!$this->openfile($filename)) { + return $this->info; + } + + // Handle tags + foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { + $option_tag = 'option_tag_'.$tag_name; + if ($this->$option_tag) { + $this->include_module('tag.'.$tag_name); + try { + $tag_class = 'getid3_'.$tag_name; + $tag = new $tag_class($this); + $tag->Analyze(); + } + catch (getid3_exception $e) { + throw $e; + } + } + } + if (isset($this->info['id3v2']['tag_offset_start'])) { + $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']); + } + foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { + if (isset($this->info[$tag_key]['tag_offset_start'])) { + $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']); + } + } + + // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier + if (!$this->option_tag_id3v2) { + fseek($this->fp, 0, SEEK_SET); + $header = fread($this->fp, 10); + if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { + $this->info['id3v2']['header'] = true; + $this->info['id3v2']['majorversion'] = ord($header{3}); + $this->info['id3v2']['minorversion'] = ord($header{4}); + $this->info['avdataoffset'] += getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length + } + } + + // read 32 kb file data + fseek($this->fp, $this->info['avdataoffset'], SEEK_SET); + $formattest = fread($this->fp, 32774); + + // determine format + $determined_format = $this->GetFileFormat($formattest, $filename); + + // unable to determine file format + if (!$determined_format) { + fclose($this->fp); + return $this->error('unable to determine file format'); + } + + // check for illegal ID3 tags + if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { + if ($determined_format['fail_id3'] === 'ERROR') { + fclose($this->fp); + return $this->error('ID3 tags not allowed on this file type.'); + } elseif ($determined_format['fail_id3'] === 'WARNING') { + $this->warning('ID3 tags not allowed on this file type.'); + } + } + + // check for illegal APE tags + if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { + if ($determined_format['fail_ape'] === 'ERROR') { + fclose($this->fp); + return $this->error('APE tags not allowed on this file type.'); + } elseif ($determined_format['fail_ape'] === 'WARNING') { + $this->warning('APE tags not allowed on this file type.'); + } + } + + // set mime type + $this->info['mime_type'] = $determined_format['mime_type']; + + // supported format signature pattern detected, but module deleted + if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { + fclose($this->fp); + return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); + } + + // module requires iconv support + // Check encoding/iconv support + if (!empty($determined_format['iconv_req']) && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { + $errormessage = 'iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. '; + if (GETID3_OS_ISWINDOWS) { + $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32'; + } else { + $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch'; + } + return $this->error($errormessage); + } + + // include module + include_once(GETID3_INCLUDEPATH.$determined_format['include']); + + // instantiate module class + $class_name = 'getid3_'.$determined_format['module']; + if (!class_exists($class_name)) { + return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); + } + $class = new $class_name($this); + $class->Analyze(); + unset($class); + + // close file + fclose($this->fp); + + // process all tags - copy to 'tags' and convert charsets + if ($this->option_tags_process) { + $this->HandleAllTags(); + } + + // perform more calculations + if ($this->option_extra_info) { + $this->ChannelsBitratePlaytimeCalculations(); + $this->CalculateCompressionRatioVideo(); + $this->CalculateCompressionRatioAudio(); + $this->CalculateReplayGain(); + $this->ProcessAudioStreams(); + } + + // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags + if ($this->option_md5_data) { + // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too + if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { + $this->getHashdata('md5'); + } + } + + // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags + if ($this->option_sha1_data) { + $this->getHashdata('sha1'); + } + + // remove undesired keys + $this->CleanUp(); + + } catch (Exception $e) { + $this->error('Caught exception: '.$e->getMessage()); + } + + // return info array + return $this->info; + } + + + // private: error handling + public function error($message) { + $this->CleanUp(); + if (!isset($this->info['error'])) { + $this->info['error'] = array(); + } + $this->info['error'][] = $message; + return $this->info; + } + + + // private: warning handling + public function warning($message) { + $this->info['warning'][] = $message; + return true; + } + + + // private: CleanUp + private function CleanUp() { + + // remove possible empty keys + $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate'); + foreach ($AVpossibleEmptyKeys as $dummy => $key) { + if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) { + unset($this->info['audio'][$key]); + } + if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) { + unset($this->info['video'][$key]); + } + } + + // remove empty root keys + if (!empty($this->info)) { + foreach ($this->info as $key => $value) { + if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) { + unset($this->info[$key]); + } + } + } + + // remove meaningless entries from unknown-format files + if (empty($this->info['fileformat'])) { + if (isset($this->info['avdataoffset'])) { + unset($this->info['avdataoffset']); + } + if (isset($this->info['avdataend'])) { + unset($this->info['avdataend']); + } + } + + // remove possible duplicated identical entries + if (!empty($this->info['error'])) { + $this->info['error'] = array_values(array_unique($this->info['error'])); + } + if (!empty($this->info['warning'])) { + $this->info['warning'] = array_values(array_unique($this->info['warning'])); + } + + // remove "global variable" type keys + unset($this->info['php_memory_limit']); + + return true; + } + + + // return array containing information about all supported formats + public function GetFileFormatArray() { + static $format_info = array(); + if (empty($format_info)) { + $format_info = array( + + // Audio formats + + // AC-3 - audio - Dolby AC-3 / Dolby Digital + 'ac3' => array( + 'pattern' => '^\x0B\x77', + 'group' => 'audio', + 'module' => 'ac3', + 'mime_type' => 'audio/ac3', + ), + + // AAC - audio - Advanced Audio Coding (AAC) - ADIF format + 'adif' => array( + 'pattern' => '^ADIF', + 'group' => 'audio', + 'module' => 'aac', + 'mime_type' => 'application/octet-stream', + 'fail_ape' => 'WARNING', + ), + +/* + // AA - audio - Audible Audiobook + 'aa' => array( + 'pattern' => '^.{4}\x57\x90\x75\x36', + 'group' => 'audio', + 'module' => 'aa', + 'mime_type' => 'audio/audible', + ), +*/ + // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) + 'adts' => array( + 'pattern' => '^\xFF[\xF0-\xF1\xF8-\xF9]', + 'group' => 'audio', + 'module' => 'aac', + 'mime_type' => 'application/octet-stream', + 'fail_ape' => 'WARNING', + ), + + + // AU - audio - NeXT/Sun AUdio (AU) + 'au' => array( + 'pattern' => '^\.snd', + 'group' => 'audio', + 'module' => 'au', + 'mime_type' => 'audio/basic', + ), + + // AVR - audio - Audio Visual Research + 'avr' => array( + 'pattern' => '^2BIT', + 'group' => 'audio', + 'module' => 'avr', + 'mime_type' => 'application/octet-stream', + ), + + // BONK - audio - Bonk v0.9+ + 'bonk' => array( + 'pattern' => '^\x00(BONK|INFO|META| ID3)', + 'group' => 'audio', + 'module' => 'bonk', + 'mime_type' => 'audio/xmms-bonk', + ), + + // DSS - audio - Digital Speech Standard + 'dss' => array( + 'pattern' => '^[\x02-\x03]ds[s2]', + 'group' => 'audio', + 'module' => 'dss', + 'mime_type' => 'application/octet-stream', + ), + + // DTS - audio - Dolby Theatre System + 'dts' => array( + 'pattern' => '^\x7F\xFE\x80\x01', + 'group' => 'audio', + 'module' => 'dts', + 'mime_type' => 'audio/dts', + ), + + // FLAC - audio - Free Lossless Audio Codec + 'flac' => array( + 'pattern' => '^fLaC', + 'group' => 'audio', + 'module' => 'flac', + 'mime_type' => 'audio/x-flac', + ), + + // LA - audio - Lossless Audio (LA) + 'la' => array( + 'pattern' => '^LA0[2-4]', + 'group' => 'audio', + 'module' => 'la', + 'mime_type' => 'application/octet-stream', + ), + + // LPAC - audio - Lossless Predictive Audio Compression (LPAC) + 'lpac' => array( + 'pattern' => '^LPAC', + 'group' => 'audio', + 'module' => 'lpac', + 'mime_type' => 'application/octet-stream', + ), + + // MIDI - audio - MIDI (Musical Instrument Digital Interface) + 'midi' => array( + 'pattern' => '^MThd', + 'group' => 'audio', + 'module' => 'midi', + 'mime_type' => 'audio/midi', + ), + + // MAC - audio - Monkey's Audio Compressor + 'mac' => array( + 'pattern' => '^MAC ', + 'group' => 'audio', + 'module' => 'monkey', + 'mime_type' => 'application/octet-stream', + ), + +// has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available +// // MOD - audio - MODule (assorted sub-formats) +// 'mod' => array( +// 'pattern' => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)', +// 'group' => 'audio', +// 'module' => 'mod', +// 'option' => 'mod', +// 'mime_type' => 'audio/mod', +// ), + + // MOD - audio - MODule (Impulse Tracker) + 'it' => array( + 'pattern' => '^IMPM', + 'group' => 'audio', + 'module' => 'mod', + //'option' => 'it', + 'mime_type' => 'audio/it', + ), + + // MOD - audio - MODule (eXtended Module, various sub-formats) + 'xm' => array( + 'pattern' => '^Extended Module', + 'group' => 'audio', + 'module' => 'mod', + //'option' => 'xm', + 'mime_type' => 'audio/xm', + ), + + // MOD - audio - MODule (ScreamTracker) + 's3m' => array( + 'pattern' => '^.{44}SCRM', + 'group' => 'audio', + 'module' => 'mod', + //'option' => 's3m', + 'mime_type' => 'audio/s3m', + ), + + // MPC - audio - Musepack / MPEGplus + 'mpc' => array( + 'pattern' => '^(MPCK|MP\+|[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0])', + 'group' => 'audio', + 'module' => 'mpc', + 'mime_type' => 'audio/x-musepack', + ), + + // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS) + 'mp3' => array( + 'pattern' => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\x0B\x10-\x1B\x20-\x2B\x30-\x3B\x40-\x4B\x50-\x5B\x60-\x6B\x70-\x7B\x80-\x8B\x90-\x9B\xA0-\xAB\xB0-\xBB\xC0-\xCB\xD0-\xDB\xE0-\xEB\xF0-\xFB]', + 'group' => 'audio', + 'module' => 'mp3', + 'mime_type' => 'audio/mpeg', + ), + + // OFR - audio - OptimFROG + 'ofr' => array( + 'pattern' => '^(\*RIFF|OFR)', + 'group' => 'audio', + 'module' => 'optimfrog', + 'mime_type' => 'application/octet-stream', + ), + + // RKAU - audio - RKive AUdio compressor + 'rkau' => array( + 'pattern' => '^RKA', + 'group' => 'audio', + 'module' => 'rkau', + 'mime_type' => 'application/octet-stream', + ), + + // SHN - audio - Shorten + 'shn' => array( + 'pattern' => '^ajkg', + 'group' => 'audio', + 'module' => 'shorten', + 'mime_type' => 'audio/xmms-shn', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org) + 'tta' => array( + 'pattern' => '^TTA', // could also be '^TTA(\x01|\x02|\x03|2|1)' + 'group' => 'audio', + 'module' => 'tta', + 'mime_type' => 'application/octet-stream', + ), + + // VOC - audio - Creative Voice (VOC) + 'voc' => array( + 'pattern' => '^Creative Voice File', + 'group' => 'audio', + 'module' => 'voc', + 'mime_type' => 'audio/voc', + ), + + // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF) + 'vqf' => array( + 'pattern' => '^TWIN', + 'group' => 'audio', + 'module' => 'vqf', + 'mime_type' => 'application/octet-stream', + ), + + // WV - audio - WavPack (v4.0+) + 'wv' => array( + 'pattern' => '^wvpk', + 'group' => 'audio', + 'module' => 'wavpack', + 'mime_type' => 'application/octet-stream', + ), + + + // Audio-Video formats + + // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio + 'asf' => array( + 'pattern' => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C', + 'group' => 'audio-video', + 'module' => 'asf', + 'mime_type' => 'video/x-ms-asf', + 'iconv_req' => false, + ), + + // BINK - audio/video - Bink / Smacker + 'bink' => array( + 'pattern' => '^(BIK|SMK)', + 'group' => 'audio-video', + 'module' => 'bink', + 'mime_type' => 'application/octet-stream', + ), + + // FLV - audio/video - FLash Video + 'flv' => array( + 'pattern' => '^FLV\x01', + 'group' => 'audio-video', + 'module' => 'flv', + 'mime_type' => 'video/x-flv', + ), + + // MKAV - audio/video - Mastroka + 'matroska' => array( + 'pattern' => '^\x1A\x45\xDF\xA3', + 'group' => 'audio-video', + 'module' => 'matroska', + 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska + ), + + // MPEG - audio/video - MPEG (Moving Pictures Experts Group) + 'mpeg' => array( + 'pattern' => '^\x00\x00\x01(\xBA|\xB3)', + 'group' => 'audio-video', + 'module' => 'mpeg', + 'mime_type' => 'video/mpeg', + ), + + // NSV - audio/video - Nullsoft Streaming Video (NSV) + 'nsv' => array( + 'pattern' => '^NSV[sf]', + 'group' => 'audio-video', + 'module' => 'nsv', + 'mime_type' => 'application/octet-stream', + ), + + // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*)) + 'ogg' => array( + 'pattern' => '^OggS', + 'group' => 'audio', + 'module' => 'ogg', + 'mime_type' => 'application/ogg', + 'fail_id3' => 'WARNING', + 'fail_ape' => 'WARNING', + ), + + // QT - audio/video - Quicktime + 'quicktime' => array( + 'pattern' => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)', + 'group' => 'audio-video', + 'module' => 'quicktime', + 'mime_type' => 'video/quicktime', + ), + + // RIFF - audio/video - Resource Interchange File Format (RIFF) / WAV / AVI / CD-audio / SDSS = renamed variant used by SmartSound QuickTracks (www.smartsound.com) / FORM = Audio Interchange File Format (AIFF) + 'riff' => array( + 'pattern' => '^(RIFF|SDSS|FORM)', + 'group' => 'audio-video', + 'module' => 'riff', + 'mime_type' => 'audio/x-wave', + 'fail_ape' => 'WARNING', + ), + + // Real - audio/video - RealAudio, RealVideo + 'real' => array( + 'pattern' => '^(\\.RMF|\\.ra)', + 'group' => 'audio-video', + 'module' => 'real', + 'mime_type' => 'audio/x-realaudio', + ), + + // SWF - audio/video - ShockWave Flash + 'swf' => array( + 'pattern' => '^(F|C)WS', + 'group' => 'audio-video', + 'module' => 'swf', + 'mime_type' => 'application/x-shockwave-flash', + ), + + // TS - audio/video - MPEG-2 Transport Stream + 'ts' => array( + 'pattern' => '^(\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G". Check for at least 10 packets matching this pattern + 'group' => 'audio-video', + 'module' => 'ts', + 'mime_type' => 'video/MP2T', + ), + + + // Still-Image formats + + // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4) + 'bmp' => array( + 'pattern' => '^BM', + 'group' => 'graphic', + 'module' => 'bmp', + 'mime_type' => 'image/bmp', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // GIF - still image - Graphics Interchange Format + 'gif' => array( + 'pattern' => '^GIF', + 'group' => 'graphic', + 'module' => 'gif', + 'mime_type' => 'image/gif', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // JPEG - still image - Joint Photographic Experts Group (JPEG) + 'jpg' => array( + 'pattern' => '^\xFF\xD8\xFF', + 'group' => 'graphic', + 'module' => 'jpg', + 'mime_type' => 'image/jpeg', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // PCD - still image - Kodak Photo CD + 'pcd' => array( + 'pattern' => '^.{2048}PCD_IPI\x00', + 'group' => 'graphic', + 'module' => 'pcd', + 'mime_type' => 'image/x-photo-cd', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // PNG - still image - Portable Network Graphics (PNG) + 'png' => array( + 'pattern' => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A', + 'group' => 'graphic', + 'module' => 'png', + 'mime_type' => 'image/png', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // SVG - still image - Scalable Vector Graphics (SVG) + 'svg' => array( + 'pattern' => '( 'graphic', + 'module' => 'svg', + 'mime_type' => 'image/svg+xml', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // TIFF - still image - Tagged Information File Format (TIFF) + 'tiff' => array( + 'pattern' => '^(II\x2A\x00|MM\x00\x2A)', + 'group' => 'graphic', + 'module' => 'tiff', + 'mime_type' => 'image/tiff', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // EFAX - still image - eFax (TIFF derivative) + 'efax' => array( + 'pattern' => '^\xDC\xFE', + 'group' => 'graphic', + 'module' => 'efax', + 'mime_type' => 'image/efax', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // Data formats + + // ISO - data - International Standards Organization (ISO) CD-ROM Image + 'iso' => array( + 'pattern' => '^.{32769}CD001', + 'group' => 'misc', + 'module' => 'iso', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + 'iconv_req' => false, + ), + + // RAR - data - RAR compressed data + 'rar' => array( + 'pattern' => '^Rar\!', + 'group' => 'archive', + 'module' => 'rar', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // SZIP - audio/data - SZIP compressed data + 'szip' => array( + 'pattern' => '^SZ\x0A\x04', + 'group' => 'archive', + 'module' => 'szip', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // TAR - data - TAR compressed data + 'tar' => array( + 'pattern' => '^.{100}[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20\x00]{12}[0-9\x20\x00]{12}', + 'group' => 'archive', + 'module' => 'tar', + 'mime_type' => 'application/x-tar', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // GZIP - data - GZIP compressed data + 'gz' => array( + 'pattern' => '^\x1F\x8B\x08', + 'group' => 'archive', + 'module' => 'gzip', + 'mime_type' => 'application/x-gzip', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // ZIP - data - ZIP compressed data + 'zip' => array( + 'pattern' => '^PK\x03\x04', + 'group' => 'archive', + 'module' => 'zip', + 'mime_type' => 'application/zip', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // Misc other formats + + // PAR2 - data - Parity Volume Set Specification 2.0 + 'par2' => array ( + 'pattern' => '^PAR2\x00PKT', + 'group' => 'misc', + 'module' => 'par2', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // PDF - data - Portable Document Format + 'pdf' => array( + 'pattern' => '^\x25PDF', + 'group' => 'misc', + 'module' => 'pdf', + 'mime_type' => 'application/pdf', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // MSOFFICE - data - ZIP compressed data + 'msoffice' => array( + 'pattern' => '^\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', // D0CF11E == DOCFILE == Microsoft Office Document + 'group' => 'misc', + 'module' => 'msoffice', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // CUE - data - CUEsheet (index to single-file disc images) + 'cue' => array( + 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents + 'group' => 'misc', + 'module' => 'cue', + 'mime_type' => 'application/octet-stream', + ), + + ); + } + + return $format_info; + } + + + + public function GetFileFormat(&$filedata, $filename='') { + // this function will determine the format of a file based on usually + // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG, + // and in the case of ISO CD image, 6 bytes offset 32kb from the start + // of the file). + + // Identify file format - loop through $format_info and detect with reg expr + foreach ($this->GetFileFormatArray() as $format_name => $info) { + // The /s switch on preg_match() forces preg_match() NOT to treat + // newline (0x0A) characters as special chars but do a binary match + if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) { + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + return $info; + } + } + + + if (preg_match('#\.mp[123a]$#i', $filename)) { + // Too many mp3 encoders on the market put gabage in front of mpeg files + // use assume format on these if format detection failed + $GetFileFormatArray = $this->GetFileFormatArray(); + $info = $GetFileFormatArray['mp3']; + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + return $info; + } elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) { + // there's not really a useful consistent "magic" at the beginning of .cue files to identify them + // so until I think of something better, just go by filename if all other format checks fail + // and verify there's at least one instance of "TRACK xx AUDIO" in the file + $GetFileFormatArray = $this->GetFileFormatArray(); + $info = $GetFileFormatArray['cue']; + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + return $info; + } + + return false; + } + + + // converts array to $encoding charset from $this->encoding + public function CharConvert(&$array, $encoding) { + + // identical encoding - end here + if ($encoding == $this->encoding) { + return; + } + + // loop thru array + foreach ($array as $key => $value) { + + // go recursive + if (is_array($value)) { + $this->CharConvert($array[$key], $encoding); + } + + // convert string + elseif (is_string($value)) { + $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value)); + } + } + } + + + public function HandleAllTags() { + + // key name => array (tag name, character encoding) + static $tags; + if (empty($tags)) { + $tags = array( + 'asf' => array('asf' , 'UTF-16LE'), + 'midi' => array('midi' , 'ISO-8859-1'), + 'nsv' => array('nsv' , 'ISO-8859-1'), + 'ogg' => array('vorbiscomment' , 'UTF-8'), + 'png' => array('png' , 'UTF-8'), + 'tiff' => array('tiff' , 'ISO-8859-1'), + 'quicktime' => array('quicktime' , 'UTF-8'), + 'real' => array('real' , 'ISO-8859-1'), + 'vqf' => array('vqf' , 'ISO-8859-1'), + 'zip' => array('zip' , 'ISO-8859-1'), + 'riff' => array('riff' , 'ISO-8859-1'), + 'lyrics3' => array('lyrics3' , 'ISO-8859-1'), + 'id3v1' => array('id3v1' , $this->encoding_id3v1), + 'id3v2' => array('id3v2' , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8 + 'ape' => array('ape' , 'UTF-8'), + 'cue' => array('cue' , 'ISO-8859-1'), + 'matroska' => array('matroska' , 'UTF-8'), + 'flac' => array('vorbiscomment' , 'UTF-8'), + 'divxtag' => array('divx' , 'ISO-8859-1'), + ); + } + + // loop through comments array + foreach ($tags as $comment_name => $tagname_encoding_array) { + list($tag_name, $encoding) = $tagname_encoding_array; + + // fill in default encoding type if not already present + if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) { + $this->info[$comment_name]['encoding'] = $encoding; + } + + // copy comments if key name set + if (!empty($this->info[$comment_name]['comments'])) { + foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { + foreach ($valuearray as $key => $value) { + if (is_string($value)) { + $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! + } + if ($value) { + $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; + } + } + if ($tag_key == 'picture') { + unset($this->info[$comment_name]['comments'][$tag_key]); + } + } + + if (!isset($this->info['tags'][$tag_name])) { + // comments are set but contain nothing but empty strings, so skip + continue; + } + + if ($this->option_tags_html) { + foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { + foreach ($valuearray as $key => $value) { + if (is_string($value)) { + //$this->info['tags_html'][$tag_name][$tag_key][$key] = getid3_lib::MultiByteCharString2HTML($value, $encoding); + $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('�', '', trim(getid3_lib::MultiByteCharString2HTML($value, $encoding))); + } else { + $this->info['tags_html'][$tag_name][$tag_key][$key] = $value; + } + } + } + } + + $this->CharConvert($this->info['tags'][$tag_name], $encoding); // only copy gets converted! + } + + } + + // pictures can take up a lot of space, and we don't need multiple copies of them + // let there be a single copy in [comments][picture], and not elsewhere + if (!empty($this->info['tags'])) { + $unset_keys = array('tags', 'tags_html'); + foreach ($this->info['tags'] as $tagtype => $tagarray) { + foreach ($tagarray as $tagname => $tagdata) { + if ($tagname == 'picture') { + foreach ($tagdata as $key => $tagarray) { + $this->info['comments']['picture'][] = $tagarray; + if (isset($tagarray['data']) && isset($tagarray['image_mime'])) { + if (isset($this->info['tags'][$tagtype][$tagname][$key])) { + unset($this->info['tags'][$tagtype][$tagname][$key]); + } + if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) { + unset($this->info['tags_html'][$tagtype][$tagname][$key]); + } + } + } + } + } + foreach ($unset_keys as $unset_key) { + // remove possible empty keys from (e.g. [tags][id3v2][picture]) + if (empty($this->info[$unset_key][$tagtype]['picture'])) { + unset($this->info[$unset_key][$tagtype]['picture']); + } + if (empty($this->info[$unset_key][$tagtype])) { + unset($this->info[$unset_key][$tagtype]); + } + if (empty($this->info[$unset_key])) { + unset($this->info[$unset_key]); + } + } + // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture]) + if (isset($this->info[$tagtype]['comments']['picture'])) { + unset($this->info[$tagtype]['comments']['picture']); + } + if (empty($this->info[$tagtype]['comments'])) { + unset($this->info[$tagtype]['comments']); + } + if (empty($this->info[$tagtype])) { + unset($this->info[$tagtype]); + } + } + } + return true; + } + + + public function getHashdata($algorithm) { + switch ($algorithm) { + case 'md5': + case 'sha1': + break; + + default: + return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()'); + break; + } + + if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) { + + // We cannot get an identical md5_data value for Ogg files where the comments + // span more than 1 Ogg page (compared to the same audio data with smaller + // comments) using the normal getID3() method of MD5'ing the data between the + // end of the comments and the end of the file (minus any trailing tags), + // because the page sequence numbers of the pages that the audio data is on + // do not match. Under normal circumstances, where comments are smaller than + // the nominal 4-8kB page size, then this is not a problem, but if there are + // very large comments, the only way around it is to strip off the comment + // tags with vorbiscomment and MD5 that file. + // This procedure must be applied to ALL Ogg files, not just the ones with + // comments larger than 1 page, because the below method simply MD5's the + // whole file with the comments stripped, not just the portion after the + // comments block (which is the standard getID3() method. + + // The above-mentioned problem of comments spanning multiple pages and changing + // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but + // currently vorbiscomment only works on OggVorbis files. + + if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { + + $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'); + $this->info[$algorithm.'_data'] = false; + + } else { + + // Prevent user from aborting script + $old_abort = ignore_user_abort(true); + + // Create empty file + $empty = tempnam(GETID3_TEMP_DIR, 'getID3'); + touch($empty); + + // Use vorbiscomment to make temp file without comments + $temp = tempnam(GETID3_TEMP_DIR, 'getID3'); + $file = $this->info['filenamepath']; + + if (GETID3_OS_ISWINDOWS) { + + if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) { + + $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"'; + $VorbisCommentError = `$commandline`; + + } else { + + $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR; + + } + + } else { + + $commandline = 'vorbiscomment -w -c "'.$empty.'" "'.$file.'" "'.$temp.'" 2>&1'; + $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1'; + $VorbisCommentError = `$commandline`; + + } + + if (!empty($VorbisCommentError)) { + + $this->info['warning'][] = 'Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError; + $this->info[$algorithm.'_data'] = false; + + } else { + + // Get hash of newly created file + switch ($algorithm) { + case 'md5': + $this->info[$algorithm.'_data'] = md5_file($temp); + break; + + case 'sha1': + $this->info[$algorithm.'_data'] = sha1_file($temp); + break; + } + } + + // Clean up + unlink($empty); + unlink($temp); + + // Reset abort setting + ignore_user_abort($old_abort); + + } + + } else { + + if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) { + + // get hash from part of file + $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm); + + } else { + + // get hash from whole file + switch ($algorithm) { + case 'md5': + $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']); + break; + + case 'sha1': + $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']); + break; + } + } + + } + return true; + } + + + public function ChannelsBitratePlaytimeCalculations() { + + // set channelmode on audio + if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) { + // ignore + } elseif ($this->info['audio']['channels'] == 1) { + $this->info['audio']['channelmode'] = 'mono'; + } elseif ($this->info['audio']['channels'] == 2) { + $this->info['audio']['channelmode'] = 'stereo'; + } + + // Calculate combined bitrate - audio + video + $CombinedBitrate = 0; + $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0); + $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0); + if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) { + $this->info['bitrate'] = $CombinedBitrate; + } + //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) { + // // for example, VBR MPEG video files cannot determine video bitrate: + // // should not set overall bitrate and playtime from audio bitrate only + // unset($this->info['bitrate']); + //} + + // video bitrate undetermined, but calculable + if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) { + // if video bitrate not set + if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) { + // AND if audio bitrate is set to same as overall bitrate + if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) { + // AND if playtime is set + if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) { + // AND if AV data offset start/end is known + // THEN we can calculate the video bitrate + $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']); + $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate']; + } + } + } + } + + if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) { + $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate']; + } + + if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) { + $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']; + } + if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) { + if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) { + // audio only + $this->info['audio']['bitrate'] = $this->info['bitrate']; + } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) { + // video only + $this->info['video']['bitrate'] = $this->info['bitrate']; + } + } + + // Set playtime string + if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) { + $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']); + } + } + + + public function CalculateCompressionRatioVideo() { + if (empty($this->info['video'])) { + return false; + } + if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) { + return false; + } + if (empty($this->info['video']['bits_per_sample'])) { + return false; + } + + switch ($this->info['video']['dataformat']) { + case 'bmp': + case 'gif': + case 'jpeg': + case 'jpg': + case 'png': + case 'tiff': + $FrameRate = 1; + $PlaytimeSeconds = 1; + $BitrateCompressed = $this->info['filesize'] * 8; + break; + + default: + if (!empty($this->info['video']['frame_rate'])) { + $FrameRate = $this->info['video']['frame_rate']; + } else { + return false; + } + if (!empty($this->info['playtime_seconds'])) { + $PlaytimeSeconds = $this->info['playtime_seconds']; + } else { + return false; + } + if (!empty($this->info['video']['bitrate'])) { + $BitrateCompressed = $this->info['video']['bitrate']; + } else { + return false; + } + break; + } + $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate; + + $this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed; + return true; + } + + + public function CalculateCompressionRatioAudio() { + if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) { + return false; + } + $this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16)); + + if (!empty($this->info['audio']['streams'])) { + foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) { + if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) { + $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16)); + } + } + } + return true; + } + + + public function CalculateReplayGain() { + if (isset($this->info['replay_gain'])) { + if (!isset($this->info['replay_gain']['reference_volume'])) { + $this->info['replay_gain']['reference_volume'] = (double) 89.0; + } + if (isset($this->info['replay_gain']['track']['adjustment'])) { + $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; + } + if (isset($this->info['replay_gain']['album']['adjustment'])) { + $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment']; + } + + if (isset($this->info['replay_gain']['track']['peak'])) { + $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']); + } + if (isset($this->info['replay_gain']['album']['peak'])) { + $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']); + } + } + return true; + } + + public function ProcessAudioStreams() { + if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) { + if (!isset($this->info['audio']['streams'])) { + foreach ($this->info['audio'] as $key => $value) { + if ($key != 'streams') { + $this->info['audio']['streams'][0][$key] = $value; + } + } + } + } + return true; + } + + public function getid3_tempnam() { + return tempnam($this->tempdir, 'gI3'); + } + + public function include_module($name) { + //if (!file_exists($this->include_path.'module.'.$name.'.php')) { + if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) { + throw new getid3_exception('Required module.'.$name.'.php is missing.'); + } + include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php'); + return true; + } + +} + + +abstract class getid3_handler +{ + protected $getid3; // pointer + + protected $data_string_flag = false; // analyzing filepointer or string + protected $data_string = ''; // string to analyze + protected $data_string_position = 0; // seek position in string + protected $data_string_length = 0; // string length + + private $dependency_to = null; + + + public function __construct(getID3 $getid3, $call_module=null) { + $this->getid3 = $getid3; + + if ($call_module) { + $this->dependency_to = str_replace('getid3_', '', $call_module); + } + } + + + // Analyze from file pointer + abstract public function Analyze(); + + + // Analyze from string instead + public function AnalyzeString($string) { + // Enter string mode + $this->setStringMode($string); + + // Save info + $saved_avdataoffset = $this->getid3->info['avdataoffset']; + $saved_avdataend = $this->getid3->info['avdataend']; + $saved_filesize = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call + + // Reset some info + $this->getid3->info['avdataoffset'] = 0; + $this->getid3->info['avdataend'] = $this->getid3->info['filesize'] = $this->data_string_length; + + // Analyze + $this->Analyze(); + + // Restore some info + $this->getid3->info['avdataoffset'] = $saved_avdataoffset; + $this->getid3->info['avdataend'] = $saved_avdataend; + $this->getid3->info['filesize'] = $saved_filesize; + + // Exit string mode + $this->data_string_flag = false; + } + + public function setStringMode($string) { + $this->data_string_flag = true; + $this->data_string = $string; + $this->data_string_length = strlen($string); + } + + protected function ftell() { + if ($this->data_string_flag) { + return $this->data_string_position; + } + return ftell($this->getid3->fp); + } + + protected function fread($bytes) { + if ($this->data_string_flag) { + $this->data_string_position += $bytes; + return substr($this->data_string, $this->data_string_position - $bytes, $bytes); + } + $pos = $this->ftell() + $bytes; + if (!getid3_lib::intValueSupported($pos)) { + throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10); + } + return fread($this->getid3->fp, $bytes); + } + + protected function fseek($bytes, $whence=SEEK_SET) { + if ($this->data_string_flag) { + switch ($whence) { + case SEEK_SET: + $this->data_string_position = $bytes; + break; + + case SEEK_CUR: + $this->data_string_position += $bytes; + break; + + case SEEK_END: + $this->data_string_position = $this->data_string_length + $bytes; + break; + } + return 0; + } else { + $pos = $bytes; + if ($whence == SEEK_CUR) { + $pos = $this->ftell() + $bytes; + } elseif ($whence == SEEK_END) { + $pos = $this->info['filesize'] + $bytes; + } + if (!getid3_lib::intValueSupported($pos)) { + throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10); + } + } + return fseek($this->getid3->fp, $bytes, $whence); + } + + protected function feof() { + if ($this->data_string_flag) { + return $this->data_string_position >= $this->data_string_length; + } + return feof($this->getid3->fp); + } + + final protected function isDependencyFor($module) { + return $this->dependency_to == $module; + } + + protected function error($text) + { + $this->getid3->info['error'][] = $text; + + return false; + } + + protected function warning($text) + { + return $this->getid3->warning($text); + } + + protected function notice($text) + { + // does nothing for now + } + + public function saveAttachment($name, $offset, $length, $image_mime=null) { + try { + + // do not extract at all + if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) { + + $attachment = null; // do not set any + + // extract to return array + } elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) { + + $this->fseek($offset); + $attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory + if ($attachment === false || strlen($attachment) != $length) { + throw new Exception('failed to read attachment data'); + } + + // assume directory path is given + } else { + + // set up destination path + $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($dir) || !is_writable($dir)) { // check supplied directory + throw new Exception('supplied path ('.$dir.') does not exist, or is not writable'); + } + $dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : ''); + + // create dest file + if (($fp_dest = fopen($dest, 'wb')) == false) { + throw new Exception('failed to create file '.$dest); + } + + // copy data + $this->fseek($offset); + $buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size()); + $bytesleft = $length; + while ($bytesleft > 0) { + if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) { + throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space'); + } + $bytesleft -= $byteswritten; + } + + fclose($fp_dest); + $attachment = $dest; + + } + + } catch (Exception $e) { + + // close and remove dest file if created + if (isset($fp_dest) && is_resource($fp_dest)) { + fclose($fp_dest); + unlink($dest); + } + + // do not set any is case of error + $attachment = null; + $this->warning('Failed to extract attachment '.$name.': '.$e->getMessage()); + + } + + // seek to the end of attachment + $this->fseek($offset + $length); + + return $attachment; + } + +} + + +class getid3_exception extends Exception +{ + public $message; +} diff --git a/extensions/TimedMediaHandler/libs/getid3/license.txt b/extensions/TimedMediaHandler/libs/getid3/license.txt new file mode 100644 index 00000000..f349faab --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/license.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 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. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. \ No newline at end of file diff --git a/extensions/TimedMediaHandler/libs/getid3/module.audio-video.matroska.php b/extensions/TimedMediaHandler/libs/getid3/module.audio-video.matroska.php new file mode 100644 index 00000000..3c1921fb --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/module.audio-video.matroska.php @@ -0,0 +1,1771 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.matriska.php // +// module for analyzing Matroska containers // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +define('EBML_ID_CHAPTERS', 0x0043A770); // [10][43][A7][70] -- A system to define basic menus and partition data. For more detailed information, look at the Chapters Explanation. +define('EBML_ID_SEEKHEAD', 0x014D9B74); // [11][4D][9B][74] -- Contains the position of other level 1 elements. +define('EBML_ID_TAGS', 0x0254C367); // [12][54][C3][67] -- Element containing elements specific to Tracks/Chapters. A list of valid tags can be found . +define('EBML_ID_INFO', 0x0549A966); // [15][49][A9][66] -- Contains miscellaneous general information and statistics on the file. +define('EBML_ID_TRACKS', 0x0654AE6B); // [16][54][AE][6B] -- A top-level block of information with many tracks described. +define('EBML_ID_SEGMENT', 0x08538067); // [18][53][80][67] -- This element contains all other top-level (level 1) elements. Typically a Matroska file is composed of 1 segment. +define('EBML_ID_ATTACHMENTS', 0x0941A469); // [19][41][A4][69] -- Contain attached files. +define('EBML_ID_EBML', 0x0A45DFA3); // [1A][45][DF][A3] -- Set the EBML characteristics of the data to follow. Each EBML document has to start with this. +define('EBML_ID_CUES', 0x0C53BB6B); // [1C][53][BB][6B] -- A top-level element to speed seeking access. All entries are local to the segment. +define('EBML_ID_CLUSTER', 0x0F43B675); // [1F][43][B6][75] -- The lower level element containing the (monolithic) Block structure. +define('EBML_ID_LANGUAGE', 0x02B59C); // [22][B5][9C] -- Specifies the language of the track in the Matroska languages form. +define('EBML_ID_TRACKTIMECODESCALE', 0x03314F); // [23][31][4F] -- The scale to apply on this track to work at normal speed in relation with other tracks (mostly used to adjust video speed when the audio length differs). +define('EBML_ID_DEFAULTDURATION', 0x03E383); // [23][E3][83] -- Number of nanoseconds (i.e. not scaled) per frame. +define('EBML_ID_CODECNAME', 0x058688); // [25][86][88] -- A human-readable string specifying the codec. +define('EBML_ID_CODECDOWNLOADURL', 0x06B240); // [26][B2][40] -- A URL to download about the codec used. +define('EBML_ID_TIMECODESCALE', 0x0AD7B1); // [2A][D7][B1] -- Timecode scale in nanoseconds (1.000.000 means all timecodes in the segment are expressed in milliseconds). +define('EBML_ID_COLOURSPACE', 0x0EB524); // [2E][B5][24] -- Same value as in AVI (32 bits). +define('EBML_ID_GAMMAVALUE', 0x0FB523); // [2F][B5][23] -- Gamma Value. +define('EBML_ID_CODECSETTINGS', 0x1A9697); // [3A][96][97] -- A string describing the encoding setting used. +define('EBML_ID_CODECINFOURL', 0x1B4040); // [3B][40][40] -- A URL to find information about the codec used. +define('EBML_ID_PREVFILENAME', 0x1C83AB); // [3C][83][AB] -- An escaped filename corresponding to the previous segment. +define('EBML_ID_PREVUID', 0x1CB923); // [3C][B9][23] -- A unique ID to identify the previous chained segment (128 bits). +define('EBML_ID_NEXTFILENAME', 0x1E83BB); // [3E][83][BB] -- An escaped filename corresponding to the next segment. +define('EBML_ID_NEXTUID', 0x1EB923); // [3E][B9][23] -- A unique ID to identify the next chained segment (128 bits). +define('EBML_ID_CONTENTCOMPALGO', 0x0254); // [42][54] -- The compression algorithm used. Algorithms that have been specified so far are: +define('EBML_ID_CONTENTCOMPSETTINGS', 0x0255); // [42][55] -- Settings that might be needed by the decompressor. For Header Stripping (ContentCompAlgo=3), the bytes that were removed from the beggining of each frames of the track. +define('EBML_ID_DOCTYPE', 0x0282); // [42][82] -- A string that describes the type of document that follows this EBML header ('matroska' in our case). +define('EBML_ID_DOCTYPEREADVERSION', 0x0285); // [42][85] -- The minimum DocType version an interpreter has to support to read this file. +define('EBML_ID_EBMLVERSION', 0x0286); // [42][86] -- The version of EBML parser used to create the file. +define('EBML_ID_DOCTYPEVERSION', 0x0287); // [42][87] -- The version of DocType interpreter used to create the file. +define('EBML_ID_EBMLMAXIDLENGTH', 0x02F2); // [42][F2] -- The maximum length of the IDs you'll find in this file (4 or less in Matroska). +define('EBML_ID_EBMLMAXSIZELENGTH', 0x02F3); // [42][F3] -- The maximum length of the sizes you'll find in this file (8 or less in Matroska). This does not override the element size indicated at the beginning of an element. Elements that have an indicated size which is larger than what is allowed by EBMLMaxSizeLength shall be considered invalid. +define('EBML_ID_EBMLREADVERSION', 0x02F7); // [42][F7] -- The minimum EBML version a parser has to support to read this file. +define('EBML_ID_CHAPLANGUAGE', 0x037C); // [43][7C] -- The languages corresponding to the string, in the bibliographic ISO-639-2 form. +define('EBML_ID_CHAPCOUNTRY', 0x037E); // [43][7E] -- The countries corresponding to the string, same 2 octets as in Internet domains. +define('EBML_ID_SEGMENTFAMILY', 0x0444); // [44][44] -- A randomly generated unique ID that all segments related to each other must use (128 bits). +define('EBML_ID_DATEUTC', 0x0461); // [44][61] -- Date of the origin of timecode (value 0), i.e. production date. +define('EBML_ID_TAGLANGUAGE', 0x047A); // [44][7A] -- Specifies the language of the tag specified, in the Matroska languages form. +define('EBML_ID_TAGDEFAULT', 0x0484); // [44][84] -- Indication to know if this is the default/original language to use for the given tag. +define('EBML_ID_TAGBINARY', 0x0485); // [44][85] -- The values of the Tag if it is binary. Note that this cannot be used in the same SimpleTag as TagString. +define('EBML_ID_TAGSTRING', 0x0487); // [44][87] -- The value of the Tag. +define('EBML_ID_DURATION', 0x0489); // [44][89] -- Duration of the segment (based on TimecodeScale). +define('EBML_ID_CHAPPROCESSPRIVATE', 0x050D); // [45][0D] -- Some optional data attached to the ChapProcessCodecID information. For ChapProcessCodecID = 1, it is the "DVD level" equivalent. +define('EBML_ID_CHAPTERFLAGENABLED', 0x0598); // [45][98] -- Specify wether the chapter is enabled. It can be enabled/disabled by a Control Track. When disabled, the movie should skip all the content between the TimeStart and TimeEnd of this chapter. +define('EBML_ID_TAGNAME', 0x05A3); // [45][A3] -- The name of the Tag that is going to be stored. +define('EBML_ID_EDITIONENTRY', 0x05B9); // [45][B9] -- Contains all information about a segment edition. +define('EBML_ID_EDITIONUID', 0x05BC); // [45][BC] -- A unique ID to identify the edition. It's useful for tagging an edition. +define('EBML_ID_EDITIONFLAGHIDDEN', 0x05BD); // [45][BD] -- If an edition is hidden (1), it should not be available to the user interface (but still to Control Tracks). +define('EBML_ID_EDITIONFLAGDEFAULT', 0x05DB); // [45][DB] -- If a flag is set (1) the edition should be used as the default one. +define('EBML_ID_EDITIONFLAGORDERED', 0x05DD); // [45][DD] -- Specify if the chapters can be defined multiple times and the order to play them is enforced. +define('EBML_ID_FILEDATA', 0x065C); // [46][5C] -- The data of the file. +define('EBML_ID_FILEMIMETYPE', 0x0660); // [46][60] -- MIME type of the file. +define('EBML_ID_FILENAME', 0x066E); // [46][6E] -- Filename of the attached file. +define('EBML_ID_FILEREFERRAL', 0x0675); // [46][75] -- A binary value that a track/codec can refer to when the attachment is needed. +define('EBML_ID_FILEDESCRIPTION', 0x067E); // [46][7E] -- A human-friendly name for the attached file. +define('EBML_ID_FILEUID', 0x06AE); // [46][AE] -- Unique ID representing the file, as random as possible. +define('EBML_ID_CONTENTENCALGO', 0x07E1); // [47][E1] -- The encryption algorithm used. The value '0' means that the contents have not been encrypted but only signed. Predefined values: +define('EBML_ID_CONTENTENCKEYID', 0x07E2); // [47][E2] -- For public key algorithms this is the ID of the public key the the data was encrypted with. +define('EBML_ID_CONTENTSIGNATURE', 0x07E3); // [47][E3] -- A cryptographic signature of the contents. +define('EBML_ID_CONTENTSIGKEYID', 0x07E4); // [47][E4] -- This is the ID of the private key the data was signed with. +define('EBML_ID_CONTENTSIGALGO', 0x07E5); // [47][E5] -- The algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values: +define('EBML_ID_CONTENTSIGHASHALGO', 0x07E6); // [47][E6] -- The hash algorithm used for the signature. A value of '0' means that the contents have not been signed but only encrypted. Predefined values: +define('EBML_ID_MUXINGAPP', 0x0D80); // [4D][80] -- Muxing application or library ("libmatroska-0.4.3"). +define('EBML_ID_SEEK', 0x0DBB); // [4D][BB] -- Contains a single seek entry to an EBML element. +define('EBML_ID_CONTENTENCODINGORDER', 0x1031); // [50][31] -- Tells when this modification was used during encoding/muxing starting with 0 and counting upwards. The decoder/demuxer has to start with the highest order number it finds and work its way down. This value has to be unique over all ContentEncodingOrder elements in the segment. +define('EBML_ID_CONTENTENCODINGSCOPE', 0x1032); // [50][32] -- A bit field that describes which elements have been modified in this way. Values (big endian) can be OR'ed. Possible values: +define('EBML_ID_CONTENTENCODINGTYPE', 0x1033); // [50][33] -- A value describing what kind of transformation has been done. Possible values: +define('EBML_ID_CONTENTCOMPRESSION', 0x1034); // [50][34] -- Settings describing the compression used. Must be present if the value of ContentEncodingType is 0 and absent otherwise. Each block must be decompressable even if no previous block is available in order not to prevent seeking. +define('EBML_ID_CONTENTENCRYPTION', 0x1035); // [50][35] -- Settings describing the encryption used. Must be present if the value of ContentEncodingType is 1 and absent otherwise. +define('EBML_ID_CUEREFNUMBER', 0x135F); // [53][5F] -- Number of the referenced Block of Track X in the specified Cluster. +define('EBML_ID_NAME', 0x136E); // [53][6E] -- A human-readable track name. +define('EBML_ID_CUEBLOCKNUMBER', 0x1378); // [53][78] -- Number of the Block in the specified Cluster. +define('EBML_ID_TRACKOFFSET', 0x137F); // [53][7F] -- A value to add to the Block's Timecode. This can be used to adjust the playback offset of a track. +define('EBML_ID_SEEKID', 0x13AB); // [53][AB] -- The binary ID corresponding to the element name. +define('EBML_ID_SEEKPOSITION', 0x13AC); // [53][AC] -- The position of the element in the segment in octets (0 = first level 1 element). +define('EBML_ID_STEREOMODE', 0x13B8); // [53][B8] -- Stereo-3D video mode. +define('EBML_ID_OLDSTEREOMODE', 0x13B9); // [53][B9] -- Bogus StereoMode value used in old versions of libmatroska. DO NOT USE. (0: mono, 1: right eye, 2: left eye, 3: both eyes). +define('EBML_ID_PIXELCROPBOTTOM', 0x14AA); // [54][AA] -- The number of video pixels to remove at the bottom of the image (for HDTV content). +define('EBML_ID_DISPLAYWIDTH', 0x14B0); // [54][B0] -- Width of the video frames to display. +define('EBML_ID_DISPLAYUNIT', 0x14B2); // [54][B2] -- Type of the unit for DisplayWidth/Height (0: pixels, 1: centimeters, 2: inches). +define('EBML_ID_ASPECTRATIOTYPE', 0x14B3); // [54][B3] -- Specify the possible modifications to the aspect ratio (0: free resizing, 1: keep aspect ratio, 2: fixed). +define('EBML_ID_DISPLAYHEIGHT', 0x14BA); // [54][BA] -- Height of the video frames to display. +define('EBML_ID_PIXELCROPTOP', 0x14BB); // [54][BB] -- The number of video pixels to remove at the top of the image. +define('EBML_ID_PIXELCROPLEFT', 0x14CC); // [54][CC] -- The number of video pixels to remove on the left of the image. +define('EBML_ID_PIXELCROPRIGHT', 0x14DD); // [54][DD] -- The number of video pixels to remove on the right of the image. +define('EBML_ID_FLAGFORCED', 0x15AA); // [55][AA] -- Set if that track MUST be used during playback. There can be many forced track for a kind (audio, video or subs), the player should select the one which language matches the user preference or the default + forced track. Overlay MAY happen between a forced and non-forced track of the same kind. +define('EBML_ID_MAXBLOCKADDITIONID', 0x15EE); // [55][EE] -- The maximum value of BlockAddID. A value 0 means there is no BlockAdditions for this track. +define('EBML_ID_WRITINGAPP', 0x1741); // [57][41] -- Writing application ("mkvmerge-0.3.3"). +define('EBML_ID_CLUSTERSILENTTRACKS', 0x1854); // [58][54] -- The list of tracks that are not used in that part of the stream. It is useful when using overlay tracks on seeking. Then you should decide what track to use. +define('EBML_ID_CLUSTERSILENTTRACKNUMBER', 0x18D7); // [58][D7] -- One of the track number that are not used from now on in the stream. It could change later if not specified as silent in a further Cluster. +define('EBML_ID_ATTACHEDFILE', 0x21A7); // [61][A7] -- An attached file. +define('EBML_ID_CONTENTENCODING', 0x2240); // [62][40] -- Settings for one content encoding like compression or encryption. +define('EBML_ID_BITDEPTH', 0x2264); // [62][64] -- Bits per sample, mostly used for PCM. +define('EBML_ID_CODECPRIVATE', 0x23A2); // [63][A2] -- Private data only known to the codec. +define('EBML_ID_TARGETS', 0x23C0); // [63][C0] -- Contain all UIDs where the specified meta data apply. It is void to describe everything in the segment. +define('EBML_ID_CHAPTERPHYSICALEQUIV', 0x23C3); // [63][C3] -- Specify the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50), see complete list of values. +define('EBML_ID_TAGCHAPTERUID', 0x23C4); // [63][C4] -- A unique ID to identify the Chapter(s) the tags belong to. If the value is 0 at this level, the tags apply to all chapters in the Segment. +define('EBML_ID_TAGTRACKUID', 0x23C5); // [63][C5] -- A unique ID to identify the Track(s) the tags belong to. If the value is 0 at this level, the tags apply to all tracks in the Segment. +define('EBML_ID_TAGATTACHMENTUID', 0x23C6); // [63][C6] -- A unique ID to identify the Attachment(s) the tags belong to. If the value is 0 at this level, the tags apply to all the attachments in the Segment. +define('EBML_ID_TAGEDITIONUID', 0x23C9); // [63][C9] -- A unique ID to identify the EditionEntry(s) the tags belong to. If the value is 0 at this level, the tags apply to all editions in the Segment. +define('EBML_ID_TARGETTYPE', 0x23CA); // [63][CA] -- An informational string that can be used to display the logical level of the target like "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc (see TargetType). +define('EBML_ID_TRACKTRANSLATE', 0x2624); // [66][24] -- The track identification for the given Chapter Codec. +define('EBML_ID_TRACKTRANSLATETRACKID', 0x26A5); // [66][A5] -- The binary value used to represent this track in the chapter codec data. The format depends on the ChapProcessCodecID used. +define('EBML_ID_TRACKTRANSLATECODEC', 0x26BF); // [66][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu). +define('EBML_ID_TRACKTRANSLATEEDITIONUID', 0x26FC); // [66][FC] -- Specify an edition UID on which this translation applies. When not specified, it means for all editions found in the segment. +define('EBML_ID_SIMPLETAG', 0x27C8); // [67][C8] -- Contains general information about the target. +define('EBML_ID_TARGETTYPEVALUE', 0x28CA); // [68][CA] -- A number to indicate the logical level of the target (see TargetType). +define('EBML_ID_CHAPPROCESSCOMMAND', 0x2911); // [69][11] -- Contains all the commands associated to the Atom. +define('EBML_ID_CHAPPROCESSTIME', 0x2922); // [69][22] -- Defines when the process command should be handled (0: during the whole chapter, 1: before starting playback, 2: after playback of the chapter). +define('EBML_ID_CHAPTERTRANSLATE', 0x2924); // [69][24] -- A tuple of corresponding ID used by chapter codecs to represent this segment. +define('EBML_ID_CHAPPROCESSDATA', 0x2933); // [69][33] -- Contains the command information. The data should be interpreted depending on the ChapProcessCodecID value. For ChapProcessCodecID = 1, the data correspond to the binary DVD cell pre/post commands. +define('EBML_ID_CHAPPROCESS', 0x2944); // [69][44] -- Contains all the commands associated to the Atom. +define('EBML_ID_CHAPPROCESSCODECID', 0x2955); // [69][55] -- Contains the type of the codec used for the processing. A value of 0 means native Matroska processing (to be defined), a value of 1 means the DVD command set is used. More codec IDs can be added later. +define('EBML_ID_CHAPTERTRANSLATEID', 0x29A5); // [69][A5] -- The binary value used to represent this segment in the chapter codec data. The format depends on the ChapProcessCodecID used. +define('EBML_ID_CHAPTERTRANSLATECODEC', 0x29BF); // [69][BF] -- The chapter codec using this ID (0: Matroska Script, 1: DVD-menu). +define('EBML_ID_CHAPTERTRANSLATEEDITIONUID', 0x29FC); // [69][FC] -- Specify an edition UID on which this correspondance applies. When not specified, it means for all editions found in the segment. +define('EBML_ID_CONTENTENCODINGS', 0x2D80); // [6D][80] -- Settings for several content encoding mechanisms like compression or encryption. +define('EBML_ID_MINCACHE', 0x2DE7); // [6D][E7] -- The minimum number of frames a player should be able to cache during playback. If set to 0, the reference pseudo-cache system is not used. +define('EBML_ID_MAXCACHE', 0x2DF8); // [6D][F8] -- The maximum cache size required to store referenced frames in and the current frame. 0 means no cache is needed. +define('EBML_ID_CHAPTERSEGMENTUID', 0x2E67); // [6E][67] -- A segment to play in place of this chapter. Edition ChapterSegmentEditionUID should be used for this segment, otherwise no edition is used. +define('EBML_ID_CHAPTERSEGMENTEDITIONUID', 0x2EBC); // [6E][BC] -- The edition to play from the segment linked in ChapterSegmentUID. +define('EBML_ID_TRACKOVERLAY', 0x2FAB); // [6F][AB] -- Specify that this track is an overlay track for the Track specified (in the u-integer). That means when this track has a gap (see SilentTracks) the overlay track should be used instead. The order of multiple TrackOverlay matters, the first one is the one that should be used. If not found it should be the second, etc. +define('EBML_ID_TAG', 0x3373); // [73][73] -- Element containing elements specific to Tracks/Chapters. +define('EBML_ID_SEGMENTFILENAME', 0x3384); // [73][84] -- A filename corresponding to this segment. +define('EBML_ID_SEGMENTUID', 0x33A4); // [73][A4] -- A randomly generated unique ID to identify the current segment between many others (128 bits). +define('EBML_ID_CHAPTERUID', 0x33C4); // [73][C4] -- A unique ID to identify the Chapter. +define('EBML_ID_TRACKUID', 0x33C5); // [73][C5] -- A unique ID to identify the Track. This should be kept the same when making a direct stream copy of the Track to another file. +define('EBML_ID_ATTACHMENTLINK', 0x3446); // [74][46] -- The UID of an attachment that is used by this codec. +define('EBML_ID_CLUSTERBLOCKADDITIONS', 0x35A1); // [75][A1] -- Contain additional blocks to complete the main one. An EBML parser that has no knowledge of the Block structure could still see and use/skip these data. +define('EBML_ID_CHANNELPOSITIONS', 0x347B); // [7D][7B] -- Table of horizontal angles for each successive channel, see appendix. +define('EBML_ID_OUTPUTSAMPLINGFREQUENCY', 0x38B5); // [78][B5] -- Real output sampling frequency in Hz (used for SBR techniques). +define('EBML_ID_TITLE', 0x3BA9); // [7B][A9] -- General name of the segment. +define('EBML_ID_CHAPTERDISPLAY', 0x00); // [80] -- Contains all possible strings to use for the chapter display. +define('EBML_ID_TRACKTYPE', 0x03); // [83] -- A set of track types coded on 8 bits (1: video, 2: audio, 3: complex, 0x10: logo, 0x11: subtitle, 0x12: buttons, 0x20: control). +define('EBML_ID_CHAPSTRING', 0x05); // [85] -- Contains the string to use as the chapter atom. +define('EBML_ID_CODECID', 0x06); // [86] -- An ID corresponding to the codec, see the codec page for more info. +define('EBML_ID_FLAGDEFAULT', 0x08); // [88] -- Set if that track (audio, video or subs) SHOULD be used if no language found matches the user preference. +define('EBML_ID_CHAPTERTRACKNUMBER', 0x09); // [89] -- UID of the Track to apply this chapter too. In the absense of a control track, choosing this chapter will select the listed Tracks and deselect unlisted tracks. Absense of this element indicates that the Chapter should be applied to any currently used Tracks. +define('EBML_ID_CLUSTERSLICES', 0x0E); // [8E] -- Contains slices description. +define('EBML_ID_CHAPTERTRACK', 0x0F); // [8F] -- List of tracks on which the chapter applies. If this element is not present, all tracks apply +define('EBML_ID_CHAPTERTIMESTART', 0x11); // [91] -- Timecode of the start of Chapter (not scaled). +define('EBML_ID_CHAPTERTIMEEND', 0x12); // [92] -- Timecode of the end of Chapter (timecode excluded, not scaled). +define('EBML_ID_CUEREFTIME', 0x16); // [96] -- Timecode of the referenced Block. +define('EBML_ID_CUEREFCLUSTER', 0x17); // [97] -- Position of the Cluster containing the referenced Block. +define('EBML_ID_CHAPTERFLAGHIDDEN', 0x18); // [98] -- If a chapter is hidden (1), it should not be available to the user interface (but still to Control Tracks). +define('EBML_ID_FLAGINTERLACED', 0x1A); // [9A] -- Set if the video is interlaced. +define('EBML_ID_CLUSTERBLOCKDURATION', 0x1B); // [9B] -- The duration of the Block (based on TimecodeScale). This element is mandatory when DefaultDuration is set for the track. When not written and with no DefaultDuration, the value is assumed to be the difference between the timecode of this Block and the timecode of the next Block in "display" order (not coding order). This element can be useful at the end of a Track (as there is not other Block available), or when there is a break in a track like for subtitle tracks. +define('EBML_ID_FLAGLACING', 0x1C); // [9C] -- Set if the track may contain blocks using lacing. +define('EBML_ID_CHANNELS', 0x1F); // [9F] -- Numbers of channels in the track. +define('EBML_ID_CLUSTERBLOCKGROUP', 0x20); // [A0] -- Basic container of information containing a single Block or BlockVirtual, and information specific to that Block/VirtualBlock. +define('EBML_ID_CLUSTERBLOCK', 0x21); // [A1] -- Block containing the actual data to be rendered and a timecode relative to the Cluster Timecode. +define('EBML_ID_CLUSTERBLOCKVIRTUAL', 0x22); // [A2] -- A Block with no data. It must be stored in the stream at the place the real Block should be in display order. +define('EBML_ID_CLUSTERSIMPLEBLOCK', 0x23); // [A3] -- Similar to Block but without all the extra information, mostly used to reduced overhead when no extra feature is needed. +define('EBML_ID_CLUSTERCODECSTATE', 0x24); // [A4] -- The new codec state to use. Data interpretation is private to the codec. This information should always be referenced by a seek entry. +define('EBML_ID_CLUSTERBLOCKADDITIONAL', 0x25); // [A5] -- Interpreted by the codec as it wishes (using the BlockAddID). +define('EBML_ID_CLUSTERBLOCKMORE', 0x26); // [A6] -- Contain the BlockAdditional and some parameters. +define('EBML_ID_CLUSTERPOSITION', 0x27); // [A7] -- Position of the Cluster in the segment (0 in live broadcast streams). It might help to resynchronise offset on damaged streams. +define('EBML_ID_CODECDECODEALL', 0x2A); // [AA] -- The codec can decode potentially damaged data. +define('EBML_ID_CLUSTERPREVSIZE', 0x2B); // [AB] -- Size of the previous Cluster, in octets. Can be useful for backward playing. +define('EBML_ID_TRACKENTRY', 0x2E); // [AE] -- Describes a track with all elements. +define('EBML_ID_CLUSTERENCRYPTEDBLOCK', 0x2F); // [AF] -- Similar to SimpleBlock but the data inside the Block are Transformed (encrypt and/or signed). +define('EBML_ID_PIXELWIDTH', 0x30); // [B0] -- Width of the encoded video frames in pixels. +define('EBML_ID_CUETIME', 0x33); // [B3] -- Absolute timecode according to the segment time base. +define('EBML_ID_SAMPLINGFREQUENCY', 0x35); // [B5] -- Sampling frequency in Hz. +define('EBML_ID_CHAPTERATOM', 0x36); // [B6] -- Contains the atom information to use as the chapter atom (apply to all tracks). +define('EBML_ID_CUETRACKPOSITIONS', 0x37); // [B7] -- Contain positions for different tracks corresponding to the timecode. +define('EBML_ID_FLAGENABLED', 0x39); // [B9] -- Set if the track is used. +define('EBML_ID_PIXELHEIGHT', 0x3A); // [BA] -- Height of the encoded video frames in pixels. +define('EBML_ID_CUEPOINT', 0x3B); // [BB] -- Contains all information relative to a seek point in the segment. +define('EBML_ID_CRC32', 0x3F); // [BF] -- The CRC is computed on all the data of the Master element it's in, regardless of its position. It's recommended to put the CRC value at the beggining of the Master element for easier reading. All level 1 elements should include a CRC-32. +define('EBML_ID_CLUSTERBLOCKADDITIONID', 0x4B); // [CB] -- The ID of the BlockAdditional element (0 is the main Block). +define('EBML_ID_CLUSTERLACENUMBER', 0x4C); // [CC] -- The reverse number of the frame in the lace (0 is the last frame, 1 is the next to last, etc). While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback. +define('EBML_ID_CLUSTERFRAMENUMBER', 0x4D); // [CD] -- The number of the frame to generate from this lace with this delay (allow you to generate many frames from the same Block/Frame). +define('EBML_ID_CLUSTERDELAY', 0x4E); // [CE] -- The (scaled) delay to apply to the element. +define('EBML_ID_CLUSTERDURATION', 0x4F); // [CF] -- The (scaled) duration to apply to the element. +define('EBML_ID_TRACKNUMBER', 0x57); // [D7] -- The track number as used in the Block Header (using more than 127 tracks is not encouraged, though the design allows an unlimited number). +define('EBML_ID_CUEREFERENCE', 0x5B); // [DB] -- The Clusters containing the required referenced Blocks. +define('EBML_ID_VIDEO', 0x60); // [E0] -- Video settings. +define('EBML_ID_AUDIO', 0x61); // [E1] -- Audio settings. +define('EBML_ID_CLUSTERTIMESLICE', 0x68); // [E8] -- Contains extra time information about the data contained in the Block. While there are a few files in the wild with this element, it is no longer in use and has been deprecated. Being able to interpret this element is not required for playback. +define('EBML_ID_CUECODECSTATE', 0x6A); // [EA] -- The position of the Codec State corresponding to this Cue element. 0 means that the data is taken from the initial Track Entry. +define('EBML_ID_CUEREFCODECSTATE', 0x6B); // [EB] -- The position of the Codec State corresponding to this referenced element. 0 means that the data is taken from the initial Track Entry. +define('EBML_ID_VOID', 0x6C); // [EC] -- Used to void damaged data, to avoid unexpected behaviors when using damaged data. The content is discarded. Also used to reserve space in a sub-element for later use. +define('EBML_ID_CLUSTERTIMECODE', 0x67); // [E7] -- Absolute timecode of the cluster (based on TimecodeScale). +define('EBML_ID_CLUSTERBLOCKADDID', 0x6E); // [EE] -- An ID to identify the BlockAdditional level. +define('EBML_ID_CUECLUSTERPOSITION', 0x71); // [F1] -- The position of the Cluster containing the required Block. +define('EBML_ID_CUETRACK', 0x77); // [F7] -- The track for which a position is given. +define('EBML_ID_CLUSTERREFERENCEPRIORITY', 0x7A); // [FA] -- This frame is referenced and has the specified cache priority. In cache only a frame of the same or higher priority can replace this frame. A value of 0 means the frame is not referenced. +define('EBML_ID_CLUSTERREFERENCEBLOCK', 0x7B); // [FB] -- Timecode of another frame used as a reference (ie: B or P frame). The timecode is relative to the block it's attached to. +define('EBML_ID_CLUSTERREFERENCEVIRTUAL', 0x7D); // [FD] -- Relative position of the data that should be in position of the virtual block. + + +/** +* @tutorial http://www.matroska.org/technical/specs/index.html +* +* @todo Rewrite EBML parser to reduce it's size and honor default element values +* @todo After rewrite implement stream size calculation, that will provide additional useful info and enable AAC/FLAC audio bitrate detection +*/ +class getid3_matroska extends getid3_handler +{ + // public options + public static $hide_clusters = true; // if true, do not return information about CLUSTER chunks, since there's a lot of them and they're not usually useful [default: TRUE] + public static $parse_whole_file = false; // true to parse the whole file, not only header [default: FALSE] + + // private parser settings/placeholders + private $EBMLbuffer = ''; + private $EBMLbuffer_offset = 0; + private $EBMLbuffer_length = 0; + private $current_offset = 0; + private $unuseful_elements = array(EBML_ID_CRC32, EBML_ID_VOID); + + public function Analyze() + { + $info = &$this->getid3->info; + + // parse container + try { + $this->parseEBML($info); + } catch (Exception $e) { + $info['error'][] = 'EBML parser: '.$e->getMessage(); + } + + // calculate playtime + if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { + foreach ($info['matroska']['info'] as $key => $infoarray) { + if (isset($infoarray['Duration'])) { + // TimecodeScale is how many nanoseconds each Duration unit is + $info['playtime_seconds'] = $infoarray['Duration'] * ((isset($infoarray['TimecodeScale']) ? $infoarray['TimecodeScale'] : 1000000) / 1000000000); + break; + } + } + } + + // extract tags + if (isset($info['matroska']['tags']) && is_array($info['matroska']['tags'])) { + foreach ($info['matroska']['tags'] as $key => $infoarray) { + $this->ExtractCommentsSimpleTag($infoarray); + } + } + + // process tracks + if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { + foreach ($info['matroska']['tracks']['tracks'] as $key => $trackarray) { + + $track_info = array(); + $track_info['dataformat'] = self::CodecIDtoCommonName($trackarray['CodecID']); + $track_info['default'] = (isset($trackarray['FlagDefault']) ? $trackarray['FlagDefault'] : true); + if (isset($trackarray['Name'])) { $track_info['name'] = $trackarray['Name']; } + + switch ($trackarray['TrackType']) { + + case 1: // Video + $track_info['resolution_x'] = $trackarray['PixelWidth']; + $track_info['resolution_y'] = $trackarray['PixelHeight']; + $track_info['display_unit'] = self::displayUnit(isset($trackarray['DisplayUnit']) ? $trackarray['DisplayUnit'] : 0); + $track_info['display_x'] = (isset($trackarray['DisplayWidth']) ? $trackarray['DisplayWidth'] : $trackarray['PixelWidth']); + $track_info['display_y'] = (isset($trackarray['DisplayHeight']) ? $trackarray['DisplayHeight'] : $trackarray['PixelHeight']); + + if (isset($trackarray['PixelCropBottom'])) { $track_info['crop_bottom'] = $trackarray['PixelCropBottom']; } + if (isset($trackarray['PixelCropTop'])) { $track_info['crop_top'] = $trackarray['PixelCropTop']; } + if (isset($trackarray['PixelCropLeft'])) { $track_info['crop_left'] = $trackarray['PixelCropLeft']; } + if (isset($trackarray['PixelCropRight'])) { $track_info['crop_right'] = $trackarray['PixelCropRight']; } + if (isset($trackarray['DefaultDuration'])) { $track_info['frame_rate'] = round(1000000000 / $trackarray['DefaultDuration'], 3); } + if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } + + switch ($trackarray['CodecID']) { + case 'V_MS/VFW/FOURCC': + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { + $this->warning('Unable to parse codec private data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'); + break; + } + $parsed = getid3_riff::ParseBITMAPINFOHEADER($trackarray['CodecPrivate']); + $track_info['codec'] = getid3_riff::fourccLookup($parsed['fourcc']); + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; + break; + + /*case 'V_MPEG4/ISO/AVC': + $h264['profile'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 1, 1)); + $h264['level'] = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 3, 1)); + $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 4, 1)); + $h264['NALUlength'] = ($rn & 3) + 1; + $rn = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], 5, 1)); + $nsps = ($rn & 31); + $offset = 6; + for ($i = 0; $i < $nsps; $i ++) { + $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); + $h264['SPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); + $offset += 2 + $length; + } + $npps = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 1)); + $offset += 1; + for ($i = 0; $i < $npps; $i ++) { + $length = getid3_lib::BigEndian2Int(substr($trackarray['CodecPrivate'], $offset, 2)); + $h264['PPS'][] = substr($trackarray['CodecPrivate'], $offset + 2, $length); + $offset += 2 + $length; + } + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $h264; + break;*/ + } + + $info['video']['streams'][] = $track_info; + break; + + case 2: // Audio + $track_info['sample_rate'] = (isset($trackarray['SamplingFrequency']) ? $trackarray['SamplingFrequency'] : 8000.0); + $track_info['channels'] = (isset($trackarray['Channels']) ? $trackarray['Channels'] : 1); + $track_info['language'] = (isset($trackarray['Language']) ? $trackarray['Language'] : 'eng'); + if (isset($trackarray['BitDepth'])) { $track_info['bits_per_sample'] = $trackarray['BitDepth']; } + if (isset($trackarray['CodecName'])) { $track_info['codec'] = $trackarray['CodecName']; } + + switch ($trackarray['CodecID']) { + case 'A_PCM/INT/LIT': + case 'A_PCM/INT/BIG': + $track_info['bitrate'] = $trackarray['SamplingFrequency'] * $trackarray['Channels'] * $trackarray['BitDepth']; + break; + + case 'A_AC3': + case 'A_DTS': + case 'A_MPEG/L3': + case 'A_MPEG/L2': + case 'A_FLAC': + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.'.($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']).'.php', __FILE__, false)) { + $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.'.$track_info['dataformat'].'.php"'); + break; + } + + if (!isset($info['matroska']['track_data_offsets'][$trackarray['TrackNumber']])) { + $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because $info[matroska][track_data_offsets]['.$trackarray['TrackNumber'].'] not set'); + break; + } + + // create temp instance + $getid3_temp = new getID3(); + if ($track_info['dataformat'] != 'flac') { + $getid3_temp->openfile($this->getid3->filename); + } + $getid3_temp->info['avdataoffset'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset']; + if ($track_info['dataformat'][0] == 'm' || $track_info['dataformat'] == 'flac') { + $getid3_temp->info['avdataend'] = $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['offset'] + $info['matroska']['track_data_offsets'][$trackarray['TrackNumber']]['length']; + } + + // analyze + $class = 'getid3_'.($track_info['dataformat'] == 'mp2' ? 'mp3' : $track_info['dataformat']); + $header_data_key = $track_info['dataformat'][0] == 'm' ? 'mpeg' : $track_info['dataformat']; + $getid3_audio = new $class($getid3_temp, __CLASS__); + if ($track_info['dataformat'] == 'flac') { + $getid3_audio->AnalyzeString($trackarray['CodecPrivate']); + } + else { + $getid3_audio->Analyze(); + } + if (!empty($getid3_temp->info[$header_data_key])) { + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info[$header_data_key]; + if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { + foreach ($getid3_temp->info['audio'] as $key => $value) { + $track_info[$key] = $value; + } + } + } + else { + $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because '.$class.'::Analyze() failed at offset '.$getid3_temp->info['avdataoffset']); + } + + // copy errors and warnings + if (!empty($getid3_temp->info['error'])) { + foreach ($getid3_temp->info['error'] as $newerror) { + $this->warning($class.'() says: ['.$newerror.']'); + } + } + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + if ($track_info['dataformat'] == 'mp3' && preg_match('/^Probable truncated file: expecting \d+ bytes of audio data, only found \d+ \(short by \d+ bytes\)$/', $newerror)) { + // LAME/Xing header is probably set, but audio data is chunked into Matroska file and near-impossible to verify if audio stream is complete, so ignore useless warning + continue; + } + $this->warning($class.'() says: ['.$newerror.']'); + } + } + unset($getid3_temp, $getid3_audio); + break; + + case 'A_AAC': + case 'A_AAC/MPEG2/LC': + case 'A_AAC/MPEG2/LC/SBR': + case 'A_AAC/MPEG4/LC': + case 'A_AAC/MPEG4/LC/SBR': + $this->warning($trackarray['CodecID'].' audio data contains no header, audio/video bitrates can\'t be calculated'); + break; + + case 'A_VORBIS': + if (!isset($trackarray['CodecPrivate'])) { + $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data not set'); + break; + } + $vorbis_offset = strpos($trackarray['CodecPrivate'], 'vorbis', 1); + if ($vorbis_offset === false) { + $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because CodecPrivate data does not contain "vorbis" keyword'); + break; + } + $vorbis_offset -= 1; + + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, false)) { + $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio.ogg.php"'); + break; + } + + // create temp instance + $getid3_temp = new getID3(); + + // analyze + $getid3_ogg = new getid3_ogg($getid3_temp); + $oggpageinfo['page_seqno'] = 0; + $getid3_ogg->ParseVorbisPageHeader($trackarray['CodecPrivate'], $vorbis_offset, $oggpageinfo); + if (!empty($getid3_temp->info['ogg'])) { + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $getid3_temp->info['ogg']; + if (isset($getid3_temp->info['audio']) && is_array($getid3_temp->info['audio'])) { + foreach ($getid3_temp->info['audio'] as $key => $value) { + $track_info[$key] = $value; + } + } + } + + // copy errors and warnings + if (!empty($getid3_temp->info['error'])) { + foreach ($getid3_temp->info['error'] as $newerror) { + $this->warning('getid3_ogg() says: ['.$newerror.']'); + } + } + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + $this->warning('getid3_ogg() says: ['.$newerror.']'); + } + } + + if (!empty($getid3_temp->info['ogg']['bitrate_nominal'])) { + $track_info['bitrate'] = $getid3_temp->info['ogg']['bitrate_nominal']; + } + unset($getid3_temp, $getid3_ogg, $oggpageinfo, $vorbis_offset); + break; + + case 'A_MS/ACM': + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, false)) { + $this->warning('Unable to parse audio data ['.basename(__FILE__).':'.__LINE__.'] because cannot include "module.audio-video.riff.php"'); + break; + } + + $parsed = getid3_riff::parseWAVEFORMATex($trackarray['CodecPrivate']); + foreach ($parsed as $key => $value) { + if ($key != 'raw') { + $track_info[$key] = $value; + } + } + $info['matroska']['track_codec_parsed'][$trackarray['TrackNumber']] = $parsed; + break; + + default: + $this->warning('Unhandled audio type "'.(isset($trackarray['CodecID']) ? $trackarray['CodecID'] : '').'"'); + } + + $info['audio']['streams'][] = $track_info; + break; + } + } + + if (!empty($info['video']['streams'])) { + $info['video'] = self::getDefaultStreamInfo($info['video']['streams']); + } + if (!empty($info['audio']['streams'])) { + $info['audio'] = self::getDefaultStreamInfo($info['audio']['streams']); + } + } + + // process attachments + if (isset($info['matroska']['attachments']) && $this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE) { + foreach ($info['matroska']['attachments'] as $i => $entry) { + if (strpos($entry['FileMimeType'], 'image/') === 0 && !empty($entry['FileData'])) { + $info['matroska']['comments']['picture'][] = array('data' => $entry['FileData'], 'image_mime' => $entry['FileMimeType'], 'filename' => $entry['FileName']); + } + } + } + + // determine mime type + if (!empty($info['video']['streams'])) { + $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'video/webm' : 'video/x-matroska'); + } elseif (!empty($info['audio']['streams'])) { + $info['mime_type'] = ($info['matroska']['doctype'] == 'webm' ? 'audio/webm' : 'audio/x-matroska'); + } elseif (isset($info['mime_type'])) { + unset($info['mime_type']); + } + + return true; + } + + private function parseEBML(&$info) { + // http://www.matroska.org/technical/specs/index.html#EBMLBasics + $this->current_offset = $info['avdataoffset']; + + while ($this->getEBMLelement($top_element, $info['avdataend'])) { + switch ($top_element['id']) { + + case EBML_ID_EBML: + $info['fileformat'] = 'matroska'; + $info['matroska']['header']['offset'] = $top_element['offset']; + $info['matroska']['header']['length'] = $top_element['length']; + + while ($this->getEBMLelement($element_data, $top_element['end'], true)) { + switch ($element_data['id']) { + + case EBML_ID_EBMLVERSION: + case EBML_ID_EBMLREADVERSION: + case EBML_ID_EBMLMAXIDLENGTH: + case EBML_ID_EBMLMAXSIZELENGTH: + case EBML_ID_DOCTYPEVERSION: + case EBML_ID_DOCTYPEREADVERSION: + $element_data['data'] = getid3_lib::BigEndian2Int($element_data['data']); + break; + + case EBML_ID_DOCTYPE: + $element_data['data'] = getid3_lib::trimNullByte($element_data['data']); + $info['matroska']['doctype'] = $element_data['data']; + break; + + case EBML_ID_CRC32: // not useful, ignore + $this->current_offset = $element_data['end']; + unset($element_data); + break; + + default: + $this->unhandledElement('header', __LINE__, $element_data); + } + if (!empty($element_data)) { + unset($element_data['offset'], $element_data['end']); + $info['matroska']['header']['elements'][] = $element_data; + } + } + break; + + case EBML_ID_SEGMENT: + $info['matroska']['segment'][0]['offset'] = $top_element['offset']; + $info['matroska']['segment'][0]['length'] = $top_element['length']; + + while ($this->getEBMLelement($element_data, $top_element['end'])) { + if ($element_data['id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required + $info['matroska']['segments'][] = $element_data; + } + switch ($element_data['id']) { + + case EBML_ID_SEEKHEAD: // Contains the position of other level 1 elements. + + while ($this->getEBMLelement($seek_entry, $element_data['end'])) { + switch ($seek_entry['id']) { + + case EBML_ID_SEEK: // Contains a single seek entry to an EBML element + while ($this->getEBMLelement($sub_seek_entry, $seek_entry['end'], true)) { + + switch ($sub_seek_entry['id']) { + + case EBML_ID_SEEKID: + $seek_entry['target_id'] = self::EBML2Int($sub_seek_entry['data']); + $seek_entry['target_name'] = self::EBMLidName($seek_entry['target_id']); + break; + + case EBML_ID_SEEKPOSITION: + $seek_entry['target_offset'] = $element_data['offset'] + getid3_lib::BigEndian2Int($sub_seek_entry['data']); + break; + + default: + $this->unhandledElement('seekhead.seek', __LINE__, $sub_seek_entry); } + } + + if ($seek_entry['target_id'] != EBML_ID_CLUSTER || !self::$hide_clusters) { // collect clusters only if required + $info['matroska']['seek'][] = $seek_entry; + } + break; + + default: + $this->unhandledElement('seekhead', __LINE__, $seek_entry); + } + } + break; + + case EBML_ID_TRACKS: // A top-level block of information with many tracks described. + $info['matroska']['tracks'] = $element_data; + + while ($this->getEBMLelement($track_entry, $element_data['end'])) { + switch ($track_entry['id']) { + + case EBML_ID_TRACKENTRY: //subelements: Describes a track with all elements. + + while ($this->getEBMLelement($subelement, $track_entry['end'], array(EBML_ID_VIDEO, EBML_ID_AUDIO, EBML_ID_CONTENTENCODINGS, EBML_ID_CODECPRIVATE))) { + switch ($subelement['id']) { + + case EBML_ID_TRACKNUMBER: + case EBML_ID_TRACKUID: + case EBML_ID_TRACKTYPE: + case EBML_ID_MINCACHE: + case EBML_ID_MAXCACHE: + case EBML_ID_MAXBLOCKADDITIONID: + case EBML_ID_DEFAULTDURATION: // nanoseconds per frame + $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); + break; + + case EBML_ID_TRACKTIMECODESCALE: + $track_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); + break; + + case EBML_ID_CODECID: + case EBML_ID_LANGUAGE: + case EBML_ID_NAME: + case EBML_ID_CODECNAME: + $track_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); + break; + + case EBML_ID_CODECPRIVATE: + $track_entry[$subelement['id_name']] = $this->readEBMLelementData($subelement['length'], true); + break; + + case EBML_ID_FLAGENABLED: + case EBML_ID_FLAGDEFAULT: + case EBML_ID_FLAGFORCED: + case EBML_ID_FLAGLACING: + case EBML_ID_CODECDECODEALL: + $track_entry[$subelement['id_name']] = (bool) getid3_lib::BigEndian2Int($subelement['data']); + break; + + case EBML_ID_VIDEO: + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { + switch ($sub_subelement['id']) { + + case EBML_ID_PIXELWIDTH: + case EBML_ID_PIXELHEIGHT: + case EBML_ID_PIXELCROPBOTTOM: + case EBML_ID_PIXELCROPTOP: + case EBML_ID_PIXELCROPLEFT: + case EBML_ID_PIXELCROPRIGHT: + case EBML_ID_DISPLAYWIDTH: + case EBML_ID_DISPLAYHEIGHT: + case EBML_ID_DISPLAYUNIT: + case EBML_ID_ASPECTRATIOTYPE: + case EBML_ID_STEREOMODE: + case EBML_ID_OLDSTEREOMODE: + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_FLAGINTERLACED: + $track_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_GAMMAVALUE: + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); + break; + + case EBML_ID_COLOURSPACE: + $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); + break; + + default: + $this->unhandledElement('track.video', __LINE__, $sub_subelement); + } + } + break; + + case EBML_ID_AUDIO: + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { + switch ($sub_subelement['id']) { + + case EBML_ID_CHANNELS: + case EBML_ID_BITDEPTH: + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_SAMPLINGFREQUENCY: + case EBML_ID_OUTPUTSAMPLINGFREQUENCY: + $track_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Float($sub_subelement['data']); + break; + + case EBML_ID_CHANNELPOSITIONS: + $track_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); + break; + + default: + $this->unhandledElement('track.audio', __LINE__, $sub_subelement); + } + } + break; + + case EBML_ID_CONTENTENCODINGS: + + while ($this->getEBMLelement($sub_subelement, $subelement['end'])) { + switch ($sub_subelement['id']) { + + case EBML_ID_CONTENTENCODING: + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CONTENTCOMPRESSION, EBML_ID_CONTENTENCRYPTION))) { + switch ($sub_sub_subelement['id']) { + + case EBML_ID_CONTENTENCODINGORDER: + case EBML_ID_CONTENTENCODINGSCOPE: + case EBML_ID_CONTENTENCODINGTYPE: + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); + break; + + case EBML_ID_CONTENTCOMPRESSION: + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + + case EBML_ID_CONTENTCOMPALGO: + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); + break; + + case EBML_ID_CONTENTCOMPSETTINGS: + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; + break; + + default: + $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); + } + } + break; + + case EBML_ID_CONTENTENCRYPTION: + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + + case EBML_ID_CONTENTENCALGO: + case EBML_ID_CONTENTSIGALGO: + case EBML_ID_CONTENTSIGHASHALGO: + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); + break; + + case EBML_ID_CONTENTENCKEYID: + case EBML_ID_CONTENTSIGNATURE: + case EBML_ID_CONTENTSIGKEYID: + $track_entry[$sub_subelement['id_name']][$sub_sub_subelement['id_name']][$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; + break; + + default: + $this->unhandledElement('track.contentencodings.contentencoding.contentcompression', __LINE__, $sub_sub_sub_subelement); + } + } + break; + + default: + $this->unhandledElement('track.contentencodings.contentencoding', __LINE__, $sub_sub_subelement); + } + } + break; + + default: + $this->unhandledElement('track.contentencodings', __LINE__, $sub_subelement); + } + } + break; + + default: + $this->unhandledElement('track', __LINE__, $subelement); + } + } + + $info['matroska']['tracks']['tracks'][] = $track_entry; + break; + + default: + $this->unhandledElement('tracks', __LINE__, $track_entry); + } + } + break; + + case EBML_ID_INFO: // Contains miscellaneous general information and statistics on the file. + $info_entry = array(); + + while ($this->getEBMLelement($subelement, $element_data['end'], true)) { + switch ($subelement['id']) { + + case EBML_ID_TIMECODESCALE: + $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); + break; + + case EBML_ID_DURATION: + $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Float($subelement['data']); + break; + + case EBML_ID_DATEUTC: + $info_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); + $info_entry[$subelement['id_name'].'_unix'] = self::EBMLdate2unix($info_entry[$subelement['id_name']]); + break; + + case EBML_ID_SEGMENTUID: + case EBML_ID_PREVUID: + case EBML_ID_NEXTUID: + $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); + break; + + case EBML_ID_SEGMENTFAMILY: + $info_entry[$subelement['id_name']][] = getid3_lib::trimNullByte($subelement['data']); + break; + + case EBML_ID_SEGMENTFILENAME: + case EBML_ID_PREVFILENAME: + case EBML_ID_NEXTFILENAME: + case EBML_ID_TITLE: + case EBML_ID_MUXINGAPP: + case EBML_ID_WRITINGAPP: + $info_entry[$subelement['id_name']] = getid3_lib::trimNullByte($subelement['data']); + $info['matroska']['comments'][strtolower($subelement['id_name'])][] = $info_entry[$subelement['id_name']]; + break; + + case EBML_ID_CHAPTERTRANSLATE: + $chaptertranslate_entry = array(); + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { + switch ($sub_subelement['id']) { + + case EBML_ID_CHAPTERTRANSLATEEDITIONUID: + $chaptertranslate_entry[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_CHAPTERTRANSLATECODEC: + $chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_CHAPTERTRANSLATEID: + $chaptertranslate_entry[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); + break; + + default: + $this->unhandledElement('info.chaptertranslate', __LINE__, $sub_subelement); + } + } + $info_entry[$subelement['id_name']] = $chaptertranslate_entry; + break; + + default: + $this->unhandledElement('info', __LINE__, $subelement); + } + } + $info['matroska']['info'][] = $info_entry; + break; + + case EBML_ID_CUES: // A top-level element to speed seeking access. All entries are local to the segment. Should be mandatory for non "live" streams. + if (self::$hide_clusters) { // do not parse cues if hide clusters is "ON" till they point to clusters anyway + $this->current_offset = $element_data['end']; + break; + } + $cues_entry = array(); + + while ($this->getEBMLelement($subelement, $element_data['end'])) { + switch ($subelement['id']) { + + case EBML_ID_CUEPOINT: + $cuepoint_entry = array(); + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CUETRACKPOSITIONS))) { + switch ($sub_subelement['id']) { + + case EBML_ID_CUETRACKPOSITIONS: + $cuetrackpositions_entry = array(); + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { + switch ($sub_sub_subelement['id']) { + + case EBML_ID_CUETRACK: + case EBML_ID_CUECLUSTERPOSITION: + case EBML_ID_CUEBLOCKNUMBER: + case EBML_ID_CUECODECSTATE: + $cuetrackpositions_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); + break; + + default: + $this->unhandledElement('cues.cuepoint.cuetrackpositions', __LINE__, $sub_sub_subelement); + } + } + $cuepoint_entry[$sub_subelement['id_name']][] = $cuetrackpositions_entry; + break; + + case EBML_ID_CUETIME: + $cuepoint_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + default: + $this->unhandledElement('cues.cuepoint', __LINE__, $sub_subelement); + } + } + $cues_entry[] = $cuepoint_entry; + break; + + default: + $this->unhandledElement('cues', __LINE__, $subelement); + } + } + $info['matroska']['cues'] = $cues_entry; + break; + + case EBML_ID_TAGS: // Element containing elements specific to Tracks/Chapters. + $tags_entry = array(); + + while ($this->getEBMLelement($subelement, $element_data['end'], false)) { + switch ($subelement['id']) { + + case EBML_ID_TAG: + $tag_entry = array(); + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], false)) { + switch ($sub_subelement['id']) { + + case EBML_ID_TARGETS: + $targets_entry = array(); + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], true)) { + switch ($sub_sub_subelement['id']) { + + case EBML_ID_TARGETTYPEVALUE: + $targets_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); + $targets_entry[strtolower($sub_sub_subelement['id_name']).'_long'] = self::TargetTypeValue($targets_entry[$sub_sub_subelement['id_name']]); + break; + + case EBML_ID_TARGETTYPE: + $targets_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; + break; + + case EBML_ID_TAGTRACKUID: + case EBML_ID_TAGEDITIONUID: + case EBML_ID_TAGCHAPTERUID: + case EBML_ID_TAGATTACHMENTUID: + $targets_entry[$sub_sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); + break; + + default: + $this->unhandledElement('tags.tag.targets', __LINE__, $sub_sub_subelement); + } + } + $tag_entry[$sub_subelement['id_name']] = $targets_entry; + break; + + case EBML_ID_SIMPLETAG: + $tag_entry[$sub_subelement['id_name']][] = $this->HandleEMBLSimpleTag($sub_subelement['end']); + break; + + default: + $this->unhandledElement('tags.tag', __LINE__, $sub_subelement); + } + } + $tags_entry[] = $tag_entry; + break; + + default: + $this->unhandledElement('tags', __LINE__, $subelement); + } + } + $info['matroska']['tags'] = $tags_entry; + break; + + case EBML_ID_ATTACHMENTS: // Contain attached files. + + while ($this->getEBMLelement($subelement, $element_data['end'])) { + switch ($subelement['id']) { + + case EBML_ID_ATTACHEDFILE: + $attachedfile_entry = array(); + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_FILEDATA))) { + switch ($sub_subelement['id']) { + + case EBML_ID_FILEDESCRIPTION: + case EBML_ID_FILENAME: + case EBML_ID_FILEMIMETYPE: + $attachedfile_entry[$sub_subelement['id_name']] = $sub_subelement['data']; + break; + + case EBML_ID_FILEDATA: + $attachedfile_entry['data_offset'] = $this->current_offset; + $attachedfile_entry['data_length'] = $sub_subelement['length']; + + $attachedfile_entry[$sub_subelement['id_name']] = $this->saveAttachment( + $attachedfile_entry['FileName'], + $attachedfile_entry['data_offset'], + $attachedfile_entry['data_length']); + + $this->current_offset = $sub_subelement['end']; + break; + + case EBML_ID_FILEUID: + $attachedfile_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + default: + $this->unhandledElement('attachments.attachedfile', __LINE__, $sub_subelement); + } + } + $info['matroska']['attachments'][] = $attachedfile_entry; + break; + + default: + $this->unhandledElement('attachments', __LINE__, $subelement); + } + } + break; + + case EBML_ID_CHAPTERS: + + while ($this->getEBMLelement($subelement, $element_data['end'])) { + switch ($subelement['id']) { + + case EBML_ID_EDITIONENTRY: + $editionentry_entry = array(); + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CHAPTERATOM))) { + switch ($sub_subelement['id']) { + + case EBML_ID_EDITIONUID: + $editionentry_entry[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_EDITIONFLAGHIDDEN: + case EBML_ID_EDITIONFLAGDEFAULT: + case EBML_ID_EDITIONFLAGORDERED: + $editionentry_entry[$sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_CHAPTERATOM: + $chapteratom_entry = array(); + + while ($this->getEBMLelement($sub_sub_subelement, $sub_subelement['end'], array(EBML_ID_CHAPTERTRACK, EBML_ID_CHAPTERDISPLAY))) { + switch ($sub_sub_subelement['id']) { + + case EBML_ID_CHAPTERSEGMENTUID: + case EBML_ID_CHAPTERSEGMENTEDITIONUID: + $chapteratom_entry[$sub_sub_subelement['id_name']] = $sub_sub_subelement['data']; + break; + + case EBML_ID_CHAPTERFLAGENABLED: + case EBML_ID_CHAPTERFLAGHIDDEN: + $chapteratom_entry[$sub_sub_subelement['id_name']] = (bool)getid3_lib::BigEndian2Int($sub_sub_subelement['data']); + break; + + case EBML_ID_CHAPTERUID: + case EBML_ID_CHAPTERTIMESTART: + case EBML_ID_CHAPTERTIMEEND: + $chapteratom_entry[$sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_subelement['data']); + break; + + case EBML_ID_CHAPTERTRACK: + $chaptertrack_entry = array(); + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + + case EBML_ID_CHAPTERTRACKNUMBER: + $chaptertrack_entry[$sub_sub_sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_sub_sub_subelement['data']); + break; + + default: + $this->unhandledElement('chapters.editionentry.chapteratom.chaptertrack', __LINE__, $sub_sub_sub_subelement); + } + } + $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chaptertrack_entry; + break; + + case EBML_ID_CHAPTERDISPLAY: + $chapterdisplay_entry = array(); + + while ($this->getEBMLelement($sub_sub_sub_subelement, $sub_sub_subelement['end'], true)) { + switch ($sub_sub_sub_subelement['id']) { + + case EBML_ID_CHAPSTRING: + case EBML_ID_CHAPLANGUAGE: + case EBML_ID_CHAPCOUNTRY: + $chapterdisplay_entry[$sub_sub_sub_subelement['id_name']] = $sub_sub_sub_subelement['data']; + break; + + default: + $this->unhandledElement('chapters.editionentry.chapteratom.chapterdisplay', __LINE__, $sub_sub_sub_subelement); + } + } + $chapteratom_entry[$sub_sub_subelement['id_name']][] = $chapterdisplay_entry; + break; + + default: + $this->unhandledElement('chapters.editionentry.chapteratom', __LINE__, $sub_sub_subelement); + } + } + $editionentry_entry[$sub_subelement['id_name']][] = $chapteratom_entry; + break; + + default: + $this->unhandledElement('chapters.editionentry', __LINE__, $sub_subelement); + } + } + $info['matroska']['chapters'][] = $editionentry_entry; + break; + + default: + $this->unhandledElement('chapters', __LINE__, $subelement); + } + } + break; + + case EBML_ID_CLUSTER: // The lower level element containing the (monolithic) Block structure. + $cluster_entry = array(); + + while ($this->getEBMLelement($subelement, $element_data['end'], array(EBML_ID_CLUSTERSILENTTRACKS, EBML_ID_CLUSTERBLOCKGROUP, EBML_ID_CLUSTERSIMPLEBLOCK))) { + switch ($subelement['id']) { + + case EBML_ID_CLUSTERTIMECODE: + case EBML_ID_CLUSTERPOSITION: + case EBML_ID_CLUSTERPREVSIZE: + $cluster_entry[$subelement['id_name']] = getid3_lib::BigEndian2Int($subelement['data']); + break; + + case EBML_ID_CLUSTERSILENTTRACKS: + $cluster_silent_tracks = array(); + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], true)) { + switch ($sub_subelement['id']) { + + case EBML_ID_CLUSTERSILENTTRACKNUMBER: + $cluster_silent_tracks[] = getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + default: + $this->unhandledElement('cluster.silenttracks', __LINE__, $sub_subelement); + } + } + $cluster_entry[$subelement['id_name']][] = $cluster_silent_tracks; + break; + + case EBML_ID_CLUSTERBLOCKGROUP: + $cluster_block_group = array('offset' => $this->current_offset); + + while ($this->getEBMLelement($sub_subelement, $subelement['end'], array(EBML_ID_CLUSTERBLOCK))) { + switch ($sub_subelement['id']) { + + case EBML_ID_CLUSTERBLOCK: + $cluster_block_group[$sub_subelement['id_name']] = $this->HandleEMBLClusterBlock($sub_subelement, EBML_ID_CLUSTERBLOCK, $info); + break; + + case EBML_ID_CLUSTERREFERENCEPRIORITY: // unsigned-int + case EBML_ID_CLUSTERBLOCKDURATION: // unsigned-int + $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::BigEndian2Int($sub_subelement['data']); + break; + + case EBML_ID_CLUSTERREFERENCEBLOCK: // signed-int + $cluster_block_group[$sub_subelement['id_name']][] = getid3_lib::BigEndian2Int($sub_subelement['data'], false, true); + break; + + case EBML_ID_CLUSTERCODECSTATE: + $cluster_block_group[$sub_subelement['id_name']] = getid3_lib::trimNullByte($sub_subelement['data']); + break; + + default: + $this->unhandledElement('clusters.blockgroup', __LINE__, $sub_subelement); + } + } + $cluster_entry[$subelement['id_name']][] = $cluster_block_group; + break; + + case EBML_ID_CLUSTERSIMPLEBLOCK: + $cluster_entry[$subelement['id_name']][] = $this->HandleEMBLClusterBlock($subelement, EBML_ID_CLUSTERSIMPLEBLOCK, $info); + break; + + default: + $this->unhandledElement('cluster', __LINE__, $subelement); + } + $this->current_offset = $subelement['end']; + } + if (!self::$hide_clusters) { + $info['matroska']['cluster'][] = $cluster_entry; + } + + // check to see if all the data we need exists already, if so, break out of the loop + if (!self::$parse_whole_file) { + if (isset($info['matroska']['info']) && is_array($info['matroska']['info'])) { + if (isset($info['matroska']['tracks']['tracks']) && is_array($info['matroska']['tracks']['tracks'])) { + if (count($info['matroska']['track_data_offsets']) == count($info['matroska']['tracks']['tracks'])) { + return; + } + } + } + } + break; + + default: + $this->unhandledElement('segment', __LINE__, $element_data); + } + } + break; + + default: + $this->unhandledElement('root', __LINE__, $top_element); + } + } + } + + private function EnsureBufferHasEnoughData($min_data=1024) { + if (($this->current_offset - $this->EBMLbuffer_offset) >= ($this->EBMLbuffer_length - $min_data)) { + $read_bytes = max($min_data, $this->getid3->fread_buffer_size()); + + try { + $this->fseek($this->current_offset); + $this->EBMLbuffer_offset = $this->current_offset; + $this->EBMLbuffer = $this->fread($read_bytes); + $this->EBMLbuffer_length = strlen($this->EBMLbuffer); + } catch (getid3_exception $e) { + $this->warning('EBML parser: '.$e->getMessage()); + return false; + } + + if ($this->EBMLbuffer_length == 0 && $this->feof()) { + return $this->error('EBML parser: ran out of file at offset '.$this->current_offset); + } + } + return true; + } + + private function readEBMLint() { + $actual_offset = $this->current_offset - $this->EBMLbuffer_offset; + + // get length of integer + $first_byte_int = ord($this->EBMLbuffer[$actual_offset]); + if (0x80 & $first_byte_int) { + $length = 1; + } elseif (0x40 & $first_byte_int) { + $length = 2; + } elseif (0x20 & $first_byte_int) { + $length = 3; + } elseif (0x10 & $first_byte_int) { + $length = 4; + } elseif (0x08 & $first_byte_int) { + $length = 5; + } elseif (0x04 & $first_byte_int) { + $length = 6; + } elseif (0x02 & $first_byte_int) { + $length = 7; + } elseif (0x01 & $first_byte_int) { + $length = 8; + } else { + throw new Exception('invalid EBML integer (leading 0x00) at '.$this->current_offset); + } + + // read + $int_value = self::EBML2Int(substr($this->EBMLbuffer, $actual_offset, $length)); + $this->current_offset += $length; + + return $int_value; + } + + private function readEBMLelementData($length, $check_buffer=false) { + if ($check_buffer && !$this->EnsureBufferHasEnoughData($length)) { + return false; + } + $data = substr($this->EBMLbuffer, $this->current_offset - $this->EBMLbuffer_offset, $length); + $this->current_offset += $length; + return $data; + } + + private function getEBMLelement(&$element, $parent_end, $get_data=false) { + if ($this->current_offset >= $parent_end) { + return false; + } + + if (!$this->EnsureBufferHasEnoughData()) { + $this->current_offset = PHP_INT_MAX; // do not exit parser right now, allow to finish current loop to gather maximum information + return false; + } + + $element = array(); + + // set offset + $element['offset'] = $this->current_offset; + + // get ID + $element['id'] = $this->readEBMLint(); + + // get name + $element['id_name'] = self::EBMLidName($element['id']); + + // get length + $element['length'] = $this->readEBMLint(); + + // get end offset + $element['end'] = $this->current_offset + $element['length']; + + // get raw data + $dont_parse = (in_array($element['id'], $this->unuseful_elements) || $element['id_name'] == dechex($element['id'])); + if (($get_data === true || (is_array($get_data) && !in_array($element['id'], $get_data))) && !$dont_parse) { + $element['data'] = $this->readEBMLelementData($element['length'], $element); + } + + return true; + } + + private function unhandledElement($type, $line, $element) { + // warn only about unknown and missed elements, not about unuseful + if (!in_array($element['id'], $this->unuseful_elements)) { + $this->warning('Unhandled '.$type.' element ['.basename(__FILE__).':'.$line.'] ('.$element['id'].'::'.$element['id_name'].' ['.$element['length'].' bytes]) at '.$element['offset']); + } + + // increase offset for unparsed elements + if (!isset($element['data'])) { + $this->current_offset = $element['end']; + } + } + + private function ExtractCommentsSimpleTag($SimpleTagArray) { + if (!empty($SimpleTagArray['SimpleTag'])) { + foreach ($SimpleTagArray['SimpleTag'] as $SimpleTagKey => $SimpleTagData) { + if (!empty($SimpleTagData['TagName']) && !empty($SimpleTagData['TagString'])) { + $this->getid3->info['matroska']['comments'][strtolower($SimpleTagData['TagName'])][] = $SimpleTagData['TagString']; + } + if (!empty($SimpleTagData['SimpleTag'])) { + $this->ExtractCommentsSimpleTag($SimpleTagData); + } + } + } + + return true; + } + + private function HandleEMBLSimpleTag($parent_end) { + $simpletag_entry = array(); + + while ($this->getEBMLelement($element, $parent_end, array(EBML_ID_SIMPLETAG))) { + switch ($element['id']) { + + case EBML_ID_TAGNAME: + case EBML_ID_TAGLANGUAGE: + case EBML_ID_TAGSTRING: + case EBML_ID_TAGBINARY: + $simpletag_entry[$element['id_name']] = $element['data']; + break; + + case EBML_ID_SIMPLETAG: + $simpletag_entry[$element['id_name']][] = $this->HandleEMBLSimpleTag($element['end']); + break; + + case EBML_ID_TAGDEFAULT: + $simpletag_entry[$element['id_name']] = (bool)getid3_lib::BigEndian2Int($element['data']); + break; + + default: + $this->unhandledElement('tag.simpletag', __LINE__, $element); + } + } + + return $simpletag_entry; + } + + private function HandleEMBLClusterBlock($element, $block_type, &$info) { + // http://www.matroska.org/technical/specs/index.html#block_structure + // http://www.matroska.org/technical/specs/index.html#simpleblock_structure + + $block_data = array(); + $block_data['tracknumber'] = $this->readEBMLint(); + $block_data['timecode'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(2), false, true); + $block_data['flags_raw'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); + + if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { + $block_data['flags']['keyframe'] = (($block_data['flags_raw'] & 0x80) >> 7); + //$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0x70) >> 4); + } + else { + //$block_data['flags']['reserved1'] = (($block_data['flags_raw'] & 0xF0) >> 4); + } + $block_data['flags']['invisible'] = (bool)(($block_data['flags_raw'] & 0x08) >> 3); + $block_data['flags']['lacing'] = (($block_data['flags_raw'] & 0x06) >> 1); // 00=no lacing; 01=Xiph lacing; 11=EBML lacing; 10=fixed-size lacing + if ($block_type == EBML_ID_CLUSTERSIMPLEBLOCK) { + $block_data['flags']['discardable'] = (($block_data['flags_raw'] & 0x01)); + } + else { + //$block_data['flags']['reserved2'] = (($block_data['flags_raw'] & 0x01) >> 0); + } + $block_data['flags']['lacing_type'] = self::BlockLacingType($block_data['flags']['lacing']); + + // Lace (when lacing bit is set) + if ($block_data['flags']['lacing'] > 0) { + $block_data['lace_frames'] = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)) + 1; // Number of frames in the lace-1 (uint8) + if ($block_data['flags']['lacing'] != 0x02) { + for ($i = 1; $i < $block_data['lace_frames']; $i ++) { // Lace-coded size of each frame of the lace, except for the last one (multiple uint8). *This is not used with Fixed-size lacing as it is calculated automatically from (total size of lace) / (number of frames in lace). + if ($block_data['flags']['lacing'] == 0x03) { // EBML lacing + $block_data['lace_frames_size'][$i] = $this->readEBMLint(); // TODO: read size correctly, calc size for the last frame. For now offsets are deteminded OK with readEBMLint() and that's the most important thing. + } + else { // Xiph lacing + $block_data['lace_frames_size'][$i] = 0; + do { + $size = getid3_lib::BigEndian2Int($this->readEBMLelementData(1)); + $block_data['lace_frames_size'][$i] += $size; + } + while ($size == 255); + } + } + if ($block_data['flags']['lacing'] == 0x01) { // calc size of the last frame only for Xiph lacing, till EBML sizes are now anyway determined incorrectly + $block_data['lace_frames_size'][] = $element['end'] - $this->current_offset - array_sum($block_data['lace_frames_size']); + } + } + } + + if (!isset($info['matroska']['track_data_offsets'][$block_data['tracknumber']])) { + $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['offset'] = $this->current_offset; + $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length'] = $element['end'] - $this->current_offset; + //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] = 0; + } + //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['total_length'] += $info['matroska']['track_data_offsets'][$block_data['tracknumber']]['length']; + //$info['matroska']['track_data_offsets'][$block_data['tracknumber']]['duration'] = $block_data['timecode'] * ((isset($info['matroska']['info'][0]['TimecodeScale']) ? $info['matroska']['info'][0]['TimecodeScale'] : 1000000) / 1000000000); + + // set offset manually + $this->current_offset = $element['end']; + + return $block_data; + } + + private static function EBML2Int($EBMLstring) { + // http://matroska.org/specs/ + + // Element ID coded with an UTF-8 like system: + // 1xxx xxxx - Class A IDs (2^7 -2 possible values) (base 0x8X) + // 01xx xxxx xxxx xxxx - Class B IDs (2^14-2 possible values) (base 0x4X 0xXX) + // 001x xxxx xxxx xxxx xxxx xxxx - Class C IDs (2^21-2 possible values) (base 0x2X 0xXX 0xXX) + // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - Class D IDs (2^28-2 possible values) (base 0x1X 0xXX 0xXX 0xXX) + // Values with all x at 0 and 1 are reserved (hence the -2). + + // Data size, in octets, is also coded with an UTF-8 like system : + // 1xxx xxxx - value 0 to 2^7-2 + // 01xx xxxx xxxx xxxx - value 0 to 2^14-2 + // 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2 + // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2 + // 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2 + // 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2 + // 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2 + // 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2 + + $first_byte_int = ord($EBMLstring[0]); + if (0x80 & $first_byte_int) { + $EBMLstring[0] = chr($first_byte_int & 0x7F); + } elseif (0x40 & $first_byte_int) { + $EBMLstring[0] = chr($first_byte_int & 0x3F); + } elseif (0x20 & $first_byte_int) { + $EBMLstring[0] = chr($first_byte_int & 0x1F); + } elseif (0x10 & $first_byte_int) { + $EBMLstring[0] = chr($first_byte_int & 0x0F); + } elseif (0x08 & $first_byte_int) { + $EBMLstring[0] = chr($first_byte_int & 0x07); + } elseif (0x04 & $first_byte_int) { + $EBMLstring[0] = chr($first_byte_int & 0x03); + } elseif (0x02 & $first_byte_int) { + $EBMLstring[0] = chr($first_byte_int & 0x01); + } elseif (0x01 & $first_byte_int) { + $EBMLstring[0] = chr($first_byte_int & 0x00); + } + + return getid3_lib::BigEndian2Int($EBMLstring); + } + + private static function EBMLdate2unix($EBMLdatestamp) { + // Date - signed 8 octets integer in nanoseconds with 0 indicating the precise beginning of the millennium (at 2001-01-01T00:00:00,000000000 UTC) + // 978307200 == mktime(0, 0, 0, 1, 1, 2001) == January 1, 2001 12:00:00am UTC + return round(($EBMLdatestamp / 1000000000) + 978307200); + } + + public static function TargetTypeValue($target_type) { + // http://www.matroska.org/technical/specs/tagging/index.html + static $TargetTypeValue = array(); + if (empty($TargetTypeValue)) { + $TargetTypeValue[10] = 'A: ~ V:shot'; // the lowest hierarchy found in music or movies + $TargetTypeValue[20] = 'A:subtrack/part/movement ~ V:scene'; // corresponds to parts of a track for audio (like a movement) + $TargetTypeValue[30] = 'A:track/song ~ V:chapter'; // the common parts of an album or a movie + $TargetTypeValue[40] = 'A:part/session ~ V:part/session'; // when an album or episode has different logical parts + $TargetTypeValue[50] = 'A:album/opera/concert ~ V:movie/episode/concert'; // the most common grouping level of music and video (equals to an episode for TV series) + $TargetTypeValue[60] = 'A:edition/issue/volume/opus ~ V:season/sequel/volume'; // a list of lower levels grouped together + $TargetTypeValue[70] = 'A:collection ~ V:collection'; // the high hierarchy consisting of many different lower items + } + return (isset($TargetTypeValue[$target_type]) ? $TargetTypeValue[$target_type] : $target_type); + } + + public static function BlockLacingType($lacingtype) { + // http://matroska.org/technical/specs/index.html#block_structure + static $BlockLacingType = array(); + if (empty($BlockLacingType)) { + $BlockLacingType[0x00] = 'no lacing'; + $BlockLacingType[0x01] = 'Xiph lacing'; + $BlockLacingType[0x02] = 'fixed-size lacing'; + $BlockLacingType[0x03] = 'EBML lacing'; + } + return (isset($BlockLacingType[$lacingtype]) ? $BlockLacingType[$lacingtype] : $lacingtype); + } + + public static function CodecIDtoCommonName($codecid) { + // http://www.matroska.org/technical/specs/codecid/index.html + static $CodecIDlist = array(); + if (empty($CodecIDlist)) { + $CodecIDlist['A_AAC'] = 'aac'; + $CodecIDlist['A_AAC/MPEG2/LC'] = 'aac'; + $CodecIDlist['A_AC3'] = 'ac3'; + $CodecIDlist['A_DTS'] = 'dts'; + $CodecIDlist['A_FLAC'] = 'flac'; + $CodecIDlist['A_MPEG/L1'] = 'mp1'; + $CodecIDlist['A_MPEG/L2'] = 'mp2'; + $CodecIDlist['A_MPEG/L3'] = 'mp3'; + $CodecIDlist['A_PCM/INT/LIT'] = 'pcm'; // PCM Integer Little Endian + $CodecIDlist['A_PCM/INT/BIG'] = 'pcm'; // PCM Integer Big Endian + $CodecIDlist['A_QUICKTIME/QDMC'] = 'quicktime'; // Quicktime: QDesign Music + $CodecIDlist['A_QUICKTIME/QDM2'] = 'quicktime'; // Quicktime: QDesign Music v2 + $CodecIDlist['A_VORBIS'] = 'vorbis'; + $CodecIDlist['V_MPEG1'] = 'mpeg'; + $CodecIDlist['V_THEORA'] = 'theora'; + $CodecIDlist['V_REAL/RV40'] = 'real'; + $CodecIDlist['V_REAL/RV10'] = 'real'; + $CodecIDlist['V_REAL/RV20'] = 'real'; + $CodecIDlist['V_REAL/RV30'] = 'real'; + $CodecIDlist['V_QUICKTIME'] = 'quicktime'; // Quicktime + $CodecIDlist['V_MPEG4/ISO/AP'] = 'mpeg4'; + $CodecIDlist['V_MPEG4/ISO/ASP'] = 'mpeg4'; + $CodecIDlist['V_MPEG4/ISO/AVC'] = 'h264'; + $CodecIDlist['V_MPEG4/ISO/SP'] = 'mpeg4'; + $CodecIDlist['V_VP8'] = 'vp8'; + $CodecIDlist['V_MS/VFW/FOURCC'] = 'riff'; + $CodecIDlist['A_MS/ACM'] = 'riff'; + } + return (isset($CodecIDlist[$codecid]) ? $CodecIDlist[$codecid] : $codecid); + } + + private static function EBMLidName($value) { + static $EBMLidList = array(); + if (empty($EBMLidList)) { + $EBMLidList[EBML_ID_ASPECTRATIOTYPE] = 'AspectRatioType'; + $EBMLidList[EBML_ID_ATTACHEDFILE] = 'AttachedFile'; + $EBMLidList[EBML_ID_ATTACHMENTLINK] = 'AttachmentLink'; + $EBMLidList[EBML_ID_ATTACHMENTS] = 'Attachments'; + $EBMLidList[EBML_ID_AUDIO] = 'Audio'; + $EBMLidList[EBML_ID_BITDEPTH] = 'BitDepth'; + $EBMLidList[EBML_ID_CHANNELPOSITIONS] = 'ChannelPositions'; + $EBMLidList[EBML_ID_CHANNELS] = 'Channels'; + $EBMLidList[EBML_ID_CHAPCOUNTRY] = 'ChapCountry'; + $EBMLidList[EBML_ID_CHAPLANGUAGE] = 'ChapLanguage'; + $EBMLidList[EBML_ID_CHAPPROCESS] = 'ChapProcess'; + $EBMLidList[EBML_ID_CHAPPROCESSCODECID] = 'ChapProcessCodecID'; + $EBMLidList[EBML_ID_CHAPPROCESSCOMMAND] = 'ChapProcessCommand'; + $EBMLidList[EBML_ID_CHAPPROCESSDATA] = 'ChapProcessData'; + $EBMLidList[EBML_ID_CHAPPROCESSPRIVATE] = 'ChapProcessPrivate'; + $EBMLidList[EBML_ID_CHAPPROCESSTIME] = 'ChapProcessTime'; + $EBMLidList[EBML_ID_CHAPSTRING] = 'ChapString'; + $EBMLidList[EBML_ID_CHAPTERATOM] = 'ChapterAtom'; + $EBMLidList[EBML_ID_CHAPTERDISPLAY] = 'ChapterDisplay'; + $EBMLidList[EBML_ID_CHAPTERFLAGENABLED] = 'ChapterFlagEnabled'; + $EBMLidList[EBML_ID_CHAPTERFLAGHIDDEN] = 'ChapterFlagHidden'; + $EBMLidList[EBML_ID_CHAPTERPHYSICALEQUIV] = 'ChapterPhysicalEquiv'; + $EBMLidList[EBML_ID_CHAPTERS] = 'Chapters'; + $EBMLidList[EBML_ID_CHAPTERSEGMENTEDITIONUID] = 'ChapterSegmentEditionUID'; + $EBMLidList[EBML_ID_CHAPTERSEGMENTUID] = 'ChapterSegmentUID'; + $EBMLidList[EBML_ID_CHAPTERTIMEEND] = 'ChapterTimeEnd'; + $EBMLidList[EBML_ID_CHAPTERTIMESTART] = 'ChapterTimeStart'; + $EBMLidList[EBML_ID_CHAPTERTRACK] = 'ChapterTrack'; + $EBMLidList[EBML_ID_CHAPTERTRACKNUMBER] = 'ChapterTrackNumber'; + $EBMLidList[EBML_ID_CHAPTERTRANSLATE] = 'ChapterTranslate'; + $EBMLidList[EBML_ID_CHAPTERTRANSLATECODEC] = 'ChapterTranslateCodec'; + $EBMLidList[EBML_ID_CHAPTERTRANSLATEEDITIONUID] = 'ChapterTranslateEditionUID'; + $EBMLidList[EBML_ID_CHAPTERTRANSLATEID] = 'ChapterTranslateID'; + $EBMLidList[EBML_ID_CHAPTERUID] = 'ChapterUID'; + $EBMLidList[EBML_ID_CLUSTER] = 'Cluster'; + $EBMLidList[EBML_ID_CLUSTERBLOCK] = 'ClusterBlock'; + $EBMLidList[EBML_ID_CLUSTERBLOCKADDID] = 'ClusterBlockAddID'; + $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONAL] = 'ClusterBlockAdditional'; + $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONID] = 'ClusterBlockAdditionID'; + $EBMLidList[EBML_ID_CLUSTERBLOCKADDITIONS] = 'ClusterBlockAdditions'; + $EBMLidList[EBML_ID_CLUSTERBLOCKDURATION] = 'ClusterBlockDuration'; + $EBMLidList[EBML_ID_CLUSTERBLOCKGROUP] = 'ClusterBlockGroup'; + $EBMLidList[EBML_ID_CLUSTERBLOCKMORE] = 'ClusterBlockMore'; + $EBMLidList[EBML_ID_CLUSTERBLOCKVIRTUAL] = 'ClusterBlockVirtual'; + $EBMLidList[EBML_ID_CLUSTERCODECSTATE] = 'ClusterCodecState'; + $EBMLidList[EBML_ID_CLUSTERDELAY] = 'ClusterDelay'; + $EBMLidList[EBML_ID_CLUSTERDURATION] = 'ClusterDuration'; + $EBMLidList[EBML_ID_CLUSTERENCRYPTEDBLOCK] = 'ClusterEncryptedBlock'; + $EBMLidList[EBML_ID_CLUSTERFRAMENUMBER] = 'ClusterFrameNumber'; + $EBMLidList[EBML_ID_CLUSTERLACENUMBER] = 'ClusterLaceNumber'; + $EBMLidList[EBML_ID_CLUSTERPOSITION] = 'ClusterPosition'; + $EBMLidList[EBML_ID_CLUSTERPREVSIZE] = 'ClusterPrevSize'; + $EBMLidList[EBML_ID_CLUSTERREFERENCEBLOCK] = 'ClusterReferenceBlock'; + $EBMLidList[EBML_ID_CLUSTERREFERENCEPRIORITY] = 'ClusterReferencePriority'; + $EBMLidList[EBML_ID_CLUSTERREFERENCEVIRTUAL] = 'ClusterReferenceVirtual'; + $EBMLidList[EBML_ID_CLUSTERSILENTTRACKNUMBER] = 'ClusterSilentTrackNumber'; + $EBMLidList[EBML_ID_CLUSTERSILENTTRACKS] = 'ClusterSilentTracks'; + $EBMLidList[EBML_ID_CLUSTERSIMPLEBLOCK] = 'ClusterSimpleBlock'; + $EBMLidList[EBML_ID_CLUSTERTIMECODE] = 'ClusterTimecode'; + $EBMLidList[EBML_ID_CLUSTERTIMESLICE] = 'ClusterTimeSlice'; + $EBMLidList[EBML_ID_CODECDECODEALL] = 'CodecDecodeAll'; + $EBMLidList[EBML_ID_CODECDOWNLOADURL] = 'CodecDownloadURL'; + $EBMLidList[EBML_ID_CODECID] = 'CodecID'; + $EBMLidList[EBML_ID_CODECINFOURL] = 'CodecInfoURL'; + $EBMLidList[EBML_ID_CODECNAME] = 'CodecName'; + $EBMLidList[EBML_ID_CODECPRIVATE] = 'CodecPrivate'; + $EBMLidList[EBML_ID_CODECSETTINGS] = 'CodecSettings'; + $EBMLidList[EBML_ID_COLOURSPACE] = 'ColourSpace'; + $EBMLidList[EBML_ID_CONTENTCOMPALGO] = 'ContentCompAlgo'; + $EBMLidList[EBML_ID_CONTENTCOMPRESSION] = 'ContentCompression'; + $EBMLidList[EBML_ID_CONTENTCOMPSETTINGS] = 'ContentCompSettings'; + $EBMLidList[EBML_ID_CONTENTENCALGO] = 'ContentEncAlgo'; + $EBMLidList[EBML_ID_CONTENTENCKEYID] = 'ContentEncKeyID'; + $EBMLidList[EBML_ID_CONTENTENCODING] = 'ContentEncoding'; + $EBMLidList[EBML_ID_CONTENTENCODINGORDER] = 'ContentEncodingOrder'; + $EBMLidList[EBML_ID_CONTENTENCODINGS] = 'ContentEncodings'; + $EBMLidList[EBML_ID_CONTENTENCODINGSCOPE] = 'ContentEncodingScope'; + $EBMLidList[EBML_ID_CONTENTENCODINGTYPE] = 'ContentEncodingType'; + $EBMLidList[EBML_ID_CONTENTENCRYPTION] = 'ContentEncryption'; + $EBMLidList[EBML_ID_CONTENTSIGALGO] = 'ContentSigAlgo'; + $EBMLidList[EBML_ID_CONTENTSIGHASHALGO] = 'ContentSigHashAlgo'; + $EBMLidList[EBML_ID_CONTENTSIGKEYID] = 'ContentSigKeyID'; + $EBMLidList[EBML_ID_CONTENTSIGNATURE] = 'ContentSignature'; + $EBMLidList[EBML_ID_CRC32] = 'CRC32'; + $EBMLidList[EBML_ID_CUEBLOCKNUMBER] = 'CueBlockNumber'; + $EBMLidList[EBML_ID_CUECLUSTERPOSITION] = 'CueClusterPosition'; + $EBMLidList[EBML_ID_CUECODECSTATE] = 'CueCodecState'; + $EBMLidList[EBML_ID_CUEPOINT] = 'CuePoint'; + $EBMLidList[EBML_ID_CUEREFCLUSTER] = 'CueRefCluster'; + $EBMLidList[EBML_ID_CUEREFCODECSTATE] = 'CueRefCodecState'; + $EBMLidList[EBML_ID_CUEREFERENCE] = 'CueReference'; + $EBMLidList[EBML_ID_CUEREFNUMBER] = 'CueRefNumber'; + $EBMLidList[EBML_ID_CUEREFTIME] = 'CueRefTime'; + $EBMLidList[EBML_ID_CUES] = 'Cues'; + $EBMLidList[EBML_ID_CUETIME] = 'CueTime'; + $EBMLidList[EBML_ID_CUETRACK] = 'CueTrack'; + $EBMLidList[EBML_ID_CUETRACKPOSITIONS] = 'CueTrackPositions'; + $EBMLidList[EBML_ID_DATEUTC] = 'DateUTC'; + $EBMLidList[EBML_ID_DEFAULTDURATION] = 'DefaultDuration'; + $EBMLidList[EBML_ID_DISPLAYHEIGHT] = 'DisplayHeight'; + $EBMLidList[EBML_ID_DISPLAYUNIT] = 'DisplayUnit'; + $EBMLidList[EBML_ID_DISPLAYWIDTH] = 'DisplayWidth'; + $EBMLidList[EBML_ID_DOCTYPE] = 'DocType'; + $EBMLidList[EBML_ID_DOCTYPEREADVERSION] = 'DocTypeReadVersion'; + $EBMLidList[EBML_ID_DOCTYPEVERSION] = 'DocTypeVersion'; + $EBMLidList[EBML_ID_DURATION] = 'Duration'; + $EBMLidList[EBML_ID_EBML] = 'EBML'; + $EBMLidList[EBML_ID_EBMLMAXIDLENGTH] = 'EBMLMaxIDLength'; + $EBMLidList[EBML_ID_EBMLMAXSIZELENGTH] = 'EBMLMaxSizeLength'; + $EBMLidList[EBML_ID_EBMLREADVERSION] = 'EBMLReadVersion'; + $EBMLidList[EBML_ID_EBMLVERSION] = 'EBMLVersion'; + $EBMLidList[EBML_ID_EDITIONENTRY] = 'EditionEntry'; + $EBMLidList[EBML_ID_EDITIONFLAGDEFAULT] = 'EditionFlagDefault'; + $EBMLidList[EBML_ID_EDITIONFLAGHIDDEN] = 'EditionFlagHidden'; + $EBMLidList[EBML_ID_EDITIONFLAGORDERED] = 'EditionFlagOrdered'; + $EBMLidList[EBML_ID_EDITIONUID] = 'EditionUID'; + $EBMLidList[EBML_ID_FILEDATA] = 'FileData'; + $EBMLidList[EBML_ID_FILEDESCRIPTION] = 'FileDescription'; + $EBMLidList[EBML_ID_FILEMIMETYPE] = 'FileMimeType'; + $EBMLidList[EBML_ID_FILENAME] = 'FileName'; + $EBMLidList[EBML_ID_FILEREFERRAL] = 'FileReferral'; + $EBMLidList[EBML_ID_FILEUID] = 'FileUID'; + $EBMLidList[EBML_ID_FLAGDEFAULT] = 'FlagDefault'; + $EBMLidList[EBML_ID_FLAGENABLED] = 'FlagEnabled'; + $EBMLidList[EBML_ID_FLAGFORCED] = 'FlagForced'; + $EBMLidList[EBML_ID_FLAGINTERLACED] = 'FlagInterlaced'; + $EBMLidList[EBML_ID_FLAGLACING] = 'FlagLacing'; + $EBMLidList[EBML_ID_GAMMAVALUE] = 'GammaValue'; + $EBMLidList[EBML_ID_INFO] = 'Info'; + $EBMLidList[EBML_ID_LANGUAGE] = 'Language'; + $EBMLidList[EBML_ID_MAXBLOCKADDITIONID] = 'MaxBlockAdditionID'; + $EBMLidList[EBML_ID_MAXCACHE] = 'MaxCache'; + $EBMLidList[EBML_ID_MINCACHE] = 'MinCache'; + $EBMLidList[EBML_ID_MUXINGAPP] = 'MuxingApp'; + $EBMLidList[EBML_ID_NAME] = 'Name'; + $EBMLidList[EBML_ID_NEXTFILENAME] = 'NextFilename'; + $EBMLidList[EBML_ID_NEXTUID] = 'NextUID'; + $EBMLidList[EBML_ID_OUTPUTSAMPLINGFREQUENCY] = 'OutputSamplingFrequency'; + $EBMLidList[EBML_ID_PIXELCROPBOTTOM] = 'PixelCropBottom'; + $EBMLidList[EBML_ID_PIXELCROPLEFT] = 'PixelCropLeft'; + $EBMLidList[EBML_ID_PIXELCROPRIGHT] = 'PixelCropRight'; + $EBMLidList[EBML_ID_PIXELCROPTOP] = 'PixelCropTop'; + $EBMLidList[EBML_ID_PIXELHEIGHT] = 'PixelHeight'; + $EBMLidList[EBML_ID_PIXELWIDTH] = 'PixelWidth'; + $EBMLidList[EBML_ID_PREVFILENAME] = 'PrevFilename'; + $EBMLidList[EBML_ID_PREVUID] = 'PrevUID'; + $EBMLidList[EBML_ID_SAMPLINGFREQUENCY] = 'SamplingFrequency'; + $EBMLidList[EBML_ID_SEEK] = 'Seek'; + $EBMLidList[EBML_ID_SEEKHEAD] = 'SeekHead'; + $EBMLidList[EBML_ID_SEEKID] = 'SeekID'; + $EBMLidList[EBML_ID_SEEKPOSITION] = 'SeekPosition'; + $EBMLidList[EBML_ID_SEGMENT] = 'Segment'; + $EBMLidList[EBML_ID_SEGMENTFAMILY] = 'SegmentFamily'; + $EBMLidList[EBML_ID_SEGMENTFILENAME] = 'SegmentFilename'; + $EBMLidList[EBML_ID_SEGMENTUID] = 'SegmentUID'; + $EBMLidList[EBML_ID_SIMPLETAG] = 'SimpleTag'; + $EBMLidList[EBML_ID_CLUSTERSLICES] = 'ClusterSlices'; + $EBMLidList[EBML_ID_STEREOMODE] = 'StereoMode'; + $EBMLidList[EBML_ID_OLDSTEREOMODE] = 'OldStereoMode'; + $EBMLidList[EBML_ID_TAG] = 'Tag'; + $EBMLidList[EBML_ID_TAGATTACHMENTUID] = 'TagAttachmentUID'; + $EBMLidList[EBML_ID_TAGBINARY] = 'TagBinary'; + $EBMLidList[EBML_ID_TAGCHAPTERUID] = 'TagChapterUID'; + $EBMLidList[EBML_ID_TAGDEFAULT] = 'TagDefault'; + $EBMLidList[EBML_ID_TAGEDITIONUID] = 'TagEditionUID'; + $EBMLidList[EBML_ID_TAGLANGUAGE] = 'TagLanguage'; + $EBMLidList[EBML_ID_TAGNAME] = 'TagName'; + $EBMLidList[EBML_ID_TAGTRACKUID] = 'TagTrackUID'; + $EBMLidList[EBML_ID_TAGS] = 'Tags'; + $EBMLidList[EBML_ID_TAGSTRING] = 'TagString'; + $EBMLidList[EBML_ID_TARGETS] = 'Targets'; + $EBMLidList[EBML_ID_TARGETTYPE] = 'TargetType'; + $EBMLidList[EBML_ID_TARGETTYPEVALUE] = 'TargetTypeValue'; + $EBMLidList[EBML_ID_TIMECODESCALE] = 'TimecodeScale'; + $EBMLidList[EBML_ID_TITLE] = 'Title'; + $EBMLidList[EBML_ID_TRACKENTRY] = 'TrackEntry'; + $EBMLidList[EBML_ID_TRACKNUMBER] = 'TrackNumber'; + $EBMLidList[EBML_ID_TRACKOFFSET] = 'TrackOffset'; + $EBMLidList[EBML_ID_TRACKOVERLAY] = 'TrackOverlay'; + $EBMLidList[EBML_ID_TRACKS] = 'Tracks'; + $EBMLidList[EBML_ID_TRACKTIMECODESCALE] = 'TrackTimecodeScale'; + $EBMLidList[EBML_ID_TRACKTRANSLATE] = 'TrackTranslate'; + $EBMLidList[EBML_ID_TRACKTRANSLATECODEC] = 'TrackTranslateCodec'; + $EBMLidList[EBML_ID_TRACKTRANSLATEEDITIONUID] = 'TrackTranslateEditionUID'; + $EBMLidList[EBML_ID_TRACKTRANSLATETRACKID] = 'TrackTranslateTrackID'; + $EBMLidList[EBML_ID_TRACKTYPE] = 'TrackType'; + $EBMLidList[EBML_ID_TRACKUID] = 'TrackUID'; + $EBMLidList[EBML_ID_VIDEO] = 'Video'; + $EBMLidList[EBML_ID_VOID] = 'Void'; + $EBMLidList[EBML_ID_WRITINGAPP] = 'WritingApp'; + } + + return (isset($EBMLidList[$value]) ? $EBMLidList[$value] : dechex($value)); + } + + public static function displayUnit($value) { + // http://www.matroska.org/technical/specs/index.html#DisplayUnit + static $units = array( + 0 => 'pixels', + 1 => 'centimeters', + 2 => 'inches', + 3 => 'Display Aspect Ratio'); + + return (isset($units[$value]) ? $units[$value] : 'unknown'); + } + + private static function getDefaultStreamInfo($streams) + { + foreach (array_reverse($streams) as $stream) { + if ($stream['default']) { + break; + } + } + + $unset = array('default', 'name'); + foreach ($unset as $u) { + if (isset($stream[$u])) { + unset($stream[$u]); + } + } + + $info = $stream; + $info['streams'] = $streams; + + return $info; + } + +} diff --git a/extensions/TimedMediaHandler/libs/getid3/module.audio-video.mpeg.php b/extensions/TimedMediaHandler/libs/getid3/module.audio-video.mpeg.php new file mode 100644 index 00000000..999c5d9a --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/module.audio-video.mpeg.php @@ -0,0 +1,296 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// 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); + +define('GETID3_MPEG_VIDEO_PICTURE_START', "\x00\x00\x01\x00"); +define('GETID3_MPEG_VIDEO_USER_DATA_START', "\x00\x00\x01\xB2"); +define('GETID3_MPEG_VIDEO_SEQUENCE_HEADER', "\x00\x00\x01\xB3"); +define('GETID3_MPEG_VIDEO_SEQUENCE_ERROR', "\x00\x00\x01\xB4"); +define('GETID3_MPEG_VIDEO_EXTENSION_START', "\x00\x00\x01\xB5"); +define('GETID3_MPEG_VIDEO_SEQUENCE_END', "\x00\x00\x01\xB7"); +define('GETID3_MPEG_VIDEO_GROUP_START', "\x00\x00\x01\xB8"); +define('GETID3_MPEG_AUDIO_START', "\x00\x00\x01\xC0"); + + +class getid3_mpeg extends getid3_handler +{ + + public function Analyze() { + $info = &$this->getid3->info; + + if ($info['avdataend'] <= $info['avdataoffset']) { + $info['error'][] = '"avdataend" ('.$info['avdataend'].') is unexpectedly less-than-or-equal-to "avdataoffset" ('.$info['avdataoffset'].')'; + return false; + } + $info['fileformat'] = 'mpeg'; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + $MPEGstreamData = fread($this->getid3->fp, min(100000, $info['avdataend'] - $info['avdataoffset'])); + $MPEGstreamDataLength = strlen($MPEGstreamData); + + $foundVideo = true; + $VideoChunkOffset = 0; + while (substr($MPEGstreamData, $VideoChunkOffset++, 4) !== GETID3_MPEG_VIDEO_SEQUENCE_HEADER) { + if ($VideoChunkOffset >= $MPEGstreamDataLength) { + $foundVideo = false; + break; + } + } + if ($foundVideo) { + + // Start code 32 bits + // horizontal frame size 12 bits + // vertical frame size 12 bits + // pixel aspect ratio 4 bits + // frame rate 4 bits + // bitrate 18 bits + // marker bit 1 bit + // VBV buffer size 10 bits + // constrained parameter flag 1 bit + // intra quant. matrix flag 1 bit + // intra quant. matrix values 512 bits (present if matrix flag == 1) + // non-intra quant. matrix flag 1 bit + // non-intra quant. matrix values 512 bits (present if matrix flag == 1) + + $info['video']['dataformat'] = 'mpeg'; + + $VideoChunkOffset += (strlen(GETID3_MPEG_VIDEO_SEQUENCE_HEADER) - 1); + + $FrameSizeDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 3)); + $VideoChunkOffset += 3; + + $AspectRatioFrameRateDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 1)); + $VideoChunkOffset += 1; + + $assortedinformation = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 4)); + $VideoChunkOffset += 4; + + $info['mpeg']['video']['raw']['framesize_horizontal'] = ($FrameSizeDWORD & 0xFFF000) >> 12; // 12 bits for horizontal frame size + $info['mpeg']['video']['raw']['framesize_vertical'] = ($FrameSizeDWORD & 0x000FFF); // 12 bits for vertical frame size + $info['mpeg']['video']['raw']['pixel_aspect_ratio'] = ($AspectRatioFrameRateDWORD & 0xF0) >> 4; + $info['mpeg']['video']['raw']['frame_rate'] = ($AspectRatioFrameRateDWORD & 0x0F); + + $info['mpeg']['video']['framesize_horizontal'] = $info['mpeg']['video']['raw']['framesize_horizontal']; + $info['mpeg']['video']['framesize_vertical'] = $info['mpeg']['video']['raw']['framesize_vertical']; + + $info['mpeg']['video']['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']); + $info['mpeg']['video']['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']); + $info['mpeg']['video']['frame_rate'] = $this->MPEGvideoFramerateLookup($info['mpeg']['video']['raw']['frame_rate']); + + $info['mpeg']['video']['raw']['bitrate'] = getid3_lib::Bin2Dec(substr($assortedinformation, 0, 18)); + $info['mpeg']['video']['raw']['marker_bit'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 18, 1)); + $info['mpeg']['video']['raw']['vbv_buffer_size'] = getid3_lib::Bin2Dec(substr($assortedinformation, 19, 10)); + $info['mpeg']['video']['raw']['constrained_param_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 29, 1)); + $info['mpeg']['video']['raw']['intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 30, 1)); + if ($info['mpeg']['video']['raw']['intra_quant_flag']) { + + // read 512 bits + $info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)); + $VideoChunkOffset += 64; + + $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($info['mpeg']['video']['raw']['intra_quant'], 511, 1)); + $info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)).substr(getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)), 0, 511); + + if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) { + $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); + $VideoChunkOffset += 64; + } + + } else { + + $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)); + if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) { + $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); + $VideoChunkOffset += 64; + } + + } + + if ($info['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits + + $info['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']['framesize_horizontal']; + $info['video']['resolution_y'] = $info['mpeg']['video']['framesize_vertical']; + $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; + + } else { + + $info['error'][] = 'Could not find start of video block in the first 100,000 bytes (or before end of file) - this might not be an MPEG-video file?'; + + } + + //0x000001B3 begins the sequence_header of every MPEG video stream. + //But in MPEG-2, this header must immediately be followed by an + //extension_start_code (0x000001B5) with a sequence_extension ID (1). + //(This extension contains all the additional MPEG-2 stuff.) + //MPEG-1 doesn't have this extension, so that's a sure way to tell the + //difference between MPEG-1 and MPEG-2 video streams. + + if (substr($MPEGstreamData, $VideoChunkOffset, 4) == GETID3_MPEG_VIDEO_EXTENSION_START) { + $info['video']['codec'] = 'MPEG-2'; + } else { + $info['video']['codec'] = 'MPEG-1'; + } + + + $AudioChunkOffset = 0; + while (true) { + while (substr($MPEGstreamData, $AudioChunkOffset++, 4) !== GETID3_MPEG_AUDIO_START) { + if ($AudioChunkOffset >= $MPEGstreamDataLength) { + break 2; + } + } + + $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 + fseek($getid3_temp->fp, ftell($this->getid3->fp), SEEK_SET); + $getid3_temp->info = $info; // only overwrite real data if valid header found + if ($getid3_mp3->decodeMPEGaudioHeader(($AudioChunkOffset + 3) + 8 + $i, $getid3_temp->info, false)) { + $info = $getid3_temp->info; + $info['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['lossless'] = false; + unset($getid3_temp, $getid3_mp3); + break 2; + } + } + unset($getid3_temp, $getid3_mp3); + } + + // 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 patter + // Use interpolated lookup tables to approximately guess how much is overhead, because + // playtime is calculated as filesize / total-bitrate + $info['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($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) { + $info['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.'; + } + } + + return true; + } + + + public function MPEGsystemNonOverheadPercentage($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 function MPEGvideoFramerateLookup($rawframerate) { + $MPEGvideoFramerateLookup = array(0, 23.976, 24, 25, 29.97, 30, 50, 59.94, 60); + return (isset($MPEGvideoFramerateLookup[$rawframerate]) ? (float) $MPEGvideoFramerateLookup[$rawframerate] : (float) 0); + } + + public function MPEGvideoAspectRatioLookup($rawaspectratio) { + $MPEGvideoAspectRatioLookup = 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($MPEGvideoAspectRatioLookup[$rawaspectratio]) ? (float) $MPEGvideoAspectRatioLookup[$rawaspectratio] : (float) 0); + } + + public function MPEGvideoAspectRatioTextLookup($rawaspectratio) { + $MPEGvideoAspectRatioTextLookup = 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($MPEGvideoAspectRatioTextLookup[$rawaspectratio]) ? $MPEGvideoAspectRatioTextLookup[$rawaspectratio] : ''); + } + +} diff --git a/extensions/TimedMediaHandler/libs/getid3/module.audio-video.quicktime.php b/extensions/TimedMediaHandler/libs/getid3/module.audio-video.quicktime.php new file mode 100644 index 00000000..e59d61cc --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/module.audio-video.quicktime.php @@ -0,0 +1,2192 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.quicktime.php // +// module for analyzing Quicktime and MP3-in-MP4 files // +// dependencies: module.audio.mp3.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); + +class getid3_quicktime extends getid3_handler +{ + + public $ReturnAtomData = true; + public $ParseAllPossibleAtoms = false; + + public function Analyze() { + $info = &$this->getid3->info; + + $info['fileformat'] = 'quicktime'; + $info['quicktime']['hinting'] = false; + $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present + + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + + $offset = 0; + $atomcounter = 0; + + while ($offset < $info['avdataend']) { + if (!getid3_lib::intValueSupported($offset)) { + $info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions'; + break; + } + fseek($this->getid3->fp, $offset, SEEK_SET); + $AtomHeader = fread($this->getid3->fp, 8); + + $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4)); + $atomname = substr($AtomHeader, 4, 4); + + // 64-bit MOV patch by jlegatektnc*com + if ($atomsize == 1) { + $atomsize = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 8)); + } + + $info['quicktime'][$atomname]['name'] = $atomname; + $info['quicktime'][$atomname]['size'] = $atomsize; + $info['quicktime'][$atomname]['offset'] = $offset; + + if (($offset + $atomsize) > $info['avdataend']) { + $info['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)'; + return false; + } + + if ($atomsize == 0) { + // Furthermore, for historical reasons the list of atoms is optionally + // terminated by a 32-bit integer set to 0. If you are writing a program + // to read user data atoms, you should allow for the terminating 0. + break; + } + switch ($atomname) { + case 'mdat': // Media DATa atom + // 'mdat' contains the actual data for the audio/video + if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) { + + $info['avdataoffset'] = $info['quicktime'][$atomname]['offset'] + 8; + $OldAVDataEnd = $info['avdataend']; + $info['avdataend'] = $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size']; + + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + $getid3_mp3 = new getid3_mp3($getid3_temp); + if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode(fread($this->getid3->fp, 4)))) { + $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $value) { + $info['warning'][] = $value; + } + } + if (!empty($getid3_temp->info['mpeg'])) { + $info['mpeg'] = $getid3_temp->info['mpeg']; + if (isset($info['mpeg']['audio'])) { + $info['audio']['dataformat'] = 'mp3'; + $info['audio']['codec'] = (!empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' :'mp3'))); + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + $info['bitrate'] = $info['audio']['bitrate']; + } + } + } + unset($getid3_mp3, $getid3_temp); + $info['avdataend'] = $OldAVDataEnd; + unset($OldAVDataEnd); + + } + break; + + case 'free': // FREE space atom + case 'skip': // SKIP atom + case 'wide': // 64-bit expansion placeholder atom + // 'free', 'skip' and 'wide' are just padding, contains no useful data at all + break; + + default: + $atomHierarchy = array(); + $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($this->getid3->fp, $atomsize), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms); + break; + } + + $offset += $atomsize; + $atomcounter++; + } + + if (!empty($info['avdataend_tmp'])) { + // this value is assigned to a temp value and then erased because + // otherwise any atoms beyond the 'mdat' atom would not get parsed + $info['avdataend'] = $info['avdataend_tmp']; + unset($info['avdataend_tmp']); + } + + if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) { + $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + } + if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) { + $info['audio']['bitrate'] = $info['bitrate']; + } + if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) { + foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) { + $samples_per_second = $samples_count / $info['playtime_seconds']; + if ($samples_per_second > 240) { + // has to be audio samples + } else { + $info['video']['frame_rate'] = $samples_per_second; + break; + } + } + } + if (($info['audio']['dataformat'] == 'mp4') && empty($info['video']['resolution_x'])) { + $info['fileformat'] = 'mp4'; + $info['mime_type'] = 'audio/mp4'; + unset($info['video']['dataformat']); + } + + if (!$this->ReturnAtomData) { + unset($info['quicktime']['moov']); + } + + if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) { + $info['audio']['dataformat'] = 'quicktime'; + } + if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) { + $info['video']['dataformat'] = 'quicktime'; + } + + return true; + } + + public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { + // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm + + $info = &$this->getid3->info; + + $atom_parent = array_pop($atomHierarchy); + array_push($atomHierarchy, $atomname); + $atom_structure['hierarchy'] = implode(' ', $atomHierarchy); + $atom_structure['name'] = $atomname; + $atom_structure['size'] = $atomsize; + $atom_structure['offset'] = $baseoffset; +//echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8)).'
'; +//echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8), false).'

'; + switch ($atomname) { + case 'moov': // MOVie container atom + case 'trak': // TRAcK container atom + case 'clip': // CLIPping container atom + case 'matt': // track MATTe container atom + case 'edts': // EDiTS container atom + case 'tref': // Track REFerence container atom + case 'mdia': // MeDIA container atom + case 'minf': // Media INFormation container atom + case 'dinf': // Data INFormation container atom + case 'udta': // User DaTA container atom + case 'cmov': // Compressed MOVie container atom + case 'rmra': // Reference Movie Record Atom + case 'rmda': // Reference Movie Descriptor Atom + case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + case 'ilst': // Item LiST container atom + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + + // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted + $allnumericnames = true; + foreach ($atom_structure['subatoms'] as $subatomarray) { + if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) { + $allnumericnames = false; + break; + } + } + if ($allnumericnames) { + $newData = array(); + foreach ($atom_structure['subatoms'] as $subatomarray) { + foreach ($subatomarray['subatoms'] as $newData_subatomarray) { + unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']); + $newData[$subatomarray['name']] = $newData_subatomarray; + break; + } + } + $atom_structure['data'] = $newData; + unset($atom_structure['subatoms']); + } + break; + + case "\x00\x00\x00\x01": + case "\x00\x00\x00\x02": + case "\x00\x00\x00\x03": + case "\x00\x00\x00\x04": + case "\x00\x00\x00\x05": + $atomname = getid3_lib::BigEndian2Int($atomname); + $atom_structure['name'] = $atomname; + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + case 'stbl': // Sample TaBLe container atom + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + $isVideo = false; + $framerate = 0; + $framecount = 0; + foreach ($atom_structure['subatoms'] as $key => $value_array) { + if (isset($value_array['sample_description_table'])) { + foreach ($value_array['sample_description_table'] as $key2 => $value_array2) { + if (isset($value_array2['data_format'])) { + switch ($value_array2['data_format']) { + case 'avc1': + case 'mp4v': + // video data + $isVideo = true; + break; + case 'mp4a': + // audio data + break; + } + } + } + } elseif (isset($value_array['time_to_sample_table'])) { + foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) { + if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) { + $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3); + $framecount = $value_array2['sample_count']; + } + } + } + } + if ($isVideo && $framerate) { + $info['quicktime']['video']['frame_rate'] = $framerate; + $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate']; + } + if ($isVideo && $framecount) { + $info['quicktime']['video']['frame_count'] = $framecount; + } + break; + + + case 'aART': // Album ARTist + case 'catg': // CaTeGory + case 'covr': // COVeR artwork + case 'cpil': // ComPILation + case 'cprt': // CoPyRighT + case 'desc': // DESCription + case 'disk': // DISK number + case 'egid': // Episode Global ID + case 'gnre': // GeNRE + case 'keyw': // KEYWord + case 'ldes': + case 'pcst': // PodCaST + case 'pgap': // GAPless Playback + case 'purd': // PURchase Date + case 'purl': // Podcast URL + case 'rati': + case 'rndu': + case 'rpdu': + case 'rtng': // RaTiNG + case 'stik': + case 'tmpo': // TeMPO (BPM) + case 'trkn': // TRacK Number + case 'tves': // TV EpiSode + case 'tvnn': // TV Network Name + case 'tvsh': // TV SHow Name + case 'tvsn': // TV SeasoN + case 'akID': // iTunes store account type + case 'apID': + case 'atID': + case 'cmID': + case 'cnID': + case 'geID': + case 'plID': + case 'sfID': // iTunes store country + case 'alb': // ALBum + case 'art': // ARTist + case 'ART': + case 'aut': + case 'cmt': // CoMmenT + case 'com': // COMposer + case 'cpy': + case 'day': // content created year + case 'dir': + case 'ed1': + case 'ed2': + case 'ed3': + case 'ed4': + case 'ed5': + case 'ed6': + case 'ed7': + case 'ed8': + case 'ed9': + case 'enc': + case 'fmt': + case 'gen': // GENre + case 'grp': // GRouPing + case 'hst': + case 'inf': + case 'lyr': // LYRics + case 'mak': + case 'mod': + case 'nam': // full NAMe + case 'ope': + case 'PRD': + case 'prd': + case 'prf': + case 'req': + case 'src': + case 'swr': + case 'too': // encoder + case 'trk': // TRacK + case 'url': + case 'wrn': + case 'wrt': // WRiTer + case '----': // itunes specific + if ($atom_parent == 'udta') { + // User data atom handler + $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); + $atom_structure['data'] = substr($atom_data, 4); + + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; + } + } else { + // Apple item list box atom handler + $atomoffset = 0; + if (substr($atom_data, 2, 2) == "\x10\xB5") { + // not sure what it means, but observed on iPhone4 data. + // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data + while ($atomoffset < strlen($atom_data)) { + $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2)); + $boxsmalltype = substr($atom_data, $atomoffset + 2, 2); + $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize); + switch ($boxsmalltype) { + case "\x10\xB5": + $atom_structure['data'] = $boxsmalldata; + break; + default: + $info['warning'][] = 'Unknown QuickTime smallbox type: "'.getid3_lib::PrintHexBytes($boxsmalltype).'" at offset '.$baseoffset; + $atom_structure['data'] = $atom_data; + break; + } + $atomoffset += (4 + $boxsmallsize); + } + } else { + while ($atomoffset < strlen($atom_data)) { + $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4)); + $boxtype = substr($atom_data, $atomoffset + 4, 4); + $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8); + if ($boxsize <= 1) { + $info['warning'][] = 'Invalid QuickTime atom box size "'.$boxsize.'" in atom "'.$atomname.'" at offset: '.($atom_structure['offset'] + $atomoffset); + $atom_structure['data'] = null; + $atomoffset = strlen($atom_data); + break; + } + $atomoffset += $boxsize; + + switch ($boxtype) { + case 'mean': + case 'name': + $atom_structure[$boxtype] = substr($boxdata, 4); + break; + + case 'data': + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3)); + switch ($atom_structure['flags_raw']) { + case 0: // data flag + case 21: // tmpo/cpil flag + switch ($atomname) { + case 'cpil': + case 'pcst': + case 'pgap': + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + break; + + case 'tmpo': + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2)); + break; + + case 'disk': + case 'trkn': + $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2)); + $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2)); + $atom_structure['data'] = empty($num) ? '' : $num; + $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total; + break; + + case 'gnre': + $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1); + break; + + case 'rtng': + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]); + break; + + case 'stik': + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1)); + $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]); + break; + + case 'sfID': + $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]); + break; + + case 'egid': + case 'purl': + $atom_structure['data'] = substr($boxdata, 8); + break; + + default: + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4)); + } + break; + + case 1: // text flag + case 13: // image flag + default: + $atom_structure['data'] = substr($boxdata, 8); + break; + + } + break; + + default: + $info['warning'][] = 'Unknown QuickTime box type: "'.getid3_lib::PrintHexBytes($boxtype).'" at offset '.$baseoffset; + $atom_structure['data'] = $atom_data; + + } + } + } + } + $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']); + break; + + + case 'play': // auto-PLAY atom + $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + + $info['quicktime']['autoplay'] = $atom_structure['autoplay']; + break; + + + case 'WLOC': // Window LOCation atom + $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); + break; + + + case 'LOOP': // LOOPing atom + case 'SelO': // play SELection Only atom + case 'AllF': // play ALL Frames atom + $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data); + break; + + + case 'name': // + case 'MCPS': // Media Cleaner PRo + case '@PRM': // adobe PReMiere version + case '@PRQ': // adobe PRemiere Quicktime version + $atom_structure['data'] = $atom_data; + break; + + + case 'cmvd': // Compressed MooV Data atom + // Code by ubergeekubergeek*tv based on information from + // http://developer.apple.com/quicktime/icefloe/dispatch012.html + $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + + $CompressedFileData = substr($atom_data, 4); + if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms); + } else { + $info['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atom_structure['offset']; + } + break; + + + case 'dcom': // Data COMpression atom + $atom_structure['compression_id'] = $atom_data; + $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data); + break; + + + case 'rdrf': // Reference movie Data ReFerence atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001); + + $atom_structure['reference_type_name'] = substr($atom_data, 4, 4); + $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + switch ($atom_structure['reference_type_name']) { + case 'url ': + $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12)); + break; + + case 'alis': + $atom_structure['file_alias'] = substr($atom_data, 12); + break; + + case 'rsrc': + $atom_structure['resource_alias'] = substr($atom_data, 12); + break; + + default: + $atom_structure['data'] = substr($atom_data, 12); + break; + } + break; + + + case 'rmqu': // Reference Movie QUality atom + $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data); + break; + + + case 'rmcs': // Reference Movie Cpu Speed atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + break; + + + case 'rmvc': // Reference Movie Version Check atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4); + $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); + break; + + + case 'rmcd': // Reference Movie Component check atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['component_type'] = substr($atom_data, 4, 4); + $atom_structure['component_subtype'] = substr($atom_data, 8, 4); + $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); + $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4)); + break; + + + case 'rmdr': // Reference Movie Data Rate atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + + $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10; + break; + + + case 'rmla': // Reference Movie Language Atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; + } + break; + + + case 'rmla': // Reference Movie Language Atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + break; + + + case 'ptv ': // Print To Video - defines a movie's full screen mode + // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm + $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); + $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000 + $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000 + $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1)); + $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1)); + + $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag']; + $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag']; + + $ptv_lookup[0] = 'normal'; + $ptv_lookup[1] = 'double'; + $ptv_lookup[2] = 'half'; + $ptv_lookup[3] = 'full'; + $ptv_lookup[4] = 'current'; + if (isset($ptv_lookup[$atom_structure['display_size_raw']])) { + $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']]; + } else { + $info['warning'][] = 'unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')'; + } + break; + + + case 'stsd': // Sample Table Sample Description atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stsdEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); + $stsdEntriesDataOffset += 4; + $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4); + $stsdEntriesDataOffset += 4; + $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6)); + $stsdEntriesDataOffset += 6; + $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2)); + $stsdEntriesDataOffset += 2; + $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2)); + $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2); + + $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2)); + $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2)); + $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4); + + switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) { + + case "\x00\x00\x00\x00": + // audio tracks + $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2)); + $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2)); + $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2)); + $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2)); + $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4)); + + // video tracks + // http://developer.apple.com/library/mac/#documentation/QuickTime/QTFF/QTFFChap3/qtff3.html + $atom_structure['sample_description_table'][$i]['temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); + $atom_structure['sample_description_table'][$i]['spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); + $atom_structure['sample_description_table'][$i]['width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); + $atom_structure['sample_description_table'][$i]['height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); + $atom_structure['sample_description_table'][$i]['resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); + $atom_structure['sample_description_table'][$i]['resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); + $atom_structure['sample_description_table'][$i]['data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 4)); + $atom_structure['sample_description_table'][$i]['frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 36, 2)); + $atom_structure['sample_description_table'][$i]['compressor_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 38, 4); + $atom_structure['sample_description_table'][$i]['pixel_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 42, 2)); + $atom_structure['sample_description_table'][$i]['color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 44, 2)); + + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case '2vuY': + case 'avc1': + case 'cvid': + case 'dvc ': + case 'dvcp': + case 'gif ': + case 'h263': + case 'jpeg': + case 'kpcd': + case 'mjpa': + case 'mjpb': + case 'mp4v': + case 'png ': + case 'raw ': + case 'rle ': + case 'rpza': + case 'smc ': + case 'SVQ1': + case 'SVQ3': + case 'tiff': + case 'v210': + case 'v216': + case 'v308': + case 'v408': + case 'v410': + case 'yuv2': + $info['fileformat'] = 'mp4'; + $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; +// http://www.getid3.org/phpBB3/viewtopic.php?t=1550 +//if ((!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['width'])) && (empty($info['video']['resolution_x']) || empty($info['video']['resolution_y']) || (number_format($info['video']['resolution_x'], 6) != number_format(round($info['video']['resolution_x']), 6)) || (number_format($info['video']['resolution_y'], 6) != number_format(round($info['video']['resolution_y']), 6)))) { // ugly check for floating point numbers +if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($atom_structure['sample_description_table'][$i]['height'])) { + // assume that values stored here are more important than values stored in [tkhd] atom + $info['video']['resolution_x'] = $atom_structure['sample_description_table'][$i]['width']; + $info['video']['resolution_y'] = $atom_structure['sample_description_table'][$i]['height']; + $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; + $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; +} + break; + + case 'qtvr': + $info['video']['dataformat'] = 'quicktimevr'; + break; + + case 'mp4a': + default: + $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); + $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate']; + $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels']; + $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth']; + $info['audio']['codec'] = $info['quicktime']['audio']['codec']; + $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate']; + $info['audio']['channels'] = $info['quicktime']['audio']['channels']; + $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth']; + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case 'raw ': // PCM + case 'alac': // Apple Lossless Audio Codec + $info['audio']['lossless'] = true; + break; + default: + $info['audio']['lossless'] = false; + break; + } + break; + } + break; + + default: + switch ($atom_structure['sample_description_table'][$i]['data_format']) { + case 'mp4s': + $info['fileformat'] = 'mp4'; + break; + + default: + // video atom + $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4)); + $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4)); + $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2)); + $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2)); + $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4)); + $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4)); + $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4)); + $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2)); + $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1)); + $atom_structure['sample_description_table'][$i]['video_encoder_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 35, $atom_structure['sample_description_table'][$i]['video_encoder_name_len']); + $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2)); + $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2)); + + $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color'); + $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']); + + if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { + $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format']; + $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']); + $info['quicktime']['video']['codec'] = (($atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']); + $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth']; + $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name']; + + $info['video']['codec'] = $info['quicktime']['video']['codec']; + $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth']; + } + $info['video']['lossless'] = false; + $info['video']['pixel_aspect_ratio'] = (float) 1; + break; + } + break; + } + switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) { + case 'mp4a': + $info['audio']['dataformat'] = 'mp4'; + $info['quicktime']['audio']['codec'] = 'mp4'; + break; + + case '3ivx': + case '3iv1': + case '3iv2': + $info['video']['dataformat'] = '3ivx'; + break; + + case 'xvid': + $info['video']['dataformat'] = 'xvid'; + break; + + case 'mp4v': + $info['video']['dataformat'] = 'mpeg4'; + break; + + case 'divx': + case 'div1': + case 'div2': + case 'div3': + case 'div4': + case 'div5': + case 'div6': + $info['video']['dataformat'] = 'divx'; + break; + + default: + // do nothing + break; + } + unset($atom_structure['sample_description_table'][$i]['data']); + } + break; + + + case 'stts': // Sample Table Time-to-Sample atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $sttsEntriesDataOffset = 8; + //$FrameRateCalculatorArray = array(); + $frames_count = 0; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); + $sttsEntriesDataOffset += 4; + $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4)); + $sttsEntriesDataOffset += 4; + + $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count']; + + // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM + //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) { + // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration']; + // if ($stts_new_framerate <= 60) { + // // some atoms have durations of "1" giving a very large framerate, which probably is not right + // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate); + // } + //} + // + //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count']; + } + $info['quicktime']['stts_framecount'][] = $frames_count; + //$sttsFramesTotal = 0; + //$sttsSecondsTotal = 0; + //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { + // if (($frames_per_second > 60) || ($frames_per_second < 1)) { + // // not video FPS information, probably audio information + // $sttsFramesTotal = 0; + // $sttsSecondsTotal = 0; + // break; + // } + // $sttsFramesTotal += $frame_count; + // $sttsSecondsTotal += $frame_count / $frames_per_second; + //} + //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { + // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) { + // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; + // } + //} + break; + + + case 'stss': // Sample Table Sync Sample (key frames) atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stssEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4)); + $stssEntriesDataOffset += 4; + } + } + break; + + + case 'stsc': // Sample Table Sample-to-Chunk atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stscEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + } + } + break; + + + case 'stsz': // Sample Table SiZe atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $stszEntriesDataOffset = 12; + if ($atom_structure['sample_size'] == 0) { + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4)); + $stszEntriesDataOffset += 4; + } + } + } + break; + + + case 'stco': // Sample Table Chunk Offset atom + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stcoEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4)); + $stcoEntriesDataOffset += 4; + } + } + break; + + + case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files) + if ($ParseAllPossibleAtoms) { + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $stcoEntriesDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8)); + $stcoEntriesDataOffset += 8; + } + } + break; + + + case 'dref': // Data REFerence atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $drefDataOffset = 8; + for ($i = 0; $i < $atom_structure['number_entries']; $i++) { + $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); + $drefDataOffset += 4; + $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4); + $drefDataOffset += 4; + $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1)); + $drefDataOffset += 1; + $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000 + $drefDataOffset += 3; + $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3)); + $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); + + $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001); + } + break; + + + case 'gmin': // base Media INformation atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); + $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); + $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2)); + $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2)); + break; + + + case 'smhd': // Sound Media information HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + break; + + + case 'vmhd': // Video Media information HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); + $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)); + $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2)); + $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); + + $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001); + break; + + + case 'hdlr': // HanDLeR reference atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['component_type'] = substr($atom_data, 4, 4); + $atom_structure['component_subtype'] = substr($atom_data, 8, 4); + $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4); + $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['component_name'] = $this->Pascal2String(substr($atom_data, 24)); + + if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) { + $info['video']['dataformat'] = 'quicktimevr'; + } + break; + + + case 'mdhd': // MeDia HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2)); + $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2)); + + if ($atom_structure['time_scale'] == 0) { + $info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero'; + return false; + } + $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); + + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; + $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']); + if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) { + $info['comments']['language'][] = $atom_structure['language']; + } + break; + + + case 'pnot': // Preview atom + $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format" + $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00 + $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT' + $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01 + + $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']); + break; + + + case 'crgn': // Clipping ReGioN atom + $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box, + $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields + $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region. + break; + + + case 'load': // track LOAD settings atom + $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + + $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020); + $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x0100); + break; + + + case 'tmcd': // TiMe CoDe atom + case 'chap': // CHAPter list atom + case 'sync': // SYNChronization atom + case 'scpt': // tranSCriPT atom + case 'ssrc': // non-primary SouRCe atom + for ($i = 0; $i < (strlen($atom_data) % 4); $i++) { + $atom_structure['track_id'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $i * 4, 4)); + } + break; + + + case 'elst': // Edit LiST atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) { + $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4)); + $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4)); + $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4)); + } + break; + + + case 'kmat': // compressed MATte atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 + $atom_structure['matte_data_raw'] = substr($atom_data, 4); + break; + + + case 'ctab': // Color TABle atom + $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000 + $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000 + $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1; + for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) { + $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2)); + $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2)); + $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2)); + $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2)); + } + break; + + + case 'mvhd': // MoVie HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4)); + $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2)); + $atom_structure['reserved'] = substr($atom_data, 26, 10); + $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4)); + $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); + $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4)); + $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4)); + $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); + $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4)); + $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4)); + $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); + $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4)); + $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4)); + $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4)); + $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4)); + $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4)); + $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4)); + $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4)); + $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4)); + + if ($atom_structure['time_scale'] == 0) { + $info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero'; + return false; + } + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']); + $info['quicktime']['display_scale'] = $atom_structure['matrix_a']; + $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale']; + break; + + + case 'tkhd': // TracK HeaDer atom + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4)); + $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4)); + $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4)); + $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4)); + $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8)); + $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2)); + $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2)); + $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2)); + $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2)); +// http://developer.apple.com/library/mac/#documentation/QuickTime/RM/MovieBasics/MTEditing/K-Chapter/11MatrixFunctions.html +// http://developer.apple.com/library/mac/#documentation/QuickTime/qtff/QTFFChap4/qtff4.html#//apple_ref/doc/uid/TP40000939-CH206-18737 + $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4)); + $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4)); + $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 48, 4)); + $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4)); + $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4)); + $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 60, 4)); + $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4)); + $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 68, 4)); + $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4)); + $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4)); + $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4)); + $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001); + $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002); + $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004); + $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x0008); + $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']); + $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']); + + if ($atom_structure['flags']['enabled'] == 1) { + if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) { + $info['video']['resolution_x'] = $atom_structure['width']; + $info['video']['resolution_y'] = $atom_structure['height']; + } + $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']); + $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']); + $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x']; + $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y']; + } else { + // see: http://www.getid3.org/phpBB3/viewtopic.php?t=1295 + //if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); } + //if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); } + //if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); } + } + break; + + + case 'iods': // Initial Object DeScriptor atom + // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h + // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html + $offset = 0; + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3)); + $offset += 3; + $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); + //$offset already adjusted by quicktime_read_mp4_descr_length() + $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); + $offset += 2; + $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + + $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields + for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) { + $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1)); + $offset += 1; + $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset); + //$offset already adjusted by quicktime_read_mp4_descr_length() + $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); + $offset += 4; + } + + $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']); + $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']); + break; + + case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) + $atom_structure['signature'] = substr($atom_data, 0, 4); + $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + $atom_structure['fourcc'] = substr($atom_data, 8, 4); + break; + + case 'mdat': // Media DATa atom + case 'free': // FREE space atom + case 'skip': // SKIP atom + case 'wide': // 64-bit expansion placeholder atom + // 'mdat' data is too big to deal with, contains no useful metadata + // 'free', 'skip' and 'wide' are just padding, contains no useful data at all + + // When writing QuickTime files, it is sometimes necessary to update an atom's size. + // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom + // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime + // puts an 8-byte placeholder atom before any atoms it may have to update the size of. + // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the + // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. + // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). + break; + + + case 'nsav': // NoSAVe atom + // http://developer.apple.com/technotes/tn/tn2038.html + $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + break; + + case 'ctyp': // Controller TYPe atom (seen on QTVR) + // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt + // some controller names are: + // 0x00 + 'std' for linear movie + // 'none' for no controls + $atom_structure['ctyp'] = substr($atom_data, 0, 4); + $info['quicktime']['controller'] = $atom_structure['ctyp']; + switch ($atom_structure['ctyp']) { + case 'qtvr': + $info['video']['dataformat'] = 'quicktimevr'; + break; + } + break; + + case 'pano': // PANOrama track (seen on QTVR) + $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); + break; + + case 'hint': // HINT track + case 'hinf': // + case 'hinv': // + case 'hnti': // + $info['quicktime']['hinting'] = true; + break; + + case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) + for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) { + $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4)); + } + break; + + + // Observed-but-not-handled atom types are just listed here to prevent warnings being generated + case 'FXTC': // Something to do with Adobe After Effects (?) + case 'PrmA': + case 'code': + case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html + case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html + // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838] + // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html + // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html + case 'ctts':// STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'cslg':// STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'sdtp':// STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + case 'stps':// STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html + //$atom_structure['data'] = $atom_data; + break; + + case 'xyz': // GPS latitude+longitude+altitude + $atom_structure['data'] = $atom_data; + if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) { + @list($all, $latitude, $longitude, $altitude) = $matches; + $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude); + $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude); + if (!empty($altitude)) { + $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude); + } + } else { + $info['warning'][] = 'QuickTime atom "xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.'; + } + break; + + case 'NCDT': + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms); + break; + case 'NCTH': // Nikon Camera THumbnail image + case 'NCVW': // Nikon Camera preVieW image + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) { + $atom_structure['data'] = $atom_data; + $atom_structure['image_mime'] = 'image/jpeg'; + $atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image')); + $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']); + } + break; + case 'NCHD': // MakerNoteVersion + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + $atom_structure['data'] = $atom_data; + break; + case 'NCTG': // NikonTags + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG + $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data); + break; + case 'NCDB': // NikonTags + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html + $atom_structure['data'] = $atom_data; + break; + + case "\x00\x00\x00\x00": + case 'meta': // METAdata atom + // some kind of metacontainer, may contain a big data dump such as: + // mdta keys  mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst   data DEApple 0  (data DE2011-05-11T17:54:04+0200 2  *data DE+52.4936+013.3897+040.247/   data DE4.3.1  data DEiPhone 4 + // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt + + $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); + $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); + $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + case 'data': // metaDATA atom + // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data + $atom_structure['language'] = substr($atom_data, 4 + 0, 2); + $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2)); + $atom_structure['data'] = substr($atom_data, 4 + 4); + break; + + default: + $info['warning'][] = 'Unknown QuickTime atom type: "'.$atomname.'" ('.trim(getid3_lib::PrintHexBytes($atomname)).') at offset '.$baseoffset; + $atom_structure['data'] = $atom_data; + break; + } + array_pop($atomHierarchy); + return $atom_structure; + } + + public function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { +//echo 'QuicktimeParseContainerAtom('.substr($atom_data, 4, 4).') @ '.$baseoffset.'

'; + $atom_structure = false; + $subatomoffset = 0; + $subatomcounter = 0; + if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) { + return false; + } + while ($subatomoffset < strlen($atom_data)) { + $subatomsize = getid3_lib::BigEndian2Int(substr($atom_data, $subatomoffset + 0, 4)); + $subatomname = substr($atom_data, $subatomoffset + 4, 4); + $subatomdata = substr($atom_data, $subatomoffset + 8, $subatomsize - 8); + if ($subatomsize == 0) { + // Furthermore, for historical reasons the list of atoms is optionally + // terminated by a 32-bit integer set to 0. If you are writing a program + // to read user data atoms, you should allow for the terminating 0. + return $atom_structure; + } + + $atom_structure[$subatomcounter] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms); + + $subatomoffset += $subatomsize; + $subatomcounter++; + } + return $atom_structure; + } + + + public function quicktime_read_mp4_descr_length($data, &$offset) { + // http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html + $num_bytes = 0; + $length = 0; + do { + $b = ord(substr($data, $offset++, 1)); + $length = ($length << 7) | ($b & 0x7F); + } while (($b & 0x80) && ($num_bytes++ < 4)); + return $length; + } + + + public function QuicktimeLanguageLookup($languageid) { + static $QuicktimeLanguageLookup = array(); + if (empty($QuicktimeLanguageLookup)) { + $QuicktimeLanguageLookup[0] = 'English'; + $QuicktimeLanguageLookup[1] = 'French'; + $QuicktimeLanguageLookup[2] = 'German'; + $QuicktimeLanguageLookup[3] = 'Italian'; + $QuicktimeLanguageLookup[4] = 'Dutch'; + $QuicktimeLanguageLookup[5] = 'Swedish'; + $QuicktimeLanguageLookup[6] = 'Spanish'; + $QuicktimeLanguageLookup[7] = 'Danish'; + $QuicktimeLanguageLookup[8] = 'Portuguese'; + $QuicktimeLanguageLookup[9] = 'Norwegian'; + $QuicktimeLanguageLookup[10] = 'Hebrew'; + $QuicktimeLanguageLookup[11] = 'Japanese'; + $QuicktimeLanguageLookup[12] = 'Arabic'; + $QuicktimeLanguageLookup[13] = 'Finnish'; + $QuicktimeLanguageLookup[14] = 'Greek'; + $QuicktimeLanguageLookup[15] = 'Icelandic'; + $QuicktimeLanguageLookup[16] = 'Maltese'; + $QuicktimeLanguageLookup[17] = 'Turkish'; + $QuicktimeLanguageLookup[18] = 'Croatian'; + $QuicktimeLanguageLookup[19] = 'Chinese (Traditional)'; + $QuicktimeLanguageLookup[20] = 'Urdu'; + $QuicktimeLanguageLookup[21] = 'Hindi'; + $QuicktimeLanguageLookup[22] = 'Thai'; + $QuicktimeLanguageLookup[23] = 'Korean'; + $QuicktimeLanguageLookup[24] = 'Lithuanian'; + $QuicktimeLanguageLookup[25] = 'Polish'; + $QuicktimeLanguageLookup[26] = 'Hungarian'; + $QuicktimeLanguageLookup[27] = 'Estonian'; + $QuicktimeLanguageLookup[28] = 'Lettish'; + $QuicktimeLanguageLookup[28] = 'Latvian'; + $QuicktimeLanguageLookup[29] = 'Saamisk'; + $QuicktimeLanguageLookup[29] = 'Lappish'; + $QuicktimeLanguageLookup[30] = 'Faeroese'; + $QuicktimeLanguageLookup[31] = 'Farsi'; + $QuicktimeLanguageLookup[31] = 'Persian'; + $QuicktimeLanguageLookup[32] = 'Russian'; + $QuicktimeLanguageLookup[33] = 'Chinese (Simplified)'; + $QuicktimeLanguageLookup[34] = 'Flemish'; + $QuicktimeLanguageLookup[35] = 'Irish'; + $QuicktimeLanguageLookup[36] = 'Albanian'; + $QuicktimeLanguageLookup[37] = 'Romanian'; + $QuicktimeLanguageLookup[38] = 'Czech'; + $QuicktimeLanguageLookup[39] = 'Slovak'; + $QuicktimeLanguageLookup[40] = 'Slovenian'; + $QuicktimeLanguageLookup[41] = 'Yiddish'; + $QuicktimeLanguageLookup[42] = 'Serbian'; + $QuicktimeLanguageLookup[43] = 'Macedonian'; + $QuicktimeLanguageLookup[44] = 'Bulgarian'; + $QuicktimeLanguageLookup[45] = 'Ukrainian'; + $QuicktimeLanguageLookup[46] = 'Byelorussian'; + $QuicktimeLanguageLookup[47] = 'Uzbek'; + $QuicktimeLanguageLookup[48] = 'Kazakh'; + $QuicktimeLanguageLookup[49] = 'Azerbaijani'; + $QuicktimeLanguageLookup[50] = 'AzerbaijanAr'; + $QuicktimeLanguageLookup[51] = 'Armenian'; + $QuicktimeLanguageLookup[52] = 'Georgian'; + $QuicktimeLanguageLookup[53] = 'Moldavian'; + $QuicktimeLanguageLookup[54] = 'Kirghiz'; + $QuicktimeLanguageLookup[55] = 'Tajiki'; + $QuicktimeLanguageLookup[56] = 'Turkmen'; + $QuicktimeLanguageLookup[57] = 'Mongolian'; + $QuicktimeLanguageLookup[58] = 'MongolianCyr'; + $QuicktimeLanguageLookup[59] = 'Pashto'; + $QuicktimeLanguageLookup[60] = 'Kurdish'; + $QuicktimeLanguageLookup[61] = 'Kashmiri'; + $QuicktimeLanguageLookup[62] = 'Sindhi'; + $QuicktimeLanguageLookup[63] = 'Tibetan'; + $QuicktimeLanguageLookup[64] = 'Nepali'; + $QuicktimeLanguageLookup[65] = 'Sanskrit'; + $QuicktimeLanguageLookup[66] = 'Marathi'; + $QuicktimeLanguageLookup[67] = 'Bengali'; + $QuicktimeLanguageLookup[68] = 'Assamese'; + $QuicktimeLanguageLookup[69] = 'Gujarati'; + $QuicktimeLanguageLookup[70] = 'Punjabi'; + $QuicktimeLanguageLookup[71] = 'Oriya'; + $QuicktimeLanguageLookup[72] = 'Malayalam'; + $QuicktimeLanguageLookup[73] = 'Kannada'; + $QuicktimeLanguageLookup[74] = 'Tamil'; + $QuicktimeLanguageLookup[75] = 'Telugu'; + $QuicktimeLanguageLookup[76] = 'Sinhalese'; + $QuicktimeLanguageLookup[77] = 'Burmese'; + $QuicktimeLanguageLookup[78] = 'Khmer'; + $QuicktimeLanguageLookup[79] = 'Lao'; + $QuicktimeLanguageLookup[80] = 'Vietnamese'; + $QuicktimeLanguageLookup[81] = 'Indonesian'; + $QuicktimeLanguageLookup[82] = 'Tagalog'; + $QuicktimeLanguageLookup[83] = 'MalayRoman'; + $QuicktimeLanguageLookup[84] = 'MalayArabic'; + $QuicktimeLanguageLookup[85] = 'Amharic'; + $QuicktimeLanguageLookup[86] = 'Tigrinya'; + $QuicktimeLanguageLookup[87] = 'Galla'; + $QuicktimeLanguageLookup[87] = 'Oromo'; + $QuicktimeLanguageLookup[88] = 'Somali'; + $QuicktimeLanguageLookup[89] = 'Swahili'; + $QuicktimeLanguageLookup[90] = 'Ruanda'; + $QuicktimeLanguageLookup[91] = 'Rundi'; + $QuicktimeLanguageLookup[92] = 'Chewa'; + $QuicktimeLanguageLookup[93] = 'Malagasy'; + $QuicktimeLanguageLookup[94] = 'Esperanto'; + $QuicktimeLanguageLookup[128] = 'Welsh'; + $QuicktimeLanguageLookup[129] = 'Basque'; + $QuicktimeLanguageLookup[130] = 'Catalan'; + $QuicktimeLanguageLookup[131] = 'Latin'; + $QuicktimeLanguageLookup[132] = 'Quechua'; + $QuicktimeLanguageLookup[133] = 'Guarani'; + $QuicktimeLanguageLookup[134] = 'Aymara'; + $QuicktimeLanguageLookup[135] = 'Tatar'; + $QuicktimeLanguageLookup[136] = 'Uighur'; + $QuicktimeLanguageLookup[137] = 'Dzongkha'; + $QuicktimeLanguageLookup[138] = 'JavaneseRom'; + } + return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid'); + } + + public function QuicktimeVideoCodecLookup($codecid) { + static $QuicktimeVideoCodecLookup = array(); + if (empty($QuicktimeVideoCodecLookup)) { + $QuicktimeVideoCodecLookup['.SGI'] = 'SGI'; + $QuicktimeVideoCodecLookup['3IV1'] = '3ivx MPEG-4 v1'; + $QuicktimeVideoCodecLookup['3IV2'] = '3ivx MPEG-4 v2'; + $QuicktimeVideoCodecLookup['3IVX'] = '3ivx MPEG-4'; + $QuicktimeVideoCodecLookup['8BPS'] = 'Planar RGB'; + $QuicktimeVideoCodecLookup['avc1'] = 'H.264/MPEG-4 AVC'; + $QuicktimeVideoCodecLookup['avr '] = 'AVR-JPEG'; + $QuicktimeVideoCodecLookup['b16g'] = '16Gray'; + $QuicktimeVideoCodecLookup['b32a'] = '32AlphaGray'; + $QuicktimeVideoCodecLookup['b48r'] = '48RGB'; + $QuicktimeVideoCodecLookup['b64a'] = '64ARGB'; + $QuicktimeVideoCodecLookup['base'] = 'Base'; + $QuicktimeVideoCodecLookup['clou'] = 'Cloud'; + $QuicktimeVideoCodecLookup['cmyk'] = 'CMYK'; + $QuicktimeVideoCodecLookup['cvid'] = 'Cinepak'; + $QuicktimeVideoCodecLookup['dmb1'] = 'OpenDML JPEG'; + $QuicktimeVideoCodecLookup['dvc '] = 'DVC-NTSC'; + $QuicktimeVideoCodecLookup['dvcp'] = 'DVC-PAL'; + $QuicktimeVideoCodecLookup['dvpn'] = 'DVCPro-NTSC'; + $QuicktimeVideoCodecLookup['dvpp'] = 'DVCPro-PAL'; + $QuicktimeVideoCodecLookup['fire'] = 'Fire'; + $QuicktimeVideoCodecLookup['flic'] = 'FLC'; + $QuicktimeVideoCodecLookup['gif '] = 'GIF'; + $QuicktimeVideoCodecLookup['h261'] = 'H261'; + $QuicktimeVideoCodecLookup['h263'] = 'H263'; + $QuicktimeVideoCodecLookup['IV41'] = 'Indeo4'; + $QuicktimeVideoCodecLookup['jpeg'] = 'JPEG'; + $QuicktimeVideoCodecLookup['kpcd'] = 'PhotoCD'; + $QuicktimeVideoCodecLookup['mjpa'] = 'Motion JPEG-A'; + $QuicktimeVideoCodecLookup['mjpb'] = 'Motion JPEG-B'; + $QuicktimeVideoCodecLookup['msvc'] = 'Microsoft Video1'; + $QuicktimeVideoCodecLookup['myuv'] = 'MPEG YUV420'; + $QuicktimeVideoCodecLookup['path'] = 'Vector'; + $QuicktimeVideoCodecLookup['png '] = 'PNG'; + $QuicktimeVideoCodecLookup['PNTG'] = 'MacPaint'; + $QuicktimeVideoCodecLookup['qdgx'] = 'QuickDrawGX'; + $QuicktimeVideoCodecLookup['qdrw'] = 'QuickDraw'; + $QuicktimeVideoCodecLookup['raw '] = 'RAW'; + $QuicktimeVideoCodecLookup['ripl'] = 'WaterRipple'; + $QuicktimeVideoCodecLookup['rpza'] = 'Video'; + $QuicktimeVideoCodecLookup['smc '] = 'Graphics'; + $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 1'; + $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 3'; + $QuicktimeVideoCodecLookup['syv9'] = 'Sorenson YUV9'; + $QuicktimeVideoCodecLookup['tga '] = 'Targa'; + $QuicktimeVideoCodecLookup['tiff'] = 'TIFF'; + $QuicktimeVideoCodecLookup['WRAW'] = 'Windows RAW'; + $QuicktimeVideoCodecLookup['WRLE'] = 'BMP'; + $QuicktimeVideoCodecLookup['y420'] = 'YUV420'; + $QuicktimeVideoCodecLookup['yuv2'] = 'ComponentVideo'; + $QuicktimeVideoCodecLookup['yuvs'] = 'ComponentVideoUnsigned'; + $QuicktimeVideoCodecLookup['yuvu'] = 'ComponentVideoSigned'; + } + return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : ''); + } + + public function QuicktimeAudioCodecLookup($codecid) { + static $QuicktimeAudioCodecLookup = array(); + if (empty($QuicktimeAudioCodecLookup)) { + $QuicktimeAudioCodecLookup['.mp3'] = 'Fraunhofer MPEG Layer-III alias'; + $QuicktimeAudioCodecLookup['aac '] = 'ISO/IEC 14496-3 AAC'; + $QuicktimeAudioCodecLookup['agsm'] = 'Apple GSM 10:1'; + $QuicktimeAudioCodecLookup['alac'] = 'Apple Lossless Audio Codec'; + $QuicktimeAudioCodecLookup['alaw'] = 'A-law 2:1'; + $QuicktimeAudioCodecLookup['conv'] = 'Sample Format'; + $QuicktimeAudioCodecLookup['dvca'] = 'DV'; + $QuicktimeAudioCodecLookup['dvi '] = 'DV 4:1'; + $QuicktimeAudioCodecLookup['eqal'] = 'Frequency Equalizer'; + $QuicktimeAudioCodecLookup['fl32'] = '32-bit Floating Point'; + $QuicktimeAudioCodecLookup['fl64'] = '64-bit Floating Point'; + $QuicktimeAudioCodecLookup['ima4'] = 'Interactive Multimedia Association 4:1'; + $QuicktimeAudioCodecLookup['in24'] = '24-bit Integer'; + $QuicktimeAudioCodecLookup['in32'] = '32-bit Integer'; + $QuicktimeAudioCodecLookup['lpc '] = 'LPC 23:1'; + $QuicktimeAudioCodecLookup['MAC3'] = 'Macintosh Audio Compression/Expansion (MACE) 3:1'; + $QuicktimeAudioCodecLookup['MAC6'] = 'Macintosh Audio Compression/Expansion (MACE) 6:1'; + $QuicktimeAudioCodecLookup['mixb'] = '8-bit Mixer'; + $QuicktimeAudioCodecLookup['mixw'] = '16-bit Mixer'; + $QuicktimeAudioCodecLookup['mp4a'] = 'ISO/IEC 14496-3 AAC'; + $QuicktimeAudioCodecLookup['MS'."\x00\x02"] = 'Microsoft ADPCM'; + $QuicktimeAudioCodecLookup['MS'."\x00\x11"] = 'DV IMA'; + $QuicktimeAudioCodecLookup['MS'."\x00\x55"] = 'Fraunhofer MPEG Layer III'; + $QuicktimeAudioCodecLookup['NONE'] = 'No Encoding'; + $QuicktimeAudioCodecLookup['Qclp'] = 'Qualcomm PureVoice'; + $QuicktimeAudioCodecLookup['QDM2'] = 'QDesign Music 2'; + $QuicktimeAudioCodecLookup['QDMC'] = 'QDesign Music 1'; + $QuicktimeAudioCodecLookup['ratb'] = '8-bit Rate'; + $QuicktimeAudioCodecLookup['ratw'] = '16-bit Rate'; + $QuicktimeAudioCodecLookup['raw '] = 'raw PCM'; + $QuicktimeAudioCodecLookup['sour'] = 'Sound Source'; + $QuicktimeAudioCodecLookup['sowt'] = 'signed/two\'s complement (Little Endian)'; + $QuicktimeAudioCodecLookup['str1'] = 'Iomega MPEG layer II'; + $QuicktimeAudioCodecLookup['str2'] = 'Iomega MPEG *layer II'; + $QuicktimeAudioCodecLookup['str3'] = 'Iomega MPEG **layer II'; + $QuicktimeAudioCodecLookup['str4'] = 'Iomega MPEG ***layer II'; + $QuicktimeAudioCodecLookup['twos'] = 'signed/two\'s complement (Big Endian)'; + $QuicktimeAudioCodecLookup['ulaw'] = 'mu-law 2:1'; + } + return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : ''); + } + + public function QuicktimeDCOMLookup($compressionid) { + static $QuicktimeDCOMLookup = array(); + if (empty($QuicktimeDCOMLookup)) { + $QuicktimeDCOMLookup['zlib'] = 'ZLib Deflate'; + $QuicktimeDCOMLookup['adec'] = 'Apple Compression'; + } + return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : ''); + } + + public function QuicktimeColorNameLookup($colordepthid) { + static $QuicktimeColorNameLookup = array(); + if (empty($QuicktimeColorNameLookup)) { + $QuicktimeColorNameLookup[1] = '2-color (monochrome)'; + $QuicktimeColorNameLookup[2] = '4-color'; + $QuicktimeColorNameLookup[4] = '16-color'; + $QuicktimeColorNameLookup[8] = '256-color'; + $QuicktimeColorNameLookup[16] = 'thousands (16-bit color)'; + $QuicktimeColorNameLookup[24] = 'millions (24-bit color)'; + $QuicktimeColorNameLookup[32] = 'millions+ (32-bit color)'; + $QuicktimeColorNameLookup[33] = 'black & white'; + $QuicktimeColorNameLookup[34] = '4-gray'; + $QuicktimeColorNameLookup[36] = '16-gray'; + $QuicktimeColorNameLookup[40] = '256-gray'; + } + return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid'); + } + + public function QuicktimeSTIKLookup($stik) { + static $QuicktimeSTIKLookup = array(); + if (empty($QuicktimeSTIKLookup)) { + $QuicktimeSTIKLookup[0] = 'Movie'; + $QuicktimeSTIKLookup[1] = 'Normal'; + $QuicktimeSTIKLookup[2] = 'Audiobook'; + $QuicktimeSTIKLookup[5] = 'Whacked Bookmark'; + $QuicktimeSTIKLookup[6] = 'Music Video'; + $QuicktimeSTIKLookup[9] = 'Short Film'; + $QuicktimeSTIKLookup[10] = 'TV Show'; + $QuicktimeSTIKLookup[11] = 'Booklet'; + $QuicktimeSTIKLookup[14] = 'Ringtone'; + $QuicktimeSTIKLookup[21] = 'Podcast'; + } + return (isset($QuicktimeSTIKLookup[$stik]) ? $QuicktimeSTIKLookup[$stik] : 'invalid'); + } + + public function QuicktimeIODSaudioProfileName($audio_profile_id) { + static $QuicktimeIODSaudioProfileNameLookup = array(); + if (empty($QuicktimeIODSaudioProfileNameLookup)) { + $QuicktimeIODSaudioProfileNameLookup = array( + 0x00 => 'ISO Reserved (0x00)', + 0x01 => 'Main Audio Profile @ Level 1', + 0x02 => 'Main Audio Profile @ Level 2', + 0x03 => 'Main Audio Profile @ Level 3', + 0x04 => 'Main Audio Profile @ Level 4', + 0x05 => 'Scalable Audio Profile @ Level 1', + 0x06 => 'Scalable Audio Profile @ Level 2', + 0x07 => 'Scalable Audio Profile @ Level 3', + 0x08 => 'Scalable Audio Profile @ Level 4', + 0x09 => 'Speech Audio Profile @ Level 1', + 0x0A => 'Speech Audio Profile @ Level 2', + 0x0B => 'Synthetic Audio Profile @ Level 1', + 0x0C => 'Synthetic Audio Profile @ Level 2', + 0x0D => 'Synthetic Audio Profile @ Level 3', + 0x0E => 'High Quality Audio Profile @ Level 1', + 0x0F => 'High Quality Audio Profile @ Level 2', + 0x10 => 'High Quality Audio Profile @ Level 3', + 0x11 => 'High Quality Audio Profile @ Level 4', + 0x12 => 'High Quality Audio Profile @ Level 5', + 0x13 => 'High Quality Audio Profile @ Level 6', + 0x14 => 'High Quality Audio Profile @ Level 7', + 0x15 => 'High Quality Audio Profile @ Level 8', + 0x16 => 'Low Delay Audio Profile @ Level 1', + 0x17 => 'Low Delay Audio Profile @ Level 2', + 0x18 => 'Low Delay Audio Profile @ Level 3', + 0x19 => 'Low Delay Audio Profile @ Level 4', + 0x1A => 'Low Delay Audio Profile @ Level 5', + 0x1B => 'Low Delay Audio Profile @ Level 6', + 0x1C => 'Low Delay Audio Profile @ Level 7', + 0x1D => 'Low Delay Audio Profile @ Level 8', + 0x1E => 'Natural Audio Profile @ Level 1', + 0x1F => 'Natural Audio Profile @ Level 2', + 0x20 => 'Natural Audio Profile @ Level 3', + 0x21 => 'Natural Audio Profile @ Level 4', + 0x22 => 'Mobile Audio Internetworking Profile @ Level 1', + 0x23 => 'Mobile Audio Internetworking Profile @ Level 2', + 0x24 => 'Mobile Audio Internetworking Profile @ Level 3', + 0x25 => 'Mobile Audio Internetworking Profile @ Level 4', + 0x26 => 'Mobile Audio Internetworking Profile @ Level 5', + 0x27 => 'Mobile Audio Internetworking Profile @ Level 6', + 0x28 => 'AAC Profile @ Level 1', + 0x29 => 'AAC Profile @ Level 2', + 0x2A => 'AAC Profile @ Level 4', + 0x2B => 'AAC Profile @ Level 5', + 0x2C => 'High Efficiency AAC Profile @ Level 2', + 0x2D => 'High Efficiency AAC Profile @ Level 3', + 0x2E => 'High Efficiency AAC Profile @ Level 4', + 0x2F => 'High Efficiency AAC Profile @ Level 5', + 0xFE => 'Not part of MPEG-4 audio profiles', + 0xFF => 'No audio capability required', + ); + } + return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private'); + } + + + public function QuicktimeIODSvideoProfileName($video_profile_id) { + static $QuicktimeIODSvideoProfileNameLookup = array(); + if (empty($QuicktimeIODSvideoProfileNameLookup)) { + $QuicktimeIODSvideoProfileNameLookup = array( + 0x00 => 'Reserved (0x00) Profile', + 0x01 => 'Simple Profile @ Level 1', + 0x02 => 'Simple Profile @ Level 2', + 0x03 => 'Simple Profile @ Level 3', + 0x08 => 'Simple Profile @ Level 0', + 0x10 => 'Simple Scalable Profile @ Level 0', + 0x11 => 'Simple Scalable Profile @ Level 1', + 0x12 => 'Simple Scalable Profile @ Level 2', + 0x15 => 'AVC/H264 Profile', + 0x21 => 'Core Profile @ Level 1', + 0x22 => 'Core Profile @ Level 2', + 0x32 => 'Main Profile @ Level 2', + 0x33 => 'Main Profile @ Level 3', + 0x34 => 'Main Profile @ Level 4', + 0x42 => 'N-bit Profile @ Level 2', + 0x51 => 'Scalable Texture Profile @ Level 1', + 0x61 => 'Simple Face Animation Profile @ Level 1', + 0x62 => 'Simple Face Animation Profile @ Level 2', + 0x63 => 'Simple FBA Profile @ Level 1', + 0x64 => 'Simple FBA Profile @ Level 2', + 0x71 => 'Basic Animated Texture Profile @ Level 1', + 0x72 => 'Basic Animated Texture Profile @ Level 2', + 0x81 => 'Hybrid Profile @ Level 1', + 0x82 => 'Hybrid Profile @ Level 2', + 0x91 => 'Advanced Real Time Simple Profile @ Level 1', + 0x92 => 'Advanced Real Time Simple Profile @ Level 2', + 0x93 => 'Advanced Real Time Simple Profile @ Level 3', + 0x94 => 'Advanced Real Time Simple Profile @ Level 4', + 0xA1 => 'Core Scalable Profile @ Level1', + 0xA2 => 'Core Scalable Profile @ Level2', + 0xA3 => 'Core Scalable Profile @ Level3', + 0xB1 => 'Advanced Coding Efficiency Profile @ Level 1', + 0xB2 => 'Advanced Coding Efficiency Profile @ Level 2', + 0xB3 => 'Advanced Coding Efficiency Profile @ Level 3', + 0xB4 => 'Advanced Coding Efficiency Profile @ Level 4', + 0xC1 => 'Advanced Core Profile @ Level 1', + 0xC2 => 'Advanced Core Profile @ Level 2', + 0xD1 => 'Advanced Scalable Texture @ Level1', + 0xD2 => 'Advanced Scalable Texture @ Level2', + 0xE1 => 'Simple Studio Profile @ Level 1', + 0xE2 => 'Simple Studio Profile @ Level 2', + 0xE3 => 'Simple Studio Profile @ Level 3', + 0xE4 => 'Simple Studio Profile @ Level 4', + 0xE5 => 'Core Studio Profile @ Level 1', + 0xE6 => 'Core Studio Profile @ Level 2', + 0xE7 => 'Core Studio Profile @ Level 3', + 0xE8 => 'Core Studio Profile @ Level 4', + 0xF0 => 'Advanced Simple Profile @ Level 0', + 0xF1 => 'Advanced Simple Profile @ Level 1', + 0xF2 => 'Advanced Simple Profile @ Level 2', + 0xF3 => 'Advanced Simple Profile @ Level 3', + 0xF4 => 'Advanced Simple Profile @ Level 4', + 0xF5 => 'Advanced Simple Profile @ Level 5', + 0xF7 => 'Advanced Simple Profile @ Level 3b', + 0xF8 => 'Fine Granularity Scalable Profile @ Level 0', + 0xF9 => 'Fine Granularity Scalable Profile @ Level 1', + 0xFA => 'Fine Granularity Scalable Profile @ Level 2', + 0xFB => 'Fine Granularity Scalable Profile @ Level 3', + 0xFC => 'Fine Granularity Scalable Profile @ Level 4', + 0xFD => 'Fine Granularity Scalable Profile @ Level 5', + 0xFE => 'Not part of MPEG-4 Visual profiles', + 0xFF => 'No visual capability required', + ); + } + return (isset($QuicktimeIODSvideoProfileNameLookup[$video_profile_id]) ? $QuicktimeIODSvideoProfileNameLookup[$video_profile_id] : 'ISO Reserved Profile'); + } + + + public function QuicktimeContentRatingLookup($rtng) { + static $QuicktimeContentRatingLookup = array(); + if (empty($QuicktimeContentRatingLookup)) { + $QuicktimeContentRatingLookup[0] = 'None'; + $QuicktimeContentRatingLookup[2] = 'Clean'; + $QuicktimeContentRatingLookup[4] = 'Explicit'; + } + return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid'); + } + + public function QuicktimeStoreAccountTypeLookup($akid) { + static $QuicktimeStoreAccountTypeLookup = array(); + if (empty($QuicktimeStoreAccountTypeLookup)) { + $QuicktimeStoreAccountTypeLookup[0] = 'iTunes'; + $QuicktimeStoreAccountTypeLookup[1] = 'AOL'; + } + return (isset($QuicktimeStoreAccountTypeLookup[$akid]) ? $QuicktimeStoreAccountTypeLookup[$akid] : 'invalid'); + } + + public function QuicktimeStoreFrontCodeLookup($sfid) { + static $QuicktimeStoreFrontCodeLookup = array(); + if (empty($QuicktimeStoreFrontCodeLookup)) { + $QuicktimeStoreFrontCodeLookup[143460] = 'Australia'; + $QuicktimeStoreFrontCodeLookup[143445] = 'Austria'; + $QuicktimeStoreFrontCodeLookup[143446] = 'Belgium'; + $QuicktimeStoreFrontCodeLookup[143455] = 'Canada'; + $QuicktimeStoreFrontCodeLookup[143458] = 'Denmark'; + $QuicktimeStoreFrontCodeLookup[143447] = 'Finland'; + $QuicktimeStoreFrontCodeLookup[143442] = 'France'; + $QuicktimeStoreFrontCodeLookup[143443] = 'Germany'; + $QuicktimeStoreFrontCodeLookup[143448] = 'Greece'; + $QuicktimeStoreFrontCodeLookup[143449] = 'Ireland'; + $QuicktimeStoreFrontCodeLookup[143450] = 'Italy'; + $QuicktimeStoreFrontCodeLookup[143462] = 'Japan'; + $QuicktimeStoreFrontCodeLookup[143451] = 'Luxembourg'; + $QuicktimeStoreFrontCodeLookup[143452] = 'Netherlands'; + $QuicktimeStoreFrontCodeLookup[143461] = 'New Zealand'; + $QuicktimeStoreFrontCodeLookup[143457] = 'Norway'; + $QuicktimeStoreFrontCodeLookup[143453] = 'Portugal'; + $QuicktimeStoreFrontCodeLookup[143454] = 'Spain'; + $QuicktimeStoreFrontCodeLookup[143456] = 'Sweden'; + $QuicktimeStoreFrontCodeLookup[143459] = 'Switzerland'; + $QuicktimeStoreFrontCodeLookup[143444] = 'United Kingdom'; + $QuicktimeStoreFrontCodeLookup[143441] = 'United States'; + } + return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid'); + } + + public function QuicktimeParseNikonNCTG($atom_data) { + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG + // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100 + // Data is stored as records of: + // * 4 bytes record type + // * 2 bytes size of data field type: + // 0x0001 = flag (size field *= 1-byte) + // 0x0002 = char (size field *= 1-byte) + // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB + // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD + // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together + // 0x0007 = bytes (size field *= 1-byte), values are stored as ?????? + // 0x0008 = ????? (size field *= 2-byte), values are stored as ?????? + // * 2 bytes data size field + // * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15") + // all integers are stored BigEndian + + $NCTGtagName = array( + 0x00000001 => 'Make', + 0x00000002 => 'Model', + 0x00000003 => 'Software', + 0x00000011 => 'CreateDate', + 0x00000012 => 'DateTimeOriginal', + 0x00000013 => 'FrameCount', + 0x00000016 => 'FrameRate', + 0x00000022 => 'FrameWidth', + 0x00000023 => 'FrameHeight', + 0x00000032 => 'AudioChannels', + 0x00000033 => 'AudioBitsPerSample', + 0x00000034 => 'AudioSampleRate', + 0x02000001 => 'MakerNoteVersion', + 0x02000005 => 'WhiteBalance', + 0x0200000b => 'WhiteBalanceFineTune', + 0x0200001e => 'ColorSpace', + 0x02000023 => 'PictureControlData', + 0x02000024 => 'WorldTime', + 0x02000032 => 'UnknownInfo', + 0x02000083 => 'LensType', + 0x02000084 => 'Lens', + ); + + $offset = 0; + $datalength = strlen($atom_data); + $parsed = array(); + while ($offset < $datalength) { +//echo getid3_lib::PrintHexBytes(substr($atom_data, $offset, 4)).'
'; + $record_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4; + $data_size_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; + $data_size = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2; + switch ($data_size_type) { + case 0x0001: // 0x0001 = flag (size field *= 1-byte) + $data = getid3_lib::BigEndian2Int(substr($atom_data, $offset, $data_size * 1)); + $offset += ($data_size * 1); + break; + case 0x0002: // 0x0002 = char (size field *= 1-byte) + $data = substr($atom_data, $offset, $data_size * 1); + $offset += ($data_size * 1); + $data = rtrim($data, "\x00"); + break; + case 0x0003: // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB + $data = ''; + for ($i = $data_size - 1; $i >= 0; $i--) { + $data .= substr($atom_data, $offset + ($i * 2), 2); + } + $data = getid3_lib::BigEndian2Int($data); + $offset += ($data_size * 2); + break; + case 0x0004: // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD + $data = ''; + for ($i = $data_size - 1; $i >= 0; $i--) { + $data .= substr($atom_data, $offset + ($i * 4), 4); + } + $data = getid3_lib::BigEndian2Int($data); + $offset += ($data_size * 4); + break; + case 0x0005: // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together + $data = array(); + for ($i = 0; $i < $data_size; $i++) { + $numerator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 0, 4)); + $denomninator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 4, 4)); + if ($denomninator == 0) { + $data[$i] = false; + } else { + $data[$i] = (double) $numerator / $denomninator; + } + } + $offset += (8 * $data_size); + if (count($data) == 1) { + $data = $data[0]; + } + break; + case 0x0007: // 0x0007 = bytes (size field *= 1-byte), values are stored as ?????? + $data = substr($atom_data, $offset, $data_size * 1); + $offset += ($data_size * 1); + break; + case 0x0008: // 0x0008 = ????? (size field *= 2-byte), values are stored as ?????? + $data = substr($atom_data, $offset, $data_size * 2); + $offset += ($data_size * 2); + break; + default: +echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'
'; + break 2; + } + + switch ($record_type) { + case 0x00000011: // CreateDate + case 0x00000012: // DateTimeOriginal + $data = strtotime($data); + break; + case 0x0200001e: // ColorSpace + switch ($data) { + case 1: + $data = 'sRGB'; + break; + case 2: + $data = 'Adobe RGB'; + break; + } + break; + case 0x02000023: // PictureControlData + $PictureControlAdjust = array(0=>'default', 1=>'quick', 2=>'full'); + $FilterEffect = array(0x80=>'off', 0x81=>'yellow', 0x82=>'orange', 0x83=>'red', 0x84=>'green', 0xff=>'n/a'); + $ToningEffect = array(0x80=>'b&w', 0x81=>'sepia', 0x82=>'cyanotype', 0x83=>'red', 0x84=>'yellow', 0x85=>'green', 0x86=>'blue-green', 0x87=>'blue', 0x88=>'purple-blue', 0x89=>'red-purple', 0xff=>'n/a'); + $data = array( + 'PictureControlVersion' => substr($data, 0, 4), + 'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"), + 'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"), + //'?' => substr($data, 44, 4), + 'PictureControlAdjust' => $PictureControlAdjust[ord(substr($data, 48, 1))], + 'PictureControlQuickAdjust' => ord(substr($data, 49, 1)), + 'Sharpness' => ord(substr($data, 50, 1)), + 'Contrast' => ord(substr($data, 51, 1)), + 'Brightness' => ord(substr($data, 52, 1)), + 'Saturation' => ord(substr($data, 53, 1)), + 'HueAdjustment' => ord(substr($data, 54, 1)), + 'FilterEffect' => $FilterEffect[ord(substr($data, 55, 1))], + 'ToningEffect' => $ToningEffect[ord(substr($data, 56, 1))], + 'ToningSaturation' => ord(substr($data, 57, 1)), + ); + break; + case 0x02000024: // WorldTime + // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#WorldTime + // timezone is stored as offset from GMT in minutes + $timezone = getid3_lib::BigEndian2Int(substr($data, 0, 2)); + if ($timezone & 0x8000) { + $timezone = 0 - (0x10000 - $timezone); + } + $timezone /= 60; + + $dst = (bool) getid3_lib::BigEndian2Int(substr($data, 2, 1)); + switch (getid3_lib::BigEndian2Int(substr($data, 3, 1))) { + case 2: + $datedisplayformat = 'D/M/Y'; break; + case 1: + $datedisplayformat = 'M/D/Y'; break; + case 0: + default: + $datedisplayformat = 'Y/M/D'; break; + } + + $data = array('timezone'=>floatval($timezone), 'dst'=>$dst, 'display'=>$datedisplayformat); + break; + case 0x02000083: // LensType + $data = array( + //'_' => $data, + 'mf' => (bool) ($data & 0x01), + 'd' => (bool) ($data & 0x02), + 'g' => (bool) ($data & 0x04), + 'vr' => (bool) ($data & 0x08), + ); + break; + } + $tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x'.str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT)); + $parsed[$tag_name] = $data; + } + return $parsed; + } + + + public function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') { + static $handyatomtranslatorarray = array(); + if (empty($handyatomtranslatorarray)) { + $handyatomtranslatorarray['cpy'] = 'copyright'; + $handyatomtranslatorarray['day'] = 'creation_date'; // iTunes 4.0 + $handyatomtranslatorarray['dir'] = 'director'; + $handyatomtranslatorarray['ed1'] = 'edit1'; + $handyatomtranslatorarray['ed2'] = 'edit2'; + $handyatomtranslatorarray['ed3'] = 'edit3'; + $handyatomtranslatorarray['ed4'] = 'edit4'; + $handyatomtranslatorarray['ed5'] = 'edit5'; + $handyatomtranslatorarray['ed6'] = 'edit6'; + $handyatomtranslatorarray['ed7'] = 'edit7'; + $handyatomtranslatorarray['ed8'] = 'edit8'; + $handyatomtranslatorarray['ed9'] = 'edit9'; + $handyatomtranslatorarray['fmt'] = 'format'; + $handyatomtranslatorarray['inf'] = 'information'; + $handyatomtranslatorarray['prd'] = 'producer'; + $handyatomtranslatorarray['prf'] = 'performers'; + $handyatomtranslatorarray['req'] = 'system_requirements'; + $handyatomtranslatorarray['src'] = 'source_credit'; + $handyatomtranslatorarray['wrt'] = 'writer'; + + // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt + $handyatomtranslatorarray['nam'] = 'title'; // iTunes 4.0 + $handyatomtranslatorarray['cmt'] = 'comment'; // iTunes 4.0 + $handyatomtranslatorarray['wrn'] = 'warning'; + $handyatomtranslatorarray['hst'] = 'host_computer'; + $handyatomtranslatorarray['mak'] = 'make'; + $handyatomtranslatorarray['mod'] = 'model'; + $handyatomtranslatorarray['PRD'] = 'product'; + $handyatomtranslatorarray['swr'] = 'software'; + $handyatomtranslatorarray['aut'] = 'author'; + $handyatomtranslatorarray['ART'] = 'artist'; + $handyatomtranslatorarray['trk'] = 'track'; + $handyatomtranslatorarray['alb'] = 'album'; // iTunes 4.0 + $handyatomtranslatorarray['com'] = 'comment'; + $handyatomtranslatorarray['gen'] = 'genre'; // iTunes 4.0 + $handyatomtranslatorarray['ope'] = 'composer'; + $handyatomtranslatorarray['url'] = 'url'; + $handyatomtranslatorarray['enc'] = 'encoder'; + + // http://atomicparsley.sourceforge.net/mpeg-4files.html + $handyatomtranslatorarray['art'] = 'artist'; // iTunes 4.0 + $handyatomtranslatorarray['aART'] = 'album_artist'; + $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0 + $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0 + $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0 + $handyatomtranslatorarray['too'] = 'encoder'; // iTunes 4.0 + $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0 + $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0? + $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0 + $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0 + $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0 + $handyatomtranslatorarray['grp'] = 'grouping'; // iTunes 4.2 + $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9 + $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9 + $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9 + $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9 + $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9 + $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9 + $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0 + $handyatomtranslatorarray['lyr'] = 'lyrics'; // iTunes 5.0 + $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0 + $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0 + $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0 + $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0 + $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2 + $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0 + + // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt + + + + // boxnames: + /* + $handyatomtranslatorarray['iTunSMPB'] = 'iTunSMPB'; + $handyatomtranslatorarray['iTunNORM'] = 'iTunNORM'; + $handyatomtranslatorarray['Encoding Params'] = 'Encoding Params'; + $handyatomtranslatorarray['replaygain_track_gain'] = 'replaygain_track_gain'; + $handyatomtranslatorarray['replaygain_track_peak'] = 'replaygain_track_peak'; + $handyatomtranslatorarray['replaygain_track_minmax'] = 'replaygain_track_minmax'; + $handyatomtranslatorarray['MusicIP PUID'] = 'MusicIP PUID'; + $handyatomtranslatorarray['MusicBrainz Artist Id'] = 'MusicBrainz Artist Id'; + $handyatomtranslatorarray['MusicBrainz Album Id'] = 'MusicBrainz Album Id'; + $handyatomtranslatorarray['MusicBrainz Album Artist Id'] = 'MusicBrainz Album Artist Id'; + $handyatomtranslatorarray['MusicBrainz Track Id'] = 'MusicBrainz Track Id'; + $handyatomtranslatorarray['MusicBrainz Disc Id'] = 'MusicBrainz Disc Id'; + + // http://age.hobba.nl/audio/tag_frame_reference.html + $handyatomtranslatorarray['PLAY_COUNTER'] = 'play_counter'; // Foobar2000 - http://www.getid3.org/phpBB3/viewtopic.php?t=1355 + $handyatomtranslatorarray['MEDIATYPE'] = 'mediatype'; // Foobar2000 - http://www.getid3.org/phpBB3/viewtopic.php?t=1355 + */ + } + $info = &$this->getid3->info; + $comment_key = ''; + if ($boxname && ($boxname != $keyname)) { + $comment_key = (isset($handyatomtranslatorarray[$boxname]) ? $handyatomtranslatorarray[$boxname] : $boxname); + } elseif (isset($handyatomtranslatorarray[$keyname])) { + $comment_key = $handyatomtranslatorarray[$keyname]; + } + if ($comment_key) { + if ($comment_key == 'picture') { + if (!is_array($data)) { + $image_mime = ''; + if (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $data)) { + $image_mime = 'image/png'; + } elseif (preg_match('#^\xFF\xD8\xFF#', $data)) { + $image_mime = 'image/jpeg'; + } elseif (preg_match('#^GIF#', $data)) { + $image_mime = 'image/gif'; + } elseif (preg_match('#^BM#', $data)) { + $image_mime = 'image/bmp'; + } + $data = array('data'=>$data, 'image_mime'=>$image_mime); + } + } + $info['quicktime']['comments'][$comment_key][] = $data; + } + return true; + } + + public function NoNullString($nullterminatedstring) { + // remove the single null terminator on null terminated strings + if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") { + return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1); + } + return $nullterminatedstring; + } + + public function Pascal2String($pascalstring) { + // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string + return substr($pascalstring, 1); + } + +} diff --git a/extensions/TimedMediaHandler/libs/getid3/module.audio-video.riff.php b/extensions/TimedMediaHandler/libs/getid3/module.audio-video.riff.php new file mode 100644 index 00000000..5ac5d1cd --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/module.audio-video.riff.php @@ -0,0 +1,2435 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.riff.php // +// module for analyzing RIFF files // +// multiple formats supported by this module: // +// Wave, AVI, AIFF/AIFC, (MP3,AC3)/RIFF, Wavpack v3, 8SVX // +// dependencies: module.audio.mp3.php // +// module.audio.ac3.php // +// module.audio.dts.php // +// /// +///////////////////////////////////////////////////////////////// + +/** +* @todo Parse AC-3/DTS audio inside WAVE correctly +* @todo Rewrite RIFF parser totally +*/ + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true); +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.dts.php', __FILE__, true); + +class getid3_riff extends getid3_handler +{ + + public function Analyze() { + $info = &$this->getid3->info; + + // initialize these values to an empty array, otherwise they default to NULL + // and you can't append array values to a NULL value + $info['riff'] = array('raw'=>array()); + + // Shortcuts + $thisfile_riff = &$info['riff']; + $thisfile_riff_raw = &$thisfile_riff['raw']; + $thisfile_audio = &$info['audio']; + $thisfile_video = &$info['video']; + $thisfile_audio_dataformat = &$thisfile_audio['dataformat']; + $thisfile_riff_audio = &$thisfile_riff['audio']; + $thisfile_riff_video = &$thisfile_riff['video']; + + $Original['avdataoffset'] = $info['avdataoffset']; + $Original['avdataend'] = $info['avdataend']; + + $this->fseek($info['avdataoffset']); + $RIFFheader = $this->fread(12); + $offset = $this->ftell(); + $RIFFtype = substr($RIFFheader, 0, 4); + $RIFFsize = substr($RIFFheader, 4, 4); + $RIFFsubtype = substr($RIFFheader, 8, 4); + + switch ($RIFFtype) { + + case 'FORM': // AIFF, AIFC + $info['fileformat'] = 'aiff'; + $thisfile_riff['header_size'] = $this->EitherEndian2Int($RIFFsize); + $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4)); + break; + + case 'RIFF': // AVI, WAV, etc + case 'SDSS': // SDSS is identical to RIFF, just renamed. Used by SmartSound QuickTracks (www.smartsound.com) + case 'RMP3': // RMP3 is identical to RIFF, just renamed. Used by [unknown program] when creating RIFF-MP3s + $info['fileformat'] = 'riff'; + $thisfile_riff['header_size'] = $this->EitherEndian2Int($RIFFsize); + if ($RIFFsubtype == 'RMP3') { + // RMP3 is identical to WAVE, just renamed. Used by [unknown program] when creating RIFF-MP3s + $RIFFsubtype = 'WAVE'; + } + $thisfile_riff[$RIFFsubtype] = $this->ParseRIFF($offset, ($offset + $thisfile_riff['header_size'] - 4)); + if (($info['avdataend'] - $info['filesize']) == 1) { + // LiteWave appears to incorrectly *not* pad actual output file + // to nearest WORD boundary so may appear to be short by one + // byte, in which case - skip warning + $info['avdataend'] = $info['filesize']; + } + + $nextRIFFoffset = $Original['avdataoffset'] + 8 + $thisfile_riff['header_size']; // 8 = "RIFF" + 32-bit offset + while ($nextRIFFoffset < min($info['filesize'], $info['avdataend'])) { + try { + $this->fseek($nextRIFFoffset); + } catch (getid3_exception $e) { + if ($e->getCode() == 10) { + //$this->warning('RIFF parser: '.$e->getMessage()); + $this->error('AVI extends beyond '.round(PHP_INT_MAX / 1073741824).'GB and PHP filesystem functions cannot read that far, playtime may be wrong'); + $this->warning('[avdataend] value may be incorrect, multiple AVIX chunks may be present'); + break; + } else { + throw $e; + } + } + $nextRIFFheader = $this->fread(12); + if ($nextRIFFoffset == ($info['avdataend'] - 1)) { + if (substr($nextRIFFheader, 0, 1) == "\x00") { + // RIFF padded to WORD boundary, we're actually already at the end + break; + } + } + $nextRIFFheaderID = substr($nextRIFFheader, 0, 4); + $nextRIFFsize = $this->EitherEndian2Int(substr($nextRIFFheader, 4, 4)); + $nextRIFFtype = substr($nextRIFFheader, 8, 4); + $chunkdata = array(); + $chunkdata['offset'] = $nextRIFFoffset + 8; + $chunkdata['size'] = $nextRIFFsize; + $nextRIFFoffset = $chunkdata['offset'] + $chunkdata['size']; + + switch ($nextRIFFheaderID) { + + case 'RIFF': + $chunkdata['chunks'] = $this->ParseRIFF($chunkdata['offset'] + 4, $nextRIFFoffset); + + if (!isset($thisfile_riff[$nextRIFFtype])) { + $thisfile_riff[$nextRIFFtype] = array(); + } + $thisfile_riff[$nextRIFFtype][] = $chunkdata; + break; + + case 'JUNK': + // ignore + $thisfile_riff[$nextRIFFheaderID][] = $chunkdata; + break; + + case 'IDVX': + $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunkdata['size'])); + break; + + default: + if ($info['filesize'] == ($chunkdata['offset'] - 8 + 128)) { + $DIVXTAG = $nextRIFFheader.$this->fread(128 - 12); + if (substr($DIVXTAG, -7) == 'DIVXTAG') { + // DIVXTAG is supposed to be inside an IDVX chunk in a LIST chunk, but some bad encoders just slap it on the end of a file + $this->warning('Found wrongly-structured DIVXTAG at offset '.($this->ftell() - 128).', parsing anyway'); + $info['divxtag']['comments'] = self::ParseDIVXTAG($DIVXTAG); + break 2; + } + } + $this->warning('Expecting "RIFF|JUNK|IDVX" at '.$nextRIFFoffset.', found "'.$nextRIFFheaderID.'" ('.getid3_lib::PrintHexBytes($nextRIFFheaderID).') - skipping rest of file'); + break 2; + + } + + } + if ($RIFFsubtype == 'WAVE') { + $thisfile_riff_WAVE = &$thisfile_riff['WAVE']; + } + break; + + default: + $this->error('Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead'); + unset($info['fileformat']); + return false; + } + + $streamindex = 0; + switch ($RIFFsubtype) { + case 'WAVE': + if (empty($thisfile_audio['bitrate_mode'])) { + $thisfile_audio['bitrate_mode'] = 'cbr'; + } + if (empty($thisfile_audio_dataformat)) { + $thisfile_audio_dataformat = 'wav'; + } + + if (isset($thisfile_riff_WAVE['data'][0]['offset'])) { + $info['avdataoffset'] = $thisfile_riff_WAVE['data'][0]['offset'] + 8; + $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff_WAVE['data'][0]['size']; + } + if (isset($thisfile_riff_WAVE['fmt '][0]['data'])) { + + $thisfile_riff_audio[$streamindex] = self::parseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']); + $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; + if (!isset($thisfile_riff_audio[$streamindex]['bitrate']) || ($thisfile_riff_audio[$streamindex]['bitrate'] == 0)) { + $info['error'][] = 'Corrupt RIFF file: bitrate_audio == zero'; + return false; + } + $thisfile_riff_raw['fmt '] = $thisfile_riff_audio[$streamindex]['raw']; + unset($thisfile_riff_audio[$streamindex]['raw']); + $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex]; + + $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); + if (substr($thisfile_audio['codec'], 0, strlen('unknown: 0x')) == 'unknown: 0x') { + $info['warning'][] = 'Audio codec = '.$thisfile_audio['codec']; + } + $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; + + if (empty($info['playtime_seconds'])) { // may already be set (e.g. DTS-WAV) + $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); + } + + $thisfile_audio['lossless'] = false; + if (isset($thisfile_riff_WAVE['data'][0]['offset']) && isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { + switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { + + case 0x0001: // PCM + $thisfile_audio['lossless'] = true; + break; + + case 0x2000: // AC-3 + $thisfile_audio_dataformat = 'ac3'; + break; + + default: + // do nothing + break; + + } + } + $thisfile_audio['streams'][$streamindex]['wformattag'] = $thisfile_audio['wformattag']; + $thisfile_audio['streams'][$streamindex]['bitrate_mode'] = $thisfile_audio['bitrate_mode']; + $thisfile_audio['streams'][$streamindex]['lossless'] = $thisfile_audio['lossless']; + $thisfile_audio['streams'][$streamindex]['dataformat'] = $thisfile_audio_dataformat; + } + + if (isset($thisfile_riff_WAVE['rgad'][0]['data'])) { + + // shortcuts + $rgadData = &$thisfile_riff_WAVE['rgad'][0]['data']; + $thisfile_riff_raw['rgad'] = array('track'=>array(), 'album'=>array()); + $thisfile_riff_raw_rgad = &$thisfile_riff_raw['rgad']; + $thisfile_riff_raw_rgad_track = &$thisfile_riff_raw_rgad['track']; + $thisfile_riff_raw_rgad_album = &$thisfile_riff_raw_rgad['album']; + + $thisfile_riff_raw_rgad['fPeakAmplitude'] = getid3_lib::LittleEndian2Float(substr($rgadData, 0, 4)); + $thisfile_riff_raw_rgad['nRadioRgAdjust'] = $this->EitherEndian2Int(substr($rgadData, 4, 2)); + $thisfile_riff_raw_rgad['nAudiophileRgAdjust'] = $this->EitherEndian2Int(substr($rgadData, 6, 2)); + + $nRadioRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nRadioRgAdjust']), 16, '0', STR_PAD_LEFT); + $nAudiophileRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nAudiophileRgAdjust']), 16, '0', STR_PAD_LEFT); + $thisfile_riff_raw_rgad_track['name'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 0, 3)); + $thisfile_riff_raw_rgad_track['originator'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 3, 3)); + $thisfile_riff_raw_rgad_track['signbit'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 6, 1)); + $thisfile_riff_raw_rgad_track['adjustment'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 7, 9)); + $thisfile_riff_raw_rgad_album['name'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 0, 3)); + $thisfile_riff_raw_rgad_album['originator'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 3, 3)); + $thisfile_riff_raw_rgad_album['signbit'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 6, 1)); + $thisfile_riff_raw_rgad_album['adjustment'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 7, 9)); + + $thisfile_riff['rgad']['peakamplitude'] = $thisfile_riff_raw_rgad['fPeakAmplitude']; + if (($thisfile_riff_raw_rgad_track['name'] != 0) && ($thisfile_riff_raw_rgad_track['originator'] != 0)) { + $thisfile_riff['rgad']['track']['name'] = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_track['name']); + $thisfile_riff['rgad']['track']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_track['originator']); + $thisfile_riff['rgad']['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_track['adjustment'], $thisfile_riff_raw_rgad_track['signbit']); + } + if (($thisfile_riff_raw_rgad_album['name'] != 0) && ($thisfile_riff_raw_rgad_album['originator'] != 0)) { + $thisfile_riff['rgad']['album']['name'] = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_album['name']); + $thisfile_riff['rgad']['album']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_album['originator']); + $thisfile_riff['rgad']['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_album['adjustment'], $thisfile_riff_raw_rgad_album['signbit']); + } + } + + if (isset($thisfile_riff_WAVE['fact'][0]['data'])) { + $thisfile_riff_raw['fact']['NumberOfSamples'] = $this->EitherEndian2Int(substr($thisfile_riff_WAVE['fact'][0]['data'], 0, 4)); + + // This should be a good way of calculating exact playtime, + // but some sample files have had incorrect number of samples, + // so cannot use this method + + // if (!empty($thisfile_riff_raw['fmt ']['nSamplesPerSec'])) { + // $info['playtime_seconds'] = (float) $thisfile_riff_raw['fact']['NumberOfSamples'] / $thisfile_riff_raw['fmt ']['nSamplesPerSec']; + // } + } + if (!empty($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'])) { + $thisfile_audio['bitrate'] = getid3_lib::CastAsInt($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'] * 8); + } + + if (isset($thisfile_riff_WAVE['bext'][0]['data'])) { + // shortcut + $thisfile_riff_WAVE_bext_0 = &$thisfile_riff_WAVE['bext'][0]; + + $thisfile_riff_WAVE_bext_0['title'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 0, 256)); + $thisfile_riff_WAVE_bext_0['author'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 256, 32)); + $thisfile_riff_WAVE_bext_0['reference'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 288, 32)); + $thisfile_riff_WAVE_bext_0['origin_date'] = substr($thisfile_riff_WAVE_bext_0['data'], 320, 10); + $thisfile_riff_WAVE_bext_0['origin_time'] = substr($thisfile_riff_WAVE_bext_0['data'], 330, 8); + $thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338, 8)); + $thisfile_riff_WAVE_bext_0['bwf_version'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 346, 1)); + $thisfile_riff_WAVE_bext_0['reserved'] = substr($thisfile_riff_WAVE_bext_0['data'], 347, 254); + $thisfile_riff_WAVE_bext_0['coding_history'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601))); + if (preg_match('#^([0-9]{4}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_date'], $matches_bext_date)) { + if (preg_match('#^([0-9]{2}).([0-9]{2}).([0-9]{2})$#', $thisfile_riff_WAVE_bext_0['origin_time'], $matches_bext_time)) { + list($dummy, $bext_timestamp['year'], $bext_timestamp['month'], $bext_timestamp['day']) = $matches_bext_date; + list($dummy, $bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second']) = $matches_bext_time; + $thisfile_riff_WAVE_bext_0['origin_date_unix'] = gmmktime($bext_timestamp['hour'], $bext_timestamp['minute'], $bext_timestamp['second'], $bext_timestamp['month'], $bext_timestamp['day'], $bext_timestamp['year']); + } else { + $info['warning'][] = 'RIFF.WAVE.BEXT.origin_time is invalid'; + } + } else { + $info['warning'][] = 'RIFF.WAVE.BEXT.origin_date is invalid'; + } + $thisfile_riff['comments']['author'][] = $thisfile_riff_WAVE_bext_0['author']; + $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_bext_0['title']; + } + + if (isset($thisfile_riff_WAVE['MEXT'][0]['data'])) { + // shortcut + $thisfile_riff_WAVE_MEXT_0 = &$thisfile_riff_WAVE['MEXT'][0]; + + $thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 0, 2)); + $thisfile_riff_WAVE_MEXT_0['flags']['homogenous'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0001); + if ($thisfile_riff_WAVE_MEXT_0['flags']['homogenous']) { + $thisfile_riff_WAVE_MEXT_0['flags']['padding'] = ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0002) ? false : true; + $thisfile_riff_WAVE_MEXT_0['flags']['22_or_44'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0004); + $thisfile_riff_WAVE_MEXT_0['flags']['free_format'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0008); + + $thisfile_riff_WAVE_MEXT_0['nominal_frame_size'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 2, 2)); + } + $thisfile_riff_WAVE_MEXT_0['anciliary_data_length'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 6, 2)); + $thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 8, 2)); + $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_left'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0001); + $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_free'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0002); + $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_right'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0004); + } + + if (isset($thisfile_riff_WAVE['cart'][0]['data'])) { + // shortcut + $thisfile_riff_WAVE_cart_0 = &$thisfile_riff_WAVE['cart'][0]; + + $thisfile_riff_WAVE_cart_0['version'] = substr($thisfile_riff_WAVE_cart_0['data'], 0, 4); + $thisfile_riff_WAVE_cart_0['title'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 4, 64)); + $thisfile_riff_WAVE_cart_0['artist'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 68, 64)); + $thisfile_riff_WAVE_cart_0['cut_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 132, 64)); + $thisfile_riff_WAVE_cart_0['client_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 196, 64)); + $thisfile_riff_WAVE_cart_0['category'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 260, 64)); + $thisfile_riff_WAVE_cart_0['classification'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 324, 64)); + $thisfile_riff_WAVE_cart_0['out_cue'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 388, 64)); + $thisfile_riff_WAVE_cart_0['start_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 452, 10)); + $thisfile_riff_WAVE_cart_0['start_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 462, 8)); + $thisfile_riff_WAVE_cart_0['end_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 470, 10)); + $thisfile_riff_WAVE_cart_0['end_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 480, 8)); + $thisfile_riff_WAVE_cart_0['producer_app_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 488, 64)); + $thisfile_riff_WAVE_cart_0['producer_app_version'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 552, 64)); + $thisfile_riff_WAVE_cart_0['user_defined_text'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 616, 64)); + $thisfile_riff_WAVE_cart_0['zero_db_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 680, 4), true); + for ($i = 0; $i < 8; $i++) { + $thisfile_riff_WAVE_cart_0['post_time'][$i]['usage_fourcc'] = substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8), 4); + $thisfile_riff_WAVE_cart_0['post_time'][$i]['timer_value'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8) + 4, 4)); + } + $thisfile_riff_WAVE_cart_0['url'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 748, 1024)); + $thisfile_riff_WAVE_cart_0['tag_text'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_cart_0['data'], 1772))); + + $thisfile_riff['comments']['artist'][] = $thisfile_riff_WAVE_cart_0['artist']; + $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_cart_0['title']; + } + + if (isset($thisfile_riff_WAVE['SNDM'][0]['data'])) { + // SoundMiner metadata + + // shortcuts + $thisfile_riff_WAVE_SNDM_0 = &$thisfile_riff_WAVE['SNDM'][0]; + $thisfile_riff_WAVE_SNDM_0_data = &$thisfile_riff_WAVE_SNDM_0['data']; + $SNDM_startoffset = 0; + $SNDM_endoffset = $thisfile_riff_WAVE_SNDM_0['size']; + + while ($SNDM_startoffset < $SNDM_endoffset) { + $SNDM_thisTagOffset = 0; + $SNDM_thisTagSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4)); + $SNDM_thisTagOffset += 4; + $SNDM_thisTagKey = substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 4); + $SNDM_thisTagOffset += 4; + $SNDM_thisTagDataSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2)); + $SNDM_thisTagOffset += 2; + $SNDM_thisTagDataFlags = getid3_lib::BigEndian2Int(substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, 2)); + $SNDM_thisTagOffset += 2; + $SNDM_thisTagDataText = substr($thisfile_riff_WAVE_SNDM_0_data, $SNDM_startoffset + $SNDM_thisTagOffset, $SNDM_thisTagDataSize); + $SNDM_thisTagOffset += $SNDM_thisTagDataSize; + + if ($SNDM_thisTagSize != (4 + 4 + 2 + 2 + $SNDM_thisTagDataSize)) { + $info['warning'][] = 'RIFF.WAVE.SNDM.data contains tag not expected length (expected: '.$SNDM_thisTagSize.', found: '.(4 + 4 + 2 + 2 + $SNDM_thisTagDataSize).') at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; + break; + } elseif ($SNDM_thisTagSize <= 0) { + $info['warning'][] = 'RIFF.WAVE.SNDM.data contains zero-size tag at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; + break; + } + $SNDM_startoffset += $SNDM_thisTagSize; + + $thisfile_riff_WAVE_SNDM_0['parsed_raw'][$SNDM_thisTagKey] = $SNDM_thisTagDataText; + if ($parsedkey = self::waveSNDMtagLookup($SNDM_thisTagKey)) { + $thisfile_riff_WAVE_SNDM_0['parsed'][$parsedkey] = $SNDM_thisTagDataText; + } else { + $info['warning'][] = 'RIFF.WAVE.SNDM contains unknown tag "'.$SNDM_thisTagKey.'" at offset '.$SNDM_startoffset.' (file offset '.($thisfile_riff_WAVE_SNDM_0['offset'] + $SNDM_startoffset).')'; + } + } + + $tagmapping = array( + 'tracktitle'=>'title', + 'category' =>'genre', + 'cdtitle' =>'album', + 'tracktitle'=>'title', + ); + foreach ($tagmapping as $fromkey => $tokey) { + if (isset($thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey])) { + $thisfile_riff['comments'][$tokey][] = $thisfile_riff_WAVE_SNDM_0['parsed'][$fromkey]; + } + } + } + + if (isset($thisfile_riff_WAVE['iXML'][0]['data'])) { + // requires functions simplexml_load_string and get_object_vars + if ($parsedXML = getid3_lib::XML2array($thisfile_riff_WAVE['iXML'][0]['data'])) { + $thisfile_riff_WAVE['iXML'][0]['parsed'] = $parsedXML; + if (isset($parsedXML['SPEED']['MASTER_SPEED'])) { + @list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['MASTER_SPEED']); + $thisfile_riff_WAVE['iXML'][0]['master_speed'] = $numerator / ($denominator ? $denominator : 1000); + } + if (isset($parsedXML['SPEED']['TIMECODE_RATE'])) { + @list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['TIMECODE_RATE']); + $thisfile_riff_WAVE['iXML'][0]['timecode_rate'] = $numerator / ($denominator ? $denominator : 1000); + } + if (isset($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO']) && !empty($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) && !empty($thisfile_riff_WAVE['iXML'][0]['timecode_rate'])) { + $samples_since_midnight = floatval(ltrim($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_HI'].$parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO'], '0')); + $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] = $samples_since_midnight / $parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']; + $h = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] / 3600); + $m = floor(($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600)) / 60); + $s = floor( $thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60)); + $f = ($thisfile_riff_WAVE['iXML'][0]['timecode_seconds'] - ($h * 3600) - ($m * 60) - $s) * $thisfile_riff_WAVE['iXML'][0]['timecode_rate']; + $thisfile_riff_WAVE['iXML'][0]['timecode_string'] = sprintf('%02d:%02d:%02d:%05.2f', $h, $m, $s, $f); + $thisfile_riff_WAVE['iXML'][0]['timecode_string_round'] = sprintf('%02d:%02d:%02d:%02d', $h, $m, $s, round($f)); + } + unset($parsedXML); + } + } + + + + if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) { + $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; + $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $thisfile_audio['bitrate']); + } + + if (!empty($info['wavpack'])) { + $thisfile_audio_dataformat = 'wavpack'; + $thisfile_audio['bitrate_mode'] = 'vbr'; + $thisfile_audio['encoder'] = 'WavPack v'.$info['wavpack']['version']; + + // Reset to the way it was - RIFF parsing will have messed this up + $info['avdataend'] = $Original['avdataend']; + $thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds']; + + $this->fseek($info['avdataoffset'] - 44); + $RIFFdata = $this->fread(44); + $OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8; + $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; + + if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { + $info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); + $this->fseek($info['avdataend']); + $RIFFdata .= $this->fread($OrignalRIFFheaderSize - $OrignalRIFFdataSize); + } + + // move the data chunk after all other chunks (if any) + // so that the RIFF parser doesn't see EOF when trying + // to skip over the data chunk + $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8); + $getid3_riff = new getid3_riff($this->getid3); + $getid3_riff->ParseRIFFdata($RIFFdata); + unset($getid3_riff); + } + + if (isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { + switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { + case 0x0001: // PCM + if (!empty($info['ac3'])) { + // Dolby Digital WAV files masquerade as PCM-WAV, but they're not + $thisfile_audio['wformattag'] = 0x2000; + $thisfile_audio['codec'] = self::wFormatTagLookup($thisfile_audio['wformattag']); + $thisfile_audio['lossless'] = false; + $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; + $thisfile_audio['sample_rate'] = $info['ac3']['sample_rate']; + } + if (!empty($info['dts'])) { + // Dolby DTS files masquerade as PCM-WAV, but they're not + $thisfile_audio['wformattag'] = 0x2001; + $thisfile_audio['codec'] = self::wFormatTagLookup($thisfile_audio['wformattag']); + $thisfile_audio['lossless'] = false; + $thisfile_audio['bitrate'] = $info['dts']['bitrate']; + $thisfile_audio['sample_rate'] = $info['dts']['sample_rate']; + } + break; + case 0x08AE: // ClearJump LiteWave + $thisfile_audio['bitrate_mode'] = 'vbr'; + $thisfile_audio_dataformat = 'litewave'; + + //typedef struct tagSLwFormat { + // WORD m_wCompFormat; // low byte defines compression method, high byte is compression flags + // DWORD m_dwScale; // scale factor for lossy compression + // DWORD m_dwBlockSize; // number of samples in encoded blocks + // WORD m_wQuality; // alias for the scale factor + // WORD m_wMarkDistance; // distance between marks in bytes + // WORD m_wReserved; + // + // //following paramters are ignored if CF_FILESRC is not set + // DWORD m_dwOrgSize; // original file size in bytes + // WORD m_bFactExists; // indicates if 'fact' chunk exists in the original file + // DWORD m_dwRiffChunkSize; // riff chunk size in the original file + // + // PCMWAVEFORMAT m_OrgWf; // original wave format + // }SLwFormat, *PSLwFormat; + + // shortcut + $thisfile_riff['litewave']['raw'] = array(); + $riff_litewave = &$thisfile_riff['litewave']; + $riff_litewave_raw = &$riff_litewave['raw']; + + $flags = array( + 'compression_method' => 1, + 'compression_flags' => 1, + 'm_dwScale' => 4, + 'm_dwBlockSize' => 4, + 'm_wQuality' => 2, + 'm_wMarkDistance' => 2, + 'm_wReserved' => 2, + 'm_dwOrgSize' => 4, + 'm_bFactExists' => 2, + 'm_dwRiffChunkSize' => 4, + ); + $litewave_offset = 18; + foreach ($flags as $flag => $length) { + $riff_litewave_raw[$flag] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], $litewave_offset, $length)); + $litewave_offset += $length; + } + + //$riff_litewave['quality_factor'] = intval(round((2000 - $riff_litewave_raw['m_dwScale']) / 20)); + $riff_litewave['quality_factor'] = $riff_litewave_raw['m_wQuality']; + + $riff_litewave['flags']['raw_source'] = ($riff_litewave_raw['compression_flags'] & 0x01) ? false : true; + $riff_litewave['flags']['vbr_blocksize'] = ($riff_litewave_raw['compression_flags'] & 0x02) ? false : true; + $riff_litewave['flags']['seekpoints'] = (bool) ($riff_litewave_raw['compression_flags'] & 0x04); + + $thisfile_audio['lossless'] = (($riff_litewave_raw['m_wQuality'] == 100) ? true : false); + $thisfile_audio['encoder_options'] = '-q'.$riff_litewave['quality_factor']; + break; + + default: + break; + } + } + if ($info['avdataend'] > $info['filesize']) { + switch (!empty($thisfile_audio_dataformat) ? $thisfile_audio_dataformat : '') { + case 'wavpack': // WavPack + case 'lpac': // LPAC + case 'ofr': // OptimFROG + case 'ofs': // OptimFROG DualStream + // lossless compressed audio formats that keep original RIFF headers - skip warning + break; + + case 'litewave': + if (($info['avdataend'] - $info['filesize']) == 1) { + // LiteWave appears to incorrectly *not* pad actual output file + // to nearest WORD boundary so may appear to be short by one + // byte, in which case - skip warning + } else { + // Short by more than one byte, throw warning + $info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; + $info['avdataend'] = $info['filesize']; + } + break; + + default: + if ((($info['avdataend'] - $info['filesize']) == 1) && (($thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2) == 0) && ((($info['filesize'] - $info['avdataoffset']) % 2) == 1)) { + // output file appears to be incorrectly *not* padded to nearest WORD boundary + // Output less severe warning + $info['warning'][] = 'File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; + $info['avdataend'] = $info['filesize']; + } else { + // Short by more than one byte, throw warning + $info['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($info['filesize'] - $info['avdataoffset'])).' bytes)'; + $info['avdataend'] = $info['filesize']; + } + break; + } + } + if (!empty($info['mpeg']['audio']['LAME']['audio_bytes'])) { + if ((($info['avdataend'] - $info['avdataoffset']) - $info['mpeg']['audio']['LAME']['audio_bytes']) == 1) { + $info['avdataend']--; + $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; + } + } + if (isset($thisfile_audio_dataformat) && ($thisfile_audio_dataformat == 'ac3')) { + unset($thisfile_audio['bits_per_sample']); + if (!empty($info['ac3']['bitrate']) && ($info['ac3']['bitrate'] != $thisfile_audio['bitrate'])) { + $thisfile_audio['bitrate'] = $info['ac3']['bitrate']; + } + } + break; + + case 'AVI ': + $thisfile_video['bitrate_mode'] = 'vbr'; // maybe not, but probably + $thisfile_video['dataformat'] = 'avi'; + $info['mime_type'] = 'video/avi'; + + if (isset($thisfile_riff[$RIFFsubtype]['movi']['offset'])) { + $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8; + if (isset($thisfile_riff['AVIX'])) { + $info['avdataend'] = $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['offset'] + $thisfile_riff['AVIX'][(count($thisfile_riff['AVIX']) - 1)]['chunks']['movi']['size']; + } else { + $info['avdataend'] = $thisfile_riff['AVI ']['movi']['offset'] + $thisfile_riff['AVI ']['movi']['size']; + } + if ($info['avdataend'] > $info['filesize']) { + $info['warning'][] = 'Probably truncated file - expecting '.($info['avdataend'] - $info['avdataoffset']).' bytes of data, only found '.($info['filesize'] - $info['avdataoffset']).' (short by '.($info['avdataend'] - $info['filesize']).' bytes)'; + $info['avdataend'] = $info['filesize']; + } + } + + if (isset($thisfile_riff['AVI ']['hdrl']['strl']['indx'])) { + //$bIndexType = array( + // 0x00 => 'AVI_INDEX_OF_INDEXES', + // 0x01 => 'AVI_INDEX_OF_CHUNKS', + // 0x80 => 'AVI_INDEX_IS_DATA', + //); + //$bIndexSubtype = array( + // 0x01 => array( + // 0x01 => 'AVI_INDEX_2FIELD', + // ), + //); + foreach ($thisfile_riff['AVI ']['hdrl']['strl']['indx'] as $streamnumber => $steamdataarray) { + $ahsisd = &$thisfile_riff['AVI ']['hdrl']['strl']['indx'][$streamnumber]['data']; + + $thisfile_riff_raw['indx'][$streamnumber]['wLongsPerEntry'] = $this->EitherEndian2Int(substr($ahsisd, 0, 2)); + $thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType'] = $this->EitherEndian2Int(substr($ahsisd, 2, 1)); + $thisfile_riff_raw['indx'][$streamnumber]['bIndexType'] = $this->EitherEndian2Int(substr($ahsisd, 3, 1)); + $thisfile_riff_raw['indx'][$streamnumber]['nEntriesInUse'] = $this->EitherEndian2Int(substr($ahsisd, 4, 4)); + $thisfile_riff_raw['indx'][$streamnumber]['dwChunkId'] = substr($ahsisd, 8, 4); + $thisfile_riff_raw['indx'][$streamnumber]['dwReserved'] = $this->EitherEndian2Int(substr($ahsisd, 12, 4)); + + //$thisfile_riff_raw['indx'][$streamnumber]['bIndexType_name'] = $bIndexType[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']]; + //$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType_name'] = $bIndexSubtype[$thisfile_riff_raw['indx'][$streamnumber]['bIndexType']][$thisfile_riff_raw['indx'][$streamnumber]['bIndexSubType']]; + + unset($ahsisd); + } + } + if (isset($thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data'])) { + $avihData = $thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data']; + + // shortcut + $thisfile_riff_raw['avih'] = array(); + $thisfile_riff_raw_avih = &$thisfile_riff_raw['avih']; + + $thisfile_riff_raw_avih['dwMicroSecPerFrame'] = $this->EitherEndian2Int(substr($avihData, 0, 4)); // frame display rate (or 0L) + if ($thisfile_riff_raw_avih['dwMicroSecPerFrame'] == 0) { + $info['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero'; + return false; + } + + $flags = array( + 'dwMaxBytesPerSec', // max. transfer rate + 'dwPaddingGranularity', // pad to multiples of this size; normally 2K. + 'dwFlags', // the ever-present flags + 'dwTotalFrames', // # frames in file + 'dwInitialFrames', // + 'dwStreams', // + 'dwSuggestedBufferSize', // + 'dwWidth', // + 'dwHeight', // + 'dwScale', // + 'dwRate', // + 'dwStart', // + 'dwLength', // + ); + $avih_offset = 4; + foreach ($flags as $flag) { + $thisfile_riff_raw_avih[$flag] = $this->EitherEndian2Int(substr($avihData, $avih_offset, 4)); + $avih_offset += 4; + } + + $flags = array( + 'hasindex' => 0x00000010, + 'mustuseindex' => 0x00000020, + 'interleaved' => 0x00000100, + 'trustcktype' => 0x00000800, + 'capturedfile' => 0x00010000, + 'copyrighted' => 0x00020010, + ); + foreach ($flags as $flag => $value) { + $thisfile_riff_raw_avih['flags'][$flag] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & $value); + } + + // shortcut + $thisfile_riff_video[$streamindex] = array(); + $thisfile_riff_video_current = &$thisfile_riff_video[$streamindex]; + + if ($thisfile_riff_raw_avih['dwWidth'] > 0) { + $thisfile_riff_video_current['frame_width'] = $thisfile_riff_raw_avih['dwWidth']; + $thisfile_video['resolution_x'] = $thisfile_riff_video_current['frame_width']; + } + if ($thisfile_riff_raw_avih['dwHeight'] > 0) { + $thisfile_riff_video_current['frame_height'] = $thisfile_riff_raw_avih['dwHeight']; + $thisfile_video['resolution_y'] = $thisfile_riff_video_current['frame_height']; + } + if ($thisfile_riff_raw_avih['dwTotalFrames'] > 0) { + $thisfile_riff_video_current['total_frames'] = $thisfile_riff_raw_avih['dwTotalFrames']; + $thisfile_video['total_frames'] = $thisfile_riff_video_current['total_frames']; + } + + $thisfile_riff_video_current['frame_rate'] = round(1000000 / $thisfile_riff_raw_avih['dwMicroSecPerFrame'], 3); + $thisfile_video['frame_rate'] = $thisfile_riff_video_current['frame_rate']; + } + if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][0]['data'])) { + if (is_array($thisfile_riff['AVI ']['hdrl']['strl']['strh'])) { + for ($i = 0; $i < count($thisfile_riff['AVI ']['hdrl']['strl']['strh']); $i++) { + if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'])) { + $strhData = $thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data']; + $strhfccType = substr($strhData, 0, 4); + + if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data'])) { + $strfData = $thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data']; + + // shortcut + $thisfile_riff_raw_strf_strhfccType_streamindex = &$thisfile_riff_raw['strf'][$strhfccType][$streamindex]; + + switch ($strhfccType) { + case 'auds': + $thisfile_audio['bitrate_mode'] = 'cbr'; + $thisfile_audio_dataformat = 'wav'; + if (isset($thisfile_riff_audio) && is_array($thisfile_riff_audio)) { + $streamindex = count($thisfile_riff_audio); + } + + $thisfile_riff_audio[$streamindex] = self::parseWAVEFORMATex($strfData); + $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; + + // shortcut + $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex]; + $thisfile_audio_streams_currentstream = &$thisfile_audio['streams'][$streamindex]; + + if ($thisfile_audio_streams_currentstream['bits_per_sample'] == 0) { + unset($thisfile_audio_streams_currentstream['bits_per_sample']); + } + $thisfile_audio_streams_currentstream['wformattag'] = $thisfile_audio_streams_currentstream['raw']['wFormatTag']; + unset($thisfile_audio_streams_currentstream['raw']); + + // shortcut + $thisfile_riff_raw['strf'][$strhfccType][$streamindex] = $thisfile_riff_audio[$streamindex]['raw']; + + unset($thisfile_riff_audio[$streamindex]['raw']); + $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); + + $thisfile_audio['lossless'] = false; + switch ($thisfile_riff_raw_strf_strhfccType_streamindex['wFormatTag']) { + case 0x0001: // PCM + $thisfile_audio_dataformat = 'wav'; + $thisfile_audio['lossless'] = true; + break; + + case 0x0050: // MPEG Layer 2 or Layer 1 + $thisfile_audio_dataformat = 'mp2'; // Assume Layer-2 + break; + + case 0x0055: // MPEG Layer 3 + $thisfile_audio_dataformat = 'mp3'; + break; + + case 0x00FF: // AAC + $thisfile_audio_dataformat = 'aac'; + break; + + case 0x0161: // Windows Media v7 / v8 / v9 + case 0x0162: // Windows Media Professional v9 + case 0x0163: // Windows Media Lossess v9 + $thisfile_audio_dataformat = 'wma'; + break; + + case 0x2000: // AC-3 + $thisfile_audio_dataformat = 'ac3'; + break; + + case 0x2001: // DTS + $thisfile_audio_dataformat = 'dts'; + break; + + default: + $thisfile_audio_dataformat = 'wav'; + break; + } + $thisfile_audio_streams_currentstream['dataformat'] = $thisfile_audio_dataformat; + $thisfile_audio_streams_currentstream['lossless'] = $thisfile_audio['lossless']; + $thisfile_audio_streams_currentstream['bitrate_mode'] = $thisfile_audio['bitrate_mode']; + break; + + + case 'iavs': + case 'vids': + // shortcut + $thisfile_riff_raw['strh'][$i] = array(); + $thisfile_riff_raw_strh_current = &$thisfile_riff_raw['strh'][$i]; + + $thisfile_riff_raw_strh_current['fccType'] = substr($strhData, 0, 4); // same as $strhfccType; + $thisfile_riff_raw_strh_current['fccHandler'] = substr($strhData, 4, 4); + $thisfile_riff_raw_strh_current['dwFlags'] = $this->EitherEndian2Int(substr($strhData, 8, 4)); // Contains AVITF_* flags + $thisfile_riff_raw_strh_current['wPriority'] = $this->EitherEndian2Int(substr($strhData, 12, 2)); + $thisfile_riff_raw_strh_current['wLanguage'] = $this->EitherEndian2Int(substr($strhData, 14, 2)); + $thisfile_riff_raw_strh_current['dwInitialFrames'] = $this->EitherEndian2Int(substr($strhData, 16, 4)); + $thisfile_riff_raw_strh_current['dwScale'] = $this->EitherEndian2Int(substr($strhData, 20, 4)); + $thisfile_riff_raw_strh_current['dwRate'] = $this->EitherEndian2Int(substr($strhData, 24, 4)); + $thisfile_riff_raw_strh_current['dwStart'] = $this->EitherEndian2Int(substr($strhData, 28, 4)); + $thisfile_riff_raw_strh_current['dwLength'] = $this->EitherEndian2Int(substr($strhData, 32, 4)); + $thisfile_riff_raw_strh_current['dwSuggestedBufferSize'] = $this->EitherEndian2Int(substr($strhData, 36, 4)); + $thisfile_riff_raw_strh_current['dwQuality'] = $this->EitherEndian2Int(substr($strhData, 40, 4)); + $thisfile_riff_raw_strh_current['dwSampleSize'] = $this->EitherEndian2Int(substr($strhData, 44, 4)); + $thisfile_riff_raw_strh_current['rcFrame'] = $this->EitherEndian2Int(substr($strhData, 48, 4)); + + $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_riff_raw_strh_current['fccHandler']); + $thisfile_video['fourcc'] = $thisfile_riff_raw_strh_current['fccHandler']; + if (!$thisfile_riff_video_current['codec'] && isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && self::fourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { + $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']); + $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; + } + $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; + $thisfile_video['pixel_aspect_ratio'] = (float) 1; + switch ($thisfile_riff_raw_strh_current['fccHandler']) { + case 'HFYU': // Huffman Lossless Codec + case 'IRAW': // Intel YUV Uncompressed + case 'YUY2': // Uncompressed YUV 4:2:2 + $thisfile_video['lossless'] = true; + break; + + default: + $thisfile_video['lossless'] = false; + break; + } + + switch ($strhfccType) { + case 'vids': + $thisfile_riff_raw_strf_strhfccType_streamindex = self::ParseBITMAPINFOHEADER(substr($strfData, 0, 40), ($info['fileformat'] == 'riff')); + $thisfile_video['bits_per_sample'] = $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount']; + + if ($thisfile_riff_video_current['codec'] == 'DV') { + $thisfile_riff_video_current['dv_type'] = 2; + } + break; + + case 'iavs': + $thisfile_riff_video_current['dv_type'] = 1; + break; + } + break; + + default: + $info['warning'][] = 'Unhandled fccType for stream ('.$i.'): "'.$strhfccType.'"'; + break; + + } + } + } + + if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { + + $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; + if (self::fourccLookup($thisfile_video['fourcc'])) { + $thisfile_riff_video_current['codec'] = self::fourccLookup($thisfile_video['fourcc']); + $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; + } + + switch ($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) { + case 'HFYU': // Huffman Lossless Codec + case 'IRAW': // Intel YUV Uncompressed + case 'YUY2': // Uncompressed YUV 4:2:2 + $thisfile_video['lossless'] = true; + //$thisfile_video['bits_per_sample'] = 24; + break; + + default: + $thisfile_video['lossless'] = false; + //$thisfile_video['bits_per_sample'] = 24; + break; + } + + } + } + } + } + break; + + case 'CDDA': + $thisfile_audio['bitrate_mode'] = 'cbr'; + $thisfile_audio_dataformat = 'cda'; + $thisfile_audio['lossless'] = true; + unset($info['mime_type']); + + $info['avdataoffset'] = 44; + + if (isset($thisfile_riff['CDDA']['fmt '][0]['data'])) { + // shortcut + $thisfile_riff_CDDA_fmt_0 = &$thisfile_riff['CDDA']['fmt '][0]; + + $thisfile_riff_CDDA_fmt_0['unknown1'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 0, 2)); + $thisfile_riff_CDDA_fmt_0['track_num'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 2, 2)); + $thisfile_riff_CDDA_fmt_0['disc_id'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 4, 4)); + $thisfile_riff_CDDA_fmt_0['start_offset_frame'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 8, 4)); + $thisfile_riff_CDDA_fmt_0['playtime_frames'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 12, 4)); + $thisfile_riff_CDDA_fmt_0['unknown6'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 16, 4)); + $thisfile_riff_CDDA_fmt_0['unknown7'] = $this->EitherEndian2Int(substr($thisfile_riff_CDDA_fmt_0['data'], 20, 4)); + + $thisfile_riff_CDDA_fmt_0['start_offset_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['start_offset_frame'] / 75; + $thisfile_riff_CDDA_fmt_0['playtime_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['playtime_frames'] / 75; + $info['comments']['track'] = $thisfile_riff_CDDA_fmt_0['track_num']; + $info['playtime_seconds'] = $thisfile_riff_CDDA_fmt_0['playtime_seconds']; + + // hardcoded data for CD-audio + $thisfile_audio['sample_rate'] = 44100; + $thisfile_audio['channels'] = 2; + $thisfile_audio['bits_per_sample'] = 16; + $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $thisfile_audio['channels'] * $thisfile_audio['bits_per_sample']; + $thisfile_audio['bitrate_mode'] = 'cbr'; + } + break; + + + case 'AIFF': + case 'AIFC': + $thisfile_audio['bitrate_mode'] = 'cbr'; + $thisfile_audio_dataformat = 'aiff'; + $thisfile_audio['lossless'] = true; + $info['mime_type'] = 'audio/x-aiff'; + + if (isset($thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'])) { + $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8; + $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['SSND'][0]['size']; + if ($info['avdataend'] > $info['filesize']) { + if (($info['avdataend'] == ($info['filesize'] + 1)) && (($info['filesize'] % 2) == 1)) { + // structures rounded to 2-byte boundary, but dumb encoders + // forget to pad end of file to make this actually work + } else { + $info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['SSND'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found'; + } + $info['avdataend'] = $info['filesize']; + } + } + + if (isset($thisfile_riff[$RIFFsubtype]['COMM'][0]['data'])) { + + // shortcut + $thisfile_riff_RIFFsubtype_COMM_0_data = &$thisfile_riff[$RIFFsubtype]['COMM'][0]['data']; + + $thisfile_riff_audio['channels'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 0, 2), true); + $thisfile_riff_audio['total_samples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 2, 4), false); + $thisfile_riff_audio['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 6, 2), true); + $thisfile_riff_audio['sample_rate'] = (int) getid3_lib::BigEndian2Float(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 8, 10)); + + if ($thisfile_riff[$RIFFsubtype]['COMM'][0]['size'] > 18) { + $thisfile_riff_audio['codec_fourcc'] = substr($thisfile_riff_RIFFsubtype_COMM_0_data, 18, 4); + $CodecNameSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 22, 1), false); + $thisfile_riff_audio['codec_name'] = substr($thisfile_riff_RIFFsubtype_COMM_0_data, 23, $CodecNameSize); + switch ($thisfile_riff_audio['codec_name']) { + case 'NONE': + $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; + $thisfile_audio['lossless'] = true; + break; + + case '': + switch ($thisfile_riff_audio['codec_fourcc']) { + // http://developer.apple.com/qa/snd/snd07.html + case 'sowt': + $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Little-Endian PCM'; + $thisfile_audio['lossless'] = true; + break; + + case 'twos': + $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Big-Endian PCM'; + $thisfile_audio['lossless'] = true; + break; + + default: + break; + } + break; + + default: + $thisfile_audio['codec'] = $thisfile_riff_audio['codec_name']; + $thisfile_audio['lossless'] = false; + break; + } + } + + $thisfile_audio['channels'] = $thisfile_riff_audio['channels']; + if ($thisfile_riff_audio['bits_per_sample'] > 0) { + $thisfile_audio['bits_per_sample'] = $thisfile_riff_audio['bits_per_sample']; + } + $thisfile_audio['sample_rate'] = $thisfile_riff_audio['sample_rate']; + if ($thisfile_audio['sample_rate'] == 0) { + $info['error'][] = 'Corrupted AIFF file: sample_rate == zero'; + return false; + } + $info['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate']; + } + + if (isset($thisfile_riff[$RIFFsubtype]['COMT'])) { + $offset = 0; + $CommentCount = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); + $offset += 2; + for ($i = 0; $i < $CommentCount; $i++) { + $info['comments_raw'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 4), false); + $offset += 4; + $info['comments_raw'][$i]['marker_id'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), true); + $offset += 2; + $CommentLength = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); + $offset += 2; + $info['comments_raw'][$i]['comment'] = substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, $CommentLength); + $offset += $CommentLength; + + $info['comments_raw'][$i]['timestamp_unix'] = getid3_lib::DateMac2Unix($info['comments_raw'][$i]['timestamp']); + $thisfile_riff['comments']['comment'][] = $info['comments_raw'][$i]['comment']; + } + } + + $CommentsChunkNames = array('NAME'=>'title', 'author'=>'artist', '(c) '=>'copyright', 'ANNO'=>'comment'); + foreach ($CommentsChunkNames as $key => $value) { + if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) { + $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; + } + } +/* + if (isset($thisfile_riff[$RIFFsubtype]['ID3 '])) { + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_id3v2 = new getid3_id3v2($getid3_temp); + $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['ID3 '][0]['offset'] + 8; + if ($thisfile_riff[$RIFFsubtype]['ID3 '][0]['valid'] = $getid3_id3v2->Analyze()) { + $info['id3v2'] = $getid3_temp->info['id3v2']; + } + unset($getid3_temp, $getid3_id3v2); + } +*/ + break; + + case '8SVX': + $thisfile_audio['bitrate_mode'] = 'cbr'; + $thisfile_audio_dataformat = '8svx'; + $thisfile_audio['bits_per_sample'] = 8; + $thisfile_audio['channels'] = 1; // overridden below, if need be + $info['mime_type'] = 'audio/x-aiff'; + + if (isset($thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'])) { + $info['avdataoffset'] = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8; + $info['avdataend'] = $info['avdataoffset'] + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size']; + if ($info['avdataend'] > $info['filesize']) { + $info['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['BODY'][0]['size'].' bytes of audio data, only '.($info['filesize'] - $info['avdataoffset']).' bytes found'; + } + } + + if (isset($thisfile_riff[$RIFFsubtype]['VHDR'][0]['offset'])) { + // shortcut + $thisfile_riff_RIFFsubtype_VHDR_0 = &$thisfile_riff[$RIFFsubtype]['VHDR'][0]; + + $thisfile_riff_RIFFsubtype_VHDR_0['oneShotHiSamples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 0, 4)); + $thisfile_riff_RIFFsubtype_VHDR_0['repeatHiSamples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 4, 4)); + $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerHiCycle'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 8, 4)); + $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 12, 2)); + $thisfile_riff_RIFFsubtype_VHDR_0['ctOctave'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 14, 1)); + $thisfile_riff_RIFFsubtype_VHDR_0['sCompression'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 15, 1)); + $thisfile_riff_RIFFsubtype_VHDR_0['Volume'] = getid3_lib::FixedPoint16_16(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 16, 4)); + + $thisfile_audio['sample_rate'] = $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec']; + + switch ($thisfile_riff_RIFFsubtype_VHDR_0['sCompression']) { + case 0: + $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; + $thisfile_audio['lossless'] = true; + $ActualBitsPerSample = 8; + break; + + case 1: + $thisfile_audio['codec'] = 'Fibonacci-delta encoding'; + $thisfile_audio['lossless'] = false; + $ActualBitsPerSample = 4; + break; + + default: + $info['warning'][] = 'Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.sCompression.'"'; + break; + } + } + + if (isset($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'])) { + $ChannelsIndex = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'], 0, 4)); + switch ($ChannelsIndex) { + case 6: // Stereo + $thisfile_audio['channels'] = 2; + break; + + case 2: // Left channel only + case 4: // Right channel only + $thisfile_audio['channels'] = 1; + break; + + default: + $info['warning'][] = 'Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "'.$ChannelsIndex.'"'; + break; + } + + } + + $CommentsChunkNames = array('NAME'=>'title', 'author'=>'artist', '(c) '=>'copyright', 'ANNO'=>'comment'); + foreach ($CommentsChunkNames as $key => $value) { + if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) { + $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; + } + } + + $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $ActualBitsPerSample * $thisfile_audio['channels']; + if (!empty($thisfile_audio['bitrate'])) { + $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($thisfile_audio['bitrate'] / 8); + } + break; + + + case 'CDXA': + $info['mime_type'] = 'video/mpeg'; + if (!empty($thisfile_riff['CDXA']['data'][0]['size'])) { + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, false)) { + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_mpeg = new getid3_mpeg($getid3_temp); + $getid3_mpeg->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['video'] = $getid3_temp->info['video']; + $info['mpeg'] = $getid3_temp->info['mpeg']; + $info['warning'] = $getid3_temp->info['warning']; + } + unset($getid3_temp, $getid3_mpeg); + } + } + break; + + + default: + $info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead'; + unset($info['fileformat']); + break; + } + + switch ($RIFFsubtype) { + case 'WAVE': + case 'AIFF': + case 'AIFC': + $ID3v2_key_good = 'id3 '; + $ID3v2_keys_bad = array('ID3 ', 'tag '); + foreach ($ID3v2_keys_bad as $ID3v2_key_bad) { + if (isset($thisfile_riff[$RIFFsubtype][$ID3v2_key_bad]) && !array_key_exists($ID3v2_key_good, $thisfile_riff[$RIFFsubtype])) { + $thisfile_riff[$RIFFsubtype][$ID3v2_key_good] = $thisfile_riff[$RIFFsubtype][$ID3v2_key_bad]; + $info['warning'][] = 'mapping "'.$ID3v2_key_bad.'" chunk to "'.$ID3v2_key_good.'"'; + } + } + + if (isset($thisfile_riff[$RIFFsubtype]['id3 '])) { + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_id3v2 = new getid3_id3v2($getid3_temp); + $getid3_id3v2->StartingOffset = $thisfile_riff[$RIFFsubtype]['id3 '][0]['offset'] + 8; + if ($thisfile_riff[$RIFFsubtype]['id3 '][0]['valid'] = $getid3_id3v2->Analyze()) { + $info['id3v2'] = $getid3_temp->info['id3v2']; + } + unset($getid3_temp, $getid3_id3v2); + } + break; + } + + if (isset($thisfile_riff_WAVE['DISP']) && is_array($thisfile_riff_WAVE['DISP'])) { + $thisfile_riff['comments']['title'][] = trim(substr($thisfile_riff_WAVE['DISP'][count($thisfile_riff_WAVE['DISP']) - 1]['data'], 4)); + } + if (isset($thisfile_riff_WAVE['INFO']) && is_array($thisfile_riff_WAVE['INFO'])) { + self::parseComments($thisfile_riff_WAVE['INFO'], $thisfile_riff['comments']); + } + if (isset($thisfile_riff['AVI ']['INFO']) && is_array($thisfile_riff['AVI ']['INFO'])) { + self::parseComments($thisfile_riff['AVI ']['INFO'], $thisfile_riff['comments']); + } + + if (empty($thisfile_audio['encoder']) && !empty($info['mpeg']['audio']['LAME']['short_version'])) { + $thisfile_audio['encoder'] = $info['mpeg']['audio']['LAME']['short_version']; + } + + if (!isset($info['playtime_seconds'])) { + $info['playtime_seconds'] = 0; + } + if (isset($thisfile_riff_raw['strh'][0]['dwLength']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { + // needed for >2GB AVIs where 'avih' chunk only lists number of frames in that chunk, not entire movie + $info['playtime_seconds'] = $thisfile_riff_raw['strh'][0]['dwLength'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); + } elseif (isset($thisfile_riff_raw['avih']['dwTotalFrames']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { + $info['playtime_seconds'] = $thisfile_riff_raw['avih']['dwTotalFrames'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); + } + + if ($info['playtime_seconds'] > 0) { + if (isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { + + if (!isset($info['bitrate'])) { + $info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); + } + + } elseif (isset($thisfile_riff_audio) && !isset($thisfile_riff_video)) { + + if (!isset($thisfile_audio['bitrate'])) { + $thisfile_audio['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); + } + + } elseif (!isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { + + if (!isset($thisfile_video['bitrate'])) { + $thisfile_video['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); + } + + } + } + + + if (isset($thisfile_riff_video) && isset($thisfile_audio['bitrate']) && ($thisfile_audio['bitrate'] > 0) && ($info['playtime_seconds'] > 0)) { + + $info['bitrate'] = ((($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8); + $thisfile_audio['bitrate'] = 0; + $thisfile_video['bitrate'] = $info['bitrate']; + foreach ($thisfile_riff_audio as $channelnumber => $audioinfoarray) { + $thisfile_video['bitrate'] -= $audioinfoarray['bitrate']; + $thisfile_audio['bitrate'] += $audioinfoarray['bitrate']; + } + if ($thisfile_video['bitrate'] <= 0) { + unset($thisfile_video['bitrate']); + } + if ($thisfile_audio['bitrate'] <= 0) { + unset($thisfile_audio['bitrate']); + } + } + + if (isset($info['mpeg']['audio'])) { + $thisfile_audio_dataformat = 'mp'.$info['mpeg']['audio']['layer']; + $thisfile_audio['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $thisfile_audio['channels'] = $info['mpeg']['audio']['channels']; + $thisfile_audio['bitrate'] = $info['mpeg']['audio']['bitrate']; + $thisfile_audio['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + if (!empty($info['mpeg']['audio']['codec'])) { + $thisfile_audio['codec'] = $info['mpeg']['audio']['codec'].' '.$thisfile_audio['codec']; + } + if (!empty($thisfile_audio['streams'])) { + foreach ($thisfile_audio['streams'] as $streamnumber => $streamdata) { + if ($streamdata['dataformat'] == $thisfile_audio_dataformat) { + $thisfile_audio['streams'][$streamnumber]['sample_rate'] = $thisfile_audio['sample_rate']; + $thisfile_audio['streams'][$streamnumber]['channels'] = $thisfile_audio['channels']; + $thisfile_audio['streams'][$streamnumber]['bitrate'] = $thisfile_audio['bitrate']; + $thisfile_audio['streams'][$streamnumber]['bitrate_mode'] = $thisfile_audio['bitrate_mode']; + $thisfile_audio['streams'][$streamnumber]['codec'] = $thisfile_audio['codec']; + } + } + } + $getid3_mp3 = new getid3_mp3($this->getid3); + $thisfile_audio['encoder_options'] = $getid3_mp3->GuessEncoderOptions(); + unset($getid3_mp3); + } + + + if (!empty($thisfile_riff_raw['fmt ']['wBitsPerSample']) && ($thisfile_riff_raw['fmt ']['wBitsPerSample'] > 0)) { + switch ($thisfile_audio_dataformat) { + case 'ac3': + // ignore bits_per_sample + break; + + default: + $thisfile_audio['bits_per_sample'] = $thisfile_riff_raw['fmt ']['wBitsPerSample']; + break; + } + } + + + if (empty($thisfile_riff_raw)) { + unset($thisfile_riff['raw']); + } + if (empty($thisfile_riff_audio)) { + unset($thisfile_riff['audio']); + } + if (empty($thisfile_riff_video)) { + unset($thisfile_riff['video']); + } + + return true; + } + + public function ParseRIFF($startoffset, $maxoffset) { + $info = &$this->getid3->info; + + $RIFFchunk = false; + $FoundAllChunksWeNeed = false; + + try { + $this->fseek($startoffset); + $maxoffset = min($maxoffset, $info['avdataend']); + while ($this->ftell() < $maxoffset) { + $chunknamesize = $this->fread(8); + //$chunkname = substr($chunknamesize, 0, 4); + $chunkname = str_replace("\x00", '_', substr($chunknamesize, 0, 4)); // note: chunk names of 4 null bytes do appear to be legal (has been observed inside INFO and PRMI chunks, for example), but makes traversing array keys more difficult + $chunksize = $this->EitherEndian2Int(substr($chunknamesize, 4, 4)); + //if (strlen(trim($chunkname, "\x00")) < 4) { + if (strlen($chunkname) < 4) { + $this->error('Expecting chunk name at offset '.($this->ftell() - 8).' but found nothing. Aborting RIFF parsing.'); + break; + } + if (($chunksize == 0) && ($chunkname != 'JUNK')) { + $this->warning('Chunk ('.$chunkname.') size at offset '.($this->ftell() - 4).' is zero. Aborting RIFF parsing.'); + break; + } + if (($chunksize % 2) != 0) { + // all structures are packed on word boundaries + $chunksize++; + } + + switch ($chunkname) { + case 'LIST': + $listname = $this->fread(4); + if (preg_match('#^(movi|rec )$#i', $listname)) { + $RIFFchunk[$listname]['offset'] = $this->ftell() - 4; + $RIFFchunk[$listname]['size'] = $chunksize; + + if (!$FoundAllChunksWeNeed) { + $WhereWeWere = $this->ftell(); + $AudioChunkHeader = $this->fread(12); + $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); + $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); + $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); + + if ($AudioChunkStreamType == 'wb') { + $FirstFourBytes = substr($AudioChunkHeader, 8, 4); + if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', $FirstFourBytes)) { + // MP3 + if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; + $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; + $getid3_mp3 = new getid3_mp3($getid3_temp); + $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false); + if (isset($getid3_temp->info['mpeg']['audio'])) { + $info['mpeg']['audio'] = $getid3_temp->info['mpeg']['audio']; + $info['audio'] = $getid3_temp->info['audio']; + $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + //$info['bitrate'] = $info['audio']['bitrate']; + } + unset($getid3_temp, $getid3_mp3); + } + + } elseif (strpos($FirstFourBytes, getid3_ac3::syncword) === 0) { + + // AC3 + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $this->ftell() - 4; + $getid3_temp->info['avdataend'] = $this->ftell() + $AudioChunkSize; + $getid3_ac3 = new getid3_ac3($getid3_temp); + $getid3_ac3->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['ac3'] = $getid3_temp->info['ac3']; + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $key => $value) { + $info['warning'][] = $value; + } + } + } + unset($getid3_temp, $getid3_ac3); + } + } + $FoundAllChunksWeNeed = true; + $this->fseek($WhereWeWere); + } + $this->fseek($chunksize - 4, SEEK_CUR); + + } else { + + if (!isset($RIFFchunk[$listname])) { + $RIFFchunk[$listname] = array(); + } + $LISTchunkParent = $listname; + $LISTchunkMaxOffset = $this->ftell() - 4 + $chunksize; + if ($parsedChunk = $this->ParseRIFF($this->ftell(), $LISTchunkMaxOffset)) { + $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); + } + + } + break; + + default: + if (preg_match('#^[0-9]{2}(wb|pc|dc|db)$#', $chunkname)) { + $this->fseek($chunksize, SEEK_CUR); + break; + } + $thisindex = 0; + if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { + $thisindex = count($RIFFchunk[$chunkname]); + } + $RIFFchunk[$chunkname][$thisindex]['offset'] = $this->ftell() - 8; + $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; + switch ($chunkname) { + case 'data': + $info['avdataoffset'] = $this->ftell(); + $info['avdataend'] = $info['avdataoffset'] + $chunksize; + + $testData = $this->fread(36); + if ($testData === '') { + break; + } + if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', substr($testData, 0, 4))) { + + // Probably is MP3 data + if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($testData, 0, 4))) { + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + $getid3_mp3 = new getid3_mp3($getid3_temp); + $getid3_mp3->getOnlyMPEGaudioInfo($info['avdataoffset'], false); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['mpeg'] = $getid3_temp->info['mpeg']; + } + unset($getid3_temp, $getid3_mp3); + } + + } elseif (($isRegularAC3 = (substr($testData, 0, 2) == getid3_ac3::syncword)) || substr($testData, 8, 2) == strrev(getid3_ac3::syncword)) { + + // This is probably AC-3 data + $getid3_temp = new getID3(); + if ($isRegularAC3) { + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_temp->info['avdataend'] = $info['avdataend']; + } + $getid3_ac3 = new getid3_ac3($getid3_temp); + if ($isRegularAC3) { + $getid3_ac3->Analyze(); + } else { + // Dolby Digital WAV + // AC-3 content, but not encoded in same format as normal AC-3 file + // For one thing, byte order is swapped + $ac3_data = ''; + for ($i = 0; $i < 28; $i += 2) { + $ac3_data .= substr($testData, 8 + $i + 1, 1); + $ac3_data .= substr($testData, 8 + $i + 0, 1); + } + $getid3_ac3->AnalyzeString($ac3_data); + } + + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['ac3'] = $getid3_temp->info['ac3']; + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + $this->warning('getid3_ac3() says: ['.$newerror.']'); + } + } + } + unset($getid3_temp, $getid3_ac3); + + } elseif (preg_match('/^('.implode('|', array_map('preg_quote', getid3_dts::$syncwords)).')/', $testData)) { + + // This is probably DTS data + $getid3_temp = new getID3(); + $getid3_temp->openfile($this->getid3->filename); + $getid3_temp->info['avdataoffset'] = $info['avdataoffset']; + $getid3_dts = new getid3_dts($getid3_temp); + $getid3_dts->Analyze(); + if (empty($getid3_temp->info['error'])) { + $info['audio'] = $getid3_temp->info['audio']; + $info['dts'] = $getid3_temp->info['dts']; + $info['playtime_seconds'] = $getid3_temp->info['playtime_seconds']; // may not match RIFF calculations since DTS-WAV often used 14/16 bit-word packing + if (!empty($getid3_temp->info['warning'])) { + foreach ($getid3_temp->info['warning'] as $newerror) { + $this->warning('getid3_dts() says: ['.$newerror.']'); + } + } + } + + unset($getid3_temp, $getid3_dts); + + } elseif (substr($testData, 0, 4) == 'wvpk') { + + // This is WavPack data + $info['wavpack']['offset'] = $info['avdataoffset']; + $info['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($testData, 4, 4)); + $this->parseWavPackHeader(substr($testData, 8, 28)); + + } else { + // This is some other kind of data (quite possibly just PCM) + // do nothing special, just skip it + } + $nextoffset = $info['avdataend']; + $this->fseek($nextoffset); + break; + + case 'iXML': + case 'bext': + case 'cart': + case 'fmt ': + case 'strh': + case 'strf': + case 'indx': + case 'MEXT': + case 'DISP': + // always read data in + case 'JUNK': + // should be: never read data in + // but some programs write their version strings in a JUNK chunk (e.g. VirtualDub, AVIdemux, etc) + if ($chunksize < 1048576) { + if ($chunksize > 0) { + $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize); + if ($chunkname == 'JUNK') { + if (preg_match('#^([\\x20-\\x7F]+)#', $RIFFchunk[$chunkname][$thisindex]['data'], $matches)) { + // only keep text characters [chr(32)-chr(127)] + $info['riff']['comments']['junk'][] = trim($matches[1]); + } + // but if nothing there, ignore + // remove the key in either case + unset($RIFFchunk[$chunkname][$thisindex]['data']); + } + } + } else { + $this->warning('Chunk "'.$chunkname.'" at offset '.$this->ftell().' is unexpectedly larger than 1MB (claims to be '.number_format($chunksize).' bytes), skipping data'); + $this->fseek($chunksize, SEEK_CUR); + } + break; + + //case 'IDVX': + // $info['divxtag']['comments'] = self::ParseDIVXTAG($this->fread($chunksize)); + // break; + + default: + if (!empty($LISTchunkParent) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) { + $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; + $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; + unset($RIFFchunk[$chunkname][$thisindex]['offset']); + unset($RIFFchunk[$chunkname][$thisindex]['size']); + if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) { + unset($RIFFchunk[$chunkname][$thisindex]); + } + if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { + unset($RIFFchunk[$chunkname]); + } + $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = $this->fread($chunksize); + } elseif ($chunksize < 2048) { + // only read data in if smaller than 2kB + $RIFFchunk[$chunkname][$thisindex]['data'] = $this->fread($chunksize); + } else { + $this->fseek($chunksize, SEEK_CUR); + } + break; + } + break; + } + } + + } catch (getid3_exception $e) { + if ($e->getCode() == 10) { + $this->warning('RIFF parser: '.$e->getMessage()); + } else { + throw $e; + } + } + + return $RIFFchunk; + } + + public function ParseRIFFdata(&$RIFFdata) { + $info = &$this->getid3->info; + if ($RIFFdata) { + $tempfile = tempnam(GETID3_TEMP_DIR, 'getID3'); + $fp_temp = fopen($tempfile, 'wb'); + $RIFFdataLength = strlen($RIFFdata); + $NewLengthString = getid3_lib::LittleEndian2String($RIFFdataLength, 4); + for ($i = 0; $i < 4; $i++) { + $RIFFdata[($i + 4)] = $NewLengthString[$i]; + } + fwrite($fp_temp, $RIFFdata); + fclose($fp_temp); + + $getid3_temp = new getID3(); + $getid3_temp->openfile($tempfile); + $getid3_temp->info['filesize'] = $RIFFdataLength; + $getid3_temp->info['filenamepath'] = $info['filenamepath']; + $getid3_temp->info['tags'] = $info['tags']; + $getid3_temp->info['warning'] = $info['warning']; + $getid3_temp->info['error'] = $info['error']; + $getid3_temp->info['comments'] = $info['comments']; + $getid3_temp->info['audio'] = (isset($info['audio']) ? $info['audio'] : array()); + $getid3_temp->info['video'] = (isset($info['video']) ? $info['video'] : array()); + $getid3_riff = new getid3_riff($getid3_temp); + $getid3_riff->Analyze(); + + $info['riff'] = $getid3_temp->info['riff']; + $info['warning'] = $getid3_temp->info['warning']; + $info['error'] = $getid3_temp->info['error']; + $info['tags'] = $getid3_temp->info['tags']; + $info['comments'] = $getid3_temp->info['comments']; + unset($getid3_riff, $getid3_temp); + unlink($tempfile); + } + return false; + } + + public static function parseComments(&$RIFFinfoArray, &$CommentsTargetArray) { + $RIFFinfoKeyLookup = array( + 'IARL'=>'archivallocation', + 'IART'=>'artist', + 'ICDS'=>'costumedesigner', + 'ICMS'=>'commissionedby', + 'ICMT'=>'comment', + 'ICNT'=>'country', + 'ICOP'=>'copyright', + 'ICRD'=>'creationdate', + 'IDIM'=>'dimensions', + 'IDIT'=>'digitizationdate', + 'IDPI'=>'resolution', + 'IDST'=>'distributor', + 'IEDT'=>'editor', + 'IENG'=>'engineers', + 'IFRM'=>'accountofparts', + 'IGNR'=>'genre', + 'IKEY'=>'keywords', + 'ILGT'=>'lightness', + 'ILNG'=>'language', + 'IMED'=>'orignalmedium', + 'IMUS'=>'composer', + 'INAM'=>'title', + 'IPDS'=>'productiondesigner', + 'IPLT'=>'palette', + 'IPRD'=>'product', + 'IPRO'=>'producer', + 'IPRT'=>'part', + 'IRTD'=>'rating', + 'ISBJ'=>'subject', + 'ISFT'=>'software', + 'ISGN'=>'secondarygenre', + 'ISHP'=>'sharpness', + 'ISRC'=>'sourcesupplier', + 'ISRF'=>'digitizationsource', + 'ISTD'=>'productionstudio', + 'ISTR'=>'starring', + 'ITCH'=>'encoded_by', + 'IWEB'=>'url', + 'IWRI'=>'writer', + '____'=>'comment', + ); + foreach ($RIFFinfoKeyLookup as $key => $value) { + if (isset($RIFFinfoArray[$key])) { + foreach ($RIFFinfoArray[$key] as $commentid => $commentdata) { + if (trim($commentdata['data']) != '') { + if (isset($CommentsTargetArray[$value])) { + $CommentsTargetArray[$value][] = trim($commentdata['data']); + } else { + $CommentsTargetArray[$value] = array(trim($commentdata['data'])); + } + } + } + } + } + return true; + } + + public static function parseWAVEFORMATex($WaveFormatExData) { + // shortcut + $WaveFormatEx['raw'] = array(); + $WaveFormatEx_raw = &$WaveFormatEx['raw']; + + $WaveFormatEx_raw['wFormatTag'] = substr($WaveFormatExData, 0, 2); + $WaveFormatEx_raw['nChannels'] = substr($WaveFormatExData, 2, 2); + $WaveFormatEx_raw['nSamplesPerSec'] = substr($WaveFormatExData, 4, 4); + $WaveFormatEx_raw['nAvgBytesPerSec'] = substr($WaveFormatExData, 8, 4); + $WaveFormatEx_raw['nBlockAlign'] = substr($WaveFormatExData, 12, 2); + $WaveFormatEx_raw['wBitsPerSample'] = substr($WaveFormatExData, 14, 2); + if (strlen($WaveFormatExData) > 16) { + $WaveFormatEx_raw['cbSize'] = substr($WaveFormatExData, 16, 2); + } + $WaveFormatEx_raw = array_map('getid3_lib::LittleEndian2Int', $WaveFormatEx_raw); + + $WaveFormatEx['codec'] = self::wFormatTagLookup($WaveFormatEx_raw['wFormatTag']); + $WaveFormatEx['channels'] = $WaveFormatEx_raw['nChannels']; + $WaveFormatEx['sample_rate'] = $WaveFormatEx_raw['nSamplesPerSec']; + $WaveFormatEx['bitrate'] = $WaveFormatEx_raw['nAvgBytesPerSec'] * 8; + $WaveFormatEx['bits_per_sample'] = $WaveFormatEx_raw['wBitsPerSample']; + + return $WaveFormatEx; + } + + public function parseWavPackHeader($WavPackChunkData) { + // typedef struct { + // char ckID [4]; + // long ckSize; + // short version; + // short bits; // added for version 2.00 + // short flags, shift; // added for version 3.00 + // long total_samples, crc, crc2; + // char extension [4], extra_bc, extras [3]; + // } WavpackHeader; + + // shortcut + $info = &$this->getid3->info; + $info['wavpack'] = array(); + $thisfile_wavpack = &$info['wavpack']; + + $thisfile_wavpack['version'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 0, 2)); + if ($thisfile_wavpack['version'] >= 2) { + $thisfile_wavpack['bits'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 2, 2)); + } + if ($thisfile_wavpack['version'] >= 3) { + $thisfile_wavpack['flags_raw'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 4, 2)); + $thisfile_wavpack['shift'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 6, 2)); + $thisfile_wavpack['total_samples'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 8, 4)); + $thisfile_wavpack['crc1'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 12, 4)); + $thisfile_wavpack['crc2'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 16, 4)); + $thisfile_wavpack['extension'] = substr($WavPackChunkData, 20, 4); + $thisfile_wavpack['extra_bc'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 24, 1)); + for ($i = 0; $i <= 2; $i++) { + $thisfile_wavpack['extras'][] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 25 + $i, 1)); + } + + // shortcut + $thisfile_wavpack['flags'] = array(); + $thisfile_wavpack_flags = &$thisfile_wavpack['flags']; + + $thisfile_wavpack_flags['mono'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000001); + $thisfile_wavpack_flags['fast_mode'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000002); + $thisfile_wavpack_flags['raw_mode'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000004); + $thisfile_wavpack_flags['calc_noise'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000008); + $thisfile_wavpack_flags['high_quality'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000010); + $thisfile_wavpack_flags['3_byte_samples'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000020); + $thisfile_wavpack_flags['over_20_bits'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000040); + $thisfile_wavpack_flags['use_wvc'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000080); + $thisfile_wavpack_flags['noiseshaping'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000100); + $thisfile_wavpack_flags['very_fast_mode'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000200); + $thisfile_wavpack_flags['new_high_quality'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000400); + $thisfile_wavpack_flags['cancel_extreme'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000800); + $thisfile_wavpack_flags['cross_decorrelation'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x001000); + $thisfile_wavpack_flags['new_decorrelation'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x002000); + $thisfile_wavpack_flags['joint_stereo'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x004000); + $thisfile_wavpack_flags['extra_decorrelation'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x008000); + $thisfile_wavpack_flags['override_noiseshape'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x010000); + $thisfile_wavpack_flags['override_jointstereo'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x020000); + $thisfile_wavpack_flags['copy_source_filetime'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x040000); + $thisfile_wavpack_flags['create_exe'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x080000); + } + + return true; + } + + public static function ParseBITMAPINFOHEADER($BITMAPINFOHEADER, $littleEndian=true) { + + $parsed['biSize'] = substr($BITMAPINFOHEADER, 0, 4); // number of bytes required by the BITMAPINFOHEADER structure + $parsed['biWidth'] = substr($BITMAPINFOHEADER, 4, 4); // width of the bitmap in pixels + $parsed['biHeight'] = substr($BITMAPINFOHEADER, 8, 4); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner + $parsed['biPlanes'] = substr($BITMAPINFOHEADER, 12, 2); // number of color planes on the target device. In most cases this value must be set to 1 + $parsed['biBitCount'] = substr($BITMAPINFOHEADER, 14, 2); // Specifies the number of bits per pixels + $parsed['biSizeImage'] = substr($BITMAPINFOHEADER, 20, 4); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) + $parsed['biXPelsPerMeter'] = substr($BITMAPINFOHEADER, 24, 4); // horizontal resolution, in pixels per metre, of the target device + $parsed['biYPelsPerMeter'] = substr($BITMAPINFOHEADER, 28, 4); // vertical resolution, in pixels per metre, of the target device + $parsed['biClrUsed'] = substr($BITMAPINFOHEADER, 32, 4); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression + $parsed['biClrImportant'] = substr($BITMAPINFOHEADER, 36, 4); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important + $parsed = array_map('getid3_lib::'.($littleEndian ? 'Little' : 'Big').'Endian2Int', $parsed); + + $parsed['fourcc'] = substr($BITMAPINFOHEADER, 16, 4); // compression identifier + + return $parsed; + } + + public static function ParseDIVXTAG($DIVXTAG, $raw=false) { + // structure from "IDivX" source, Form1.frm, by "Greg Frazier of Daemonic Software Group", email: gfrazier@icestorm.net, web: http://dsg.cjb.net/ + // source available at http://files.divx-digest.com/download/c663efe7ef8ad2e90bf4af4d3ea6188a/on0SWN2r/edit/IDivX.zip + // 'Byte Layout: '1111111111111111 + // '32 for Movie - 1 '1111111111111111 + // '28 for Author - 6 '6666666666666666 + // '4 for year - 2 '6666666666662222 + // '3 for genre - 3 '7777777777777777 + // '48 for Comments - 7 '7777777777777777 + // '1 for Rating - 4 '7777777777777777 + // '5 for Future Additions - 0 '333400000DIVXTAG + // '128 bytes total + + static $DIVXTAGgenre = array( + 0 => 'Action', + 1 => 'Action/Adventure', + 2 => 'Adventure', + 3 => 'Adult', + 4 => 'Anime', + 5 => 'Cartoon', + 6 => 'Claymation', + 7 => 'Comedy', + 8 => 'Commercial', + 9 => 'Documentary', + 10 => 'Drama', + 11 => 'Home Video', + 12 => 'Horror', + 13 => 'Infomercial', + 14 => 'Interactive', + 15 => 'Mystery', + 16 => 'Music Video', + 17 => 'Other', + 18 => 'Religion', + 19 => 'Sci Fi', + 20 => 'Thriller', + 21 => 'Western', + ), + $DIVXTAGrating = array( + 0 => 'Unrated', + 1 => 'G', + 2 => 'PG', + 3 => 'PG-13', + 4 => 'R', + 5 => 'NC-17', + ); + + $parsed['title'] = trim(substr($DIVXTAG, 0, 32)); + $parsed['artist'] = trim(substr($DIVXTAG, 32, 28)); + $parsed['year'] = intval(trim(substr($DIVXTAG, 60, 4))); + $parsed['comment'] = trim(substr($DIVXTAG, 64, 48)); + $parsed['genre_id'] = intval(trim(substr($DIVXTAG, 112, 3))); + $parsed['rating_id'] = ord(substr($DIVXTAG, 115, 1)); + //$parsed['padding'] = substr($DIVXTAG, 116, 5); // 5-byte null + //$parsed['magic'] = substr($DIVXTAG, 121, 7); // "DIVXTAG" + + $parsed['genre'] = (isset($DIVXTAGgenre[$parsed['genre_id']]) ? $DIVXTAGgenre[$parsed['genre_id']] : $parsed['genre_id']); + $parsed['rating'] = (isset($DIVXTAGrating[$parsed['rating_id']]) ? $DIVXTAGrating[$parsed['rating_id']] : $parsed['rating_id']); + + if (!$raw) { + unset($parsed['genre_id'], $parsed['rating_id']); + foreach ($parsed as $key => $value) { + if (!$value === '') { + unset($parsed['key']); + } + } + } + + foreach ($parsed as $tag => $value) { + $parsed[$tag] = array($value); + } + + return $parsed; + } + + public static function waveSNDMtagLookup($tagshortname) { + $begin = __LINE__; + + /** This is not a comment! + + \A9kwd keywords + \A9BPM bpm + \A9trt tracktitle + \A9des description + \A9gen category + \A9fin featuredinstrument + \A9LID longid + \A9bex bwdescription + \A9pub publisher + \A9cdt cdtitle + \A9alb library + \A9com composer + + */ + + return getid3_lib::EmbeddedLookup($tagshortname, $begin, __LINE__, __FILE__, 'riff-sndm'); + } + + public static function wFormatTagLookup($wFormatTag) { + + $begin = __LINE__; + + /** This is not a comment! + + 0x0000 Microsoft Unknown Wave Format + 0x0001 Pulse Code Modulation (PCM) + 0x0002 Microsoft ADPCM + 0x0003 IEEE Float + 0x0004 Compaq Computer VSELP + 0x0005 IBM CVSD + 0x0006 Microsoft A-Law + 0x0007 Microsoft mu-Law + 0x0008 Microsoft DTS + 0x0010 OKI ADPCM + 0x0011 Intel DVI/IMA ADPCM + 0x0012 Videologic MediaSpace ADPCM + 0x0013 Sierra Semiconductor ADPCM + 0x0014 Antex Electronics G.723 ADPCM + 0x0015 DSP Solutions DigiSTD + 0x0016 DSP Solutions DigiFIX + 0x0017 Dialogic OKI ADPCM + 0x0018 MediaVision ADPCM + 0x0019 Hewlett-Packard CU + 0x0020 Yamaha ADPCM + 0x0021 Speech Compression Sonarc + 0x0022 DSP Group TrueSpeech + 0x0023 Echo Speech EchoSC1 + 0x0024 Audiofile AF36 + 0x0025 Audio Processing Technology APTX + 0x0026 AudioFile AF10 + 0x0027 Prosody 1612 + 0x0028 LRC + 0x0030 Dolby AC2 + 0x0031 Microsoft GSM 6.10 + 0x0032 MSNAudio + 0x0033 Antex Electronics ADPCME + 0x0034 Control Resources VQLPC + 0x0035 DSP Solutions DigiREAL + 0x0036 DSP Solutions DigiADPCM + 0x0037 Control Resources CR10 + 0x0038 Natural MicroSystems VBXADPCM + 0x0039 Crystal Semiconductor IMA ADPCM + 0x003A EchoSC3 + 0x003B Rockwell ADPCM + 0x003C Rockwell Digit LK + 0x003D Xebec + 0x0040 Antex Electronics G.721 ADPCM + 0x0041 G.728 CELP + 0x0042 MSG723 + 0x0050 MPEG Layer-2 or Layer-1 + 0x0052 RT24 + 0x0053 PAC + 0x0055 MPEG Layer-3 + 0x0059 Lucent G.723 + 0x0060 Cirrus + 0x0061 ESPCM + 0x0062 Voxware + 0x0063 Canopus Atrac + 0x0064 G.726 ADPCM + 0x0065 G.722 ADPCM + 0x0066 DSAT + 0x0067 DSAT Display + 0x0069 Voxware Byte Aligned + 0x0070 Voxware AC8 + 0x0071 Voxware AC10 + 0x0072 Voxware AC16 + 0x0073 Voxware AC20 + 0x0074 Voxware MetaVoice + 0x0075 Voxware MetaSound + 0x0076 Voxware RT29HW + 0x0077 Voxware VR12 + 0x0078 Voxware VR18 + 0x0079 Voxware TQ40 + 0x0080 Softsound + 0x0081 Voxware TQ60 + 0x0082 MSRT24 + 0x0083 G.729A + 0x0084 MVI MV12 + 0x0085 DF G.726 + 0x0086 DF GSM610 + 0x0088 ISIAudio + 0x0089 Onlive + 0x0091 SBC24 + 0x0092 Dolby AC3 SPDIF + 0x0093 MediaSonic G.723 + 0x0094 Aculab PLC Prosody 8kbps + 0x0097 ZyXEL ADPCM + 0x0098 Philips LPCBB + 0x0099 Packed + 0x00FF AAC + 0x0100 Rhetorex ADPCM + 0x0101 IBM mu-law + 0x0102 IBM A-law + 0x0103 IBM AVC Adaptive Differential Pulse Code Modulation (ADPCM) + 0x0111 Vivo G.723 + 0x0112 Vivo Siren + 0x0123 Digital G.723 + 0x0125 Sanyo LD ADPCM + 0x0130 Sipro Lab Telecom ACELP NET + 0x0131 Sipro Lab Telecom ACELP 4800 + 0x0132 Sipro Lab Telecom ACELP 8V3 + 0x0133 Sipro Lab Telecom G.729 + 0x0134 Sipro Lab Telecom G.729A + 0x0135 Sipro Lab Telecom Kelvin + 0x0140 Windows Media Video V8 + 0x0150 Qualcomm PureVoice + 0x0151 Qualcomm HalfRate + 0x0155 Ring Zero Systems TUB GSM + 0x0160 Microsoft Audio 1 + 0x0161 Windows Media Audio V7 / V8 / V9 + 0x0162 Windows Media Audio Professional V9 + 0x0163 Windows Media Audio Lossless V9 + 0x0200 Creative Labs ADPCM + 0x0202 Creative Labs Fastspeech8 + 0x0203 Creative Labs Fastspeech10 + 0x0210 UHER Informatic GmbH ADPCM + 0x0220 Quarterdeck + 0x0230 I-link Worldwide VC + 0x0240 Aureal RAW Sport + 0x0250 Interactive Products HSX + 0x0251 Interactive Products RPELP + 0x0260 Consistent Software CS2 + 0x0270 Sony SCX + 0x0300 Fujitsu FM Towns Snd + 0x0400 BTV Digital + 0x0401 Intel Music Coder + 0x0450 QDesign Music + 0x0680 VME VMPCM + 0x0681 AT&T Labs TPC + 0x08AE ClearJump LiteWave + 0x1000 Olivetti GSM + 0x1001 Olivetti ADPCM + 0x1002 Olivetti CELP + 0x1003 Olivetti SBC + 0x1004 Olivetti OPR + 0x1100 Lernout & Hauspie Codec (0x1100) + 0x1101 Lernout & Hauspie CELP Codec (0x1101) + 0x1102 Lernout & Hauspie SBC Codec (0x1102) + 0x1103 Lernout & Hauspie SBC Codec (0x1103) + 0x1104 Lernout & Hauspie SBC Codec (0x1104) + 0x1400 Norris + 0x1401 AT&T ISIAudio + 0x1500 Soundspace Music Compression + 0x181C VoxWare RT24 Speech + 0x1FC4 NCT Soft ALF2CD (www.nctsoft.com) + 0x2000 Dolby AC3 + 0x2001 Dolby DTS + 0x2002 WAVE_FORMAT_14_4 + 0x2003 WAVE_FORMAT_28_8 + 0x2004 WAVE_FORMAT_COOK + 0x2005 WAVE_FORMAT_DNET + 0x674F Ogg Vorbis 1 + 0x6750 Ogg Vorbis 2 + 0x6751 Ogg Vorbis 3 + 0x676F Ogg Vorbis 1+ + 0x6770 Ogg Vorbis 2+ + 0x6771 Ogg Vorbis 3+ + 0x7A21 GSM-AMR (CBR, no SID) + 0x7A22 GSM-AMR (VBR, including SID) + 0xFFFE WAVE_FORMAT_EXTENSIBLE + 0xFFFF WAVE_FORMAT_DEVELOPMENT + + */ + + return getid3_lib::EmbeddedLookup('0x'.str_pad(strtoupper(dechex($wFormatTag)), 4, '0', STR_PAD_LEFT), $begin, __LINE__, __FILE__, 'riff-wFormatTag'); + } + + public static function fourccLookup($fourcc) { + + $begin = __LINE__; + + /** This is not a comment! + + swot http://developer.apple.com/qa/snd/snd07.html + ____ No Codec (____) + _BIT BI_BITFIELDS (Raw RGB) + _JPG JPEG compressed + _PNG PNG compressed W3C/ISO/IEC (RFC-2083) + _RAW Full Frames (Uncompressed) + _RGB Raw RGB Bitmap + _RL4 RLE 4bpp RGB + _RL8 RLE 8bpp RGB + 3IV1 3ivx MPEG-4 v1 + 3IV2 3ivx MPEG-4 v2 + 3IVX 3ivx MPEG-4 + AASC Autodesk Animator + ABYR Kensington ?ABYR? + AEMI Array Microsystems VideoONE MPEG1-I Capture + AFLC Autodesk Animator FLC + AFLI Autodesk Animator FLI + AMPG Array Microsystems VideoONE MPEG + ANIM Intel RDX (ANIM) + AP41 AngelPotion Definitive + ASV1 Asus Video v1 + ASV2 Asus Video v2 + ASVX Asus Video 2.0 (audio) + AUR2 AuraVision Aura 2 Codec - YUV 4:2:2 + AURA AuraVision Aura 1 Codec - YUV 4:1:1 + AVDJ Independent JPEG Group\'s codec (AVDJ) + AVRN Independent JPEG Group\'s codec (AVRN) + AYUV 4:4:4 YUV (AYUV) + AZPR Quicktime Apple Video (AZPR) + BGR Raw RGB32 + BLZ0 Blizzard DivX MPEG-4 + BTVC Conexant Composite Video + BINK RAD Game Tools Bink Video + BT20 Conexant Prosumer Video + BTCV Conexant Composite Video Codec + BW10 Data Translation Broadway MPEG Capture + CC12 Intel YUV12 + CDVC Canopus DV + CFCC Digital Processing Systems DPS Perception + CGDI Microsoft Office 97 Camcorder Video + CHAM Winnov Caviara Champagne + CJPG Creative WebCam JPEG + CLJR Cirrus Logic YUV 4:1:1 + CMYK Common Data Format in Printing (Colorgraph) + CPLA Weitek 4:2:0 YUV Planar + CRAM Microsoft Video 1 (CRAM) + cvid Radius Cinepak + CVID Radius Cinepak + CWLT Microsoft Color WLT DIB + CYUV Creative Labs YUV + CYUY ATI YUV + D261 H.261 + D263 H.263 + DIB Device Independent Bitmap + DIV1 FFmpeg OpenDivX + DIV2 Microsoft MPEG-4 v1/v2 + DIV3 DivX ;-) MPEG-4 v3.x Low-Motion + DIV4 DivX ;-) MPEG-4 v3.x Fast-Motion + DIV5 DivX MPEG-4 v5.x + DIV6 DivX ;-) (MS MPEG-4 v3.x) + DIVX DivX MPEG-4 v4 (OpenDivX / Project Mayo) + divx DivX MPEG-4 + DMB1 Matrox Rainbow Runner hardware MJPEG + DMB2 Paradigm MJPEG + DSVD ?DSVD? + DUCK Duck TrueMotion 1.0 + DPS0 DPS/Leitch Reality Motion JPEG + DPSC DPS/Leitch PAR Motion JPEG + DV25 Matrox DVCPRO codec + DV50 Matrox DVCPRO50 codec + DVC IEC 61834 and SMPTE 314M (DVC/DV Video) + DVCP IEC 61834 and SMPTE 314M (DVC/DV Video) + DVHD IEC Standard DV 1125 lines @ 30fps / 1250 lines @ 25fps + DVMA Darim Vision DVMPEG (dummy for MPEG compressor) (www.darvision.com) + DVSL IEC Standard DV compressed in SD (SDL) + DVAN ?DVAN? + DVE2 InSoft DVE-2 Videoconferencing + dvsd IEC 61834 and SMPTE 314M DVC/DV Video + DVSD IEC 61834 and SMPTE 314M DVC/DV Video + DVX1 Lucent DVX1000SP Video Decoder + DVX2 Lucent DVX2000S Video Decoder + DVX3 Lucent DVX3000S Video Decoder + DX50 DivX v5 + DXT1 Microsoft DirectX Compressed Texture (DXT1) + DXT2 Microsoft DirectX Compressed Texture (DXT2) + DXT3 Microsoft DirectX Compressed Texture (DXT3) + DXT4 Microsoft DirectX Compressed Texture (DXT4) + DXT5 Microsoft DirectX Compressed Texture (DXT5) + DXTC Microsoft DirectX Compressed Texture (DXTC) + DXTn Microsoft DirectX Compressed Texture (DXTn) + EM2V Etymonix MPEG-2 I-frame (www.etymonix.com) + EKQ0 Elsa ?EKQ0? + ELK0 Elsa ?ELK0? + ESCP Eidos Escape + ETV1 eTreppid Video ETV1 + ETV2 eTreppid Video ETV2 + ETVC eTreppid Video ETVC + FLIC Autodesk FLI/FLC Animation + FLV1 Sorenson Spark + FLV4 On2 TrueMotion VP6 + FRWT Darim Vision Forward Motion JPEG (www.darvision.com) + FRWU Darim Vision Forward Uncompressed (www.darvision.com) + FLJP D-Vision Field Encoded Motion JPEG + FPS1 FRAPS v1 + FRWA SoftLab-Nsk Forward Motion JPEG w/ alpha channel + FRWD SoftLab-Nsk Forward Motion JPEG + FVF1 Iterated Systems Fractal Video Frame + GLZW Motion LZW (gabest@freemail.hu) + GPEG Motion JPEG (gabest@freemail.hu) + GWLT Microsoft Greyscale WLT DIB + H260 Intel ITU H.260 Videoconferencing + H261 Intel ITU H.261 Videoconferencing + H262 Intel ITU H.262 Videoconferencing + H263 Intel ITU H.263 Videoconferencing + H264 Intel ITU H.264 Videoconferencing + H265 Intel ITU H.265 Videoconferencing + H266 Intel ITU H.266 Videoconferencing + H267 Intel ITU H.267 Videoconferencing + H268 Intel ITU H.268 Videoconferencing + H269 Intel ITU H.269 Videoconferencing + HFYU Huffman Lossless Codec + HMCR Rendition Motion Compensation Format (HMCR) + HMRR Rendition Motion Compensation Format (HMRR) + I263 FFmpeg I263 decoder + IF09 Indeo YVU9 ("YVU9 with additional delta-frame info after the U plane") + IUYV Interlaced version of UYVY (www.leadtools.com) + IY41 Interlaced version of Y41P (www.leadtools.com) + IYU1 12 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec IEEE standard + IYU2 24 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec IEEE standard + IYUV Planar YUV format (8-bpp Y plane, followed by 8-bpp 2\D72 U and V planes) + i263 Intel ITU H.263 Videoconferencing (i263) + I420 Intel Indeo 4 + IAN Intel Indeo 4 (RDX) + ICLB InSoft CellB Videoconferencing + IGOR Power DVD + IJPG Intergraph JPEG + ILVC Intel Layered Video + ILVR ITU-T H.263+ + IPDV I-O Data Device Giga AVI DV Codec + IR21 Intel Indeo 2.1 + IRAW Intel YUV Uncompressed + IV30 Intel Indeo 3.0 + IV31 Intel Indeo 3.1 + IV32 Ligos Indeo 3.2 + IV33 Ligos Indeo 3.3 + IV34 Ligos Indeo 3.4 + IV35 Ligos Indeo 3.5 + IV36 Ligos Indeo 3.6 + IV37 Ligos Indeo 3.7 + IV38 Ligos Indeo 3.8 + IV39 Ligos Indeo 3.9 + IV40 Ligos Indeo Interactive 4.0 + IV41 Ligos Indeo Interactive 4.1 + IV42 Ligos Indeo Interactive 4.2 + IV43 Ligos Indeo Interactive 4.3 + IV44 Ligos Indeo Interactive 4.4 + IV45 Ligos Indeo Interactive 4.5 + IV46 Ligos Indeo Interactive 4.6 + IV47 Ligos Indeo Interactive 4.7 + IV48 Ligos Indeo Interactive 4.8 + IV49 Ligos Indeo Interactive 4.9 + IV50 Ligos Indeo Interactive 5.0 + JBYR Kensington ?JBYR? + JPEG Still Image JPEG DIB + JPGL Pegasus Lossless Motion JPEG + KMVC Team17 Software Karl Morton\'s Video Codec + LSVM Vianet Lighting Strike Vmail (Streaming) (www.vianet.com) + LEAD LEAD Video Codec + Ljpg LEAD MJPEG Codec + MDVD Alex MicroDVD Video (hacked MS MPEG-4) (www.tiasoft.de) + MJPA Morgan Motion JPEG (MJPA) (www.morgan-multimedia.com) + MJPB Morgan Motion JPEG (MJPB) (www.morgan-multimedia.com) + MMES Matrox MPEG-2 I-frame + MP2v Microsoft S-Mpeg 4 version 1 (MP2v) + MP42 Microsoft S-Mpeg 4 version 2 (MP42) + MP43 Microsoft S-Mpeg 4 version 3 (MP43) + MP4S Microsoft S-Mpeg 4 version 3 (MP4S) + MP4V FFmpeg MPEG-4 + MPG1 FFmpeg MPEG 1/2 + MPG2 FFmpeg MPEG 1/2 + MPG3 FFmpeg DivX ;-) (MS MPEG-4 v3) + MPG4 Microsoft MPEG-4 + MPGI Sigma Designs MPEG + MPNG PNG images decoder + MSS1 Microsoft Windows Screen Video + MSZH LCL (Lossless Codec Library) (www.geocities.co.jp/Playtown-Denei/2837/LRC.htm) + M261 Microsoft H.261 + M263 Microsoft H.263 + M4S2 Microsoft Fully Compliant MPEG-4 v2 simple profile (M4S2) + m4s2 Microsoft Fully Compliant MPEG-4 v2 simple profile (m4s2) + MC12 ATI Motion Compensation Format (MC12) + MCAM ATI Motion Compensation Format (MCAM) + MJ2C Morgan Multimedia Motion JPEG2000 + mJPG IBM Motion JPEG w/ Huffman Tables + MJPG Microsoft Motion JPEG DIB + MP42 Microsoft MPEG-4 (low-motion) + MP43 Microsoft MPEG-4 (fast-motion) + MP4S Microsoft MPEG-4 (MP4S) + mp4s Microsoft MPEG-4 (mp4s) + MPEG Chromatic Research MPEG-1 Video I-Frame + MPG4 Microsoft MPEG-4 Video High Speed Compressor + MPGI Sigma Designs MPEG + MRCA FAST Multimedia Martin Regen Codec + MRLE Microsoft Run Length Encoding + MSVC Microsoft Video 1 + MTX1 Matrox ?MTX1? + MTX2 Matrox ?MTX2? + MTX3 Matrox ?MTX3? + MTX4 Matrox ?MTX4? + MTX5 Matrox ?MTX5? + MTX6 Matrox ?MTX6? + MTX7 Matrox ?MTX7? + MTX8 Matrox ?MTX8? + MTX9 Matrox ?MTX9? + MV12 Motion Pixels Codec (old) + MWV1 Aware Motion Wavelets + nAVI SMR Codec (hack of Microsoft MPEG-4) (IRC #shadowrealm) + NT00 NewTek LightWave HDTV YUV w/ Alpha (www.newtek.com) + NUV1 NuppelVideo + NTN1 Nogatech Video Compression 1 + NVS0 nVidia GeForce Texture (NVS0) + NVS1 nVidia GeForce Texture (NVS1) + NVS2 nVidia GeForce Texture (NVS2) + NVS3 nVidia GeForce Texture (NVS3) + NVS4 nVidia GeForce Texture (NVS4) + NVS5 nVidia GeForce Texture (NVS5) + NVT0 nVidia GeForce Texture (NVT0) + NVT1 nVidia GeForce Texture (NVT1) + NVT2 nVidia GeForce Texture (NVT2) + NVT3 nVidia GeForce Texture (NVT3) + NVT4 nVidia GeForce Texture (NVT4) + NVT5 nVidia GeForce Texture (NVT5) + PIXL MiroXL, Pinnacle PCTV + PDVC I-O Data Device Digital Video Capture DV codec + PGVV Radius Video Vision + PHMO IBM Photomotion + PIM1 MPEG Realtime (Pinnacle Cards) + PIM2 Pegasus Imaging ?PIM2? + PIMJ Pegasus Imaging Lossless JPEG + PVEZ Horizons Technology PowerEZ + PVMM PacketVideo Corporation MPEG-4 + PVW2 Pegasus Imaging Wavelet Compression + Q1.0 Q-Team\'s QPEG 1.0 (www.q-team.de) + Q1.1 Q-Team\'s QPEG 1.1 (www.q-team.de) + QPEG Q-Team QPEG 1.0 + qpeq Q-Team QPEG 1.1 + RGB Raw BGR32 + RGBA Raw RGB w/ Alpha + RMP4 REALmagic MPEG-4 (unauthorized XVID copy) (www.sigmadesigns.com) + ROQV Id RoQ File Video Decoder + RPZA Quicktime Apple Video (RPZA) + RUD0 Rududu video codec (http://rududu.ifrance.com/rududu/) + RV10 RealVideo 1.0 (aka RealVideo 5.0) + RV13 RealVideo 1.0 (RV13) + RV20 RealVideo G2 + RV30 RealVideo 8 + RV40 RealVideo 9 + RGBT Raw RGB w/ Transparency + RLE Microsoft Run Length Encoder + RLE4 Run Length Encoded (4bpp, 16-color) + RLE8 Run Length Encoded (8bpp, 256-color) + RT21 Intel Indeo RealTime Video 2.1 + rv20 RealVideo G2 + rv30 RealVideo 8 + RVX Intel RDX (RVX ) + SMC Apple Graphics (SMC ) + SP54 Logitech Sunplus Sp54 Codec for Mustek GSmart Mini 2 + SPIG Radius Spigot + SVQ3 Sorenson Video 3 (Apple Quicktime 5) + s422 Tekram VideoCap C210 YUV 4:2:2 + SDCC Sun Communication Digital Camera Codec + SFMC CrystalNet Surface Fitting Method + SMSC Radius SMSC + SMSD Radius SMSD + smsv WorldConnect Wavelet Video + SPIG Radius Spigot + SPLC Splash Studios ACM Audio Codec (www.splashstudios.net) + SQZ2 Microsoft VXTreme Video Codec V2 + STVA ST Microelectronics CMOS Imager Data (Bayer) + STVB ST Microelectronics CMOS Imager Data (Nudged Bayer) + STVC ST Microelectronics CMOS Imager Data (Bunched) + STVX ST Microelectronics CMOS Imager Data (Extended CODEC Data Format) + STVY ST Microelectronics CMOS Imager Data (Extended CODEC Data Format with Correction Data) + SV10 Sorenson Video R1 + SVQ1 Sorenson Video + T420 Toshiba YUV 4:2:0 + TM2A Duck TrueMotion Archiver 2.0 (www.duck.com) + TVJP Pinnacle/Truevision Targa 2000 board (TVJP) + TVMJ Pinnacle/Truevision Targa 2000 board (TVMJ) + TY0N Tecomac Low-Bit Rate Codec (www.tecomac.com) + TY2C Trident Decompression Driver + TLMS TeraLogic Motion Intraframe Codec (TLMS) + TLST TeraLogic Motion Intraframe Codec (TLST) + TM20 Duck TrueMotion 2.0 + TM2X Duck TrueMotion 2X + TMIC TeraLogic Motion Intraframe Codec (TMIC) + TMOT Horizons Technology TrueMotion S + tmot Horizons TrueMotion Video Compression + TR20 Duck TrueMotion RealTime 2.0 + TSCC TechSmith Screen Capture Codec + TV10 Tecomac Low-Bit Rate Codec + TY2N Trident ?TY2N? + U263 UB Video H.263/H.263+/H.263++ Decoder + UMP4 UB Video MPEG 4 (www.ubvideo.com) + UYNV Nvidia UYVY packed 4:2:2 + UYVP Evans & Sutherland YCbCr 4:2:2 extended precision + UCOD eMajix.com ClearVideo + ULTI IBM Ultimotion + UYVY UYVY packed 4:2:2 + V261 Lucent VX2000S + VIFP VFAPI Reader Codec (www.yks.ne.jp/~hori/) + VIV1 FFmpeg H263+ decoder + VIV2 Vivo H.263 + VQC2 Vector-quantised codec 2 (research) http://eprints.ecs.soton.ac.uk/archive/00001310/01/VTC97-js.pdf) + VTLP Alaris VideoGramPiX + VYU9 ATI YUV (VYU9) + VYUY ATI YUV (VYUY) + V261 Lucent VX2000S + V422 Vitec Multimedia 24-bit YUV 4:2:2 Format + V655 Vitec Multimedia 16-bit YUV 4:2:2 Format + VCR1 ATI Video Codec 1 + VCR2 ATI Video Codec 2 + VCR3 ATI VCR 3.0 + VCR4 ATI VCR 4.0 + VCR5 ATI VCR 5.0 + VCR6 ATI VCR 6.0 + VCR7 ATI VCR 7.0 + VCR8 ATI VCR 8.0 + VCR9 ATI VCR 9.0 + VDCT Vitec Multimedia Video Maker Pro DIB + VDOM VDOnet VDOWave + VDOW VDOnet VDOLive (H.263) + VDTZ Darim Vison VideoTizer YUV + VGPX Alaris VideoGramPiX + VIDS Vitec Multimedia YUV 4:2:2 CCIR 601 for V422 + VIVO Vivo H.263 v2.00 + vivo Vivo H.263 + VIXL Miro/Pinnacle Video XL + VLV1 VideoLogic/PURE Digital Videologic Capture + VP30 On2 VP3.0 + VP31 On2 VP3.1 + VP6F On2 TrueMotion VP6 + VX1K Lucent VX1000S Video Codec + VX2K Lucent VX2000S Video Codec + VXSP Lucent VX1000SP Video Codec + WBVC Winbond W9960 + WHAM Microsoft Video 1 (WHAM) + WINX Winnov Software Compression + WJPG AverMedia Winbond JPEG + WMV1 Windows Media Video V7 + WMV2 Windows Media Video V8 + WMV3 Windows Media Video V9 + WNV1 Winnov Hardware Compression + XYZP Extended PAL format XYZ palette (www.riff.org) + x263 Xirlink H.263 + XLV0 NetXL Video Decoder + XMPG Xing MPEG (I-Frame only) + XVID XviD MPEG-4 (www.xvid.org) + XXAN ?XXAN? + YU92 Intel YUV (YU92) + YUNV Nvidia Uncompressed YUV 4:2:2 + YUVP Extended PAL format YUV palette (www.riff.org) + Y211 YUV 2:1:1 Packed + Y411 YUV 4:1:1 Packed + Y41B Weitek YUV 4:1:1 Planar + Y41P Brooktree PC1 YUV 4:1:1 Packed + Y41T Brooktree PC1 YUV 4:1:1 with transparency + Y42B Weitek YUV 4:2:2 Planar + Y42T Brooktree UYUV 4:2:2 with transparency + Y422 ADS Technologies Copy of UYVY used in Pyro WebCam firewire camera + Y800 Simple, single Y plane for monochrome images + Y8 Grayscale video + YC12 Intel YUV 12 codec + YUV8 Winnov Caviar YUV8 + YUV9 Intel YUV9 + YUY2 Uncompressed YUV 4:2:2 + YUYV Canopus YUV + YV12 YVU12 Planar + YVU9 Intel YVU9 Planar (8-bpp Y plane, followed by 8-bpp 4x4 U and V planes) + YVYU YVYU 4:2:2 Packed + ZLIB Lossless Codec Library zlib compression (www.geocities.co.jp/Playtown-Denei/2837/LRC.htm) + ZPEG Metheus Video Zipper + + */ + + return getid3_lib::EmbeddedLookup($fourcc, $begin, __LINE__, __FILE__, 'riff-fourcc'); + } + + private function EitherEndian2Int($byteword, $signed=false) { + if ($this->getid3->info['fileformat'] == 'riff') { + return getid3_lib::LittleEndian2Int($byteword, $signed); + } + return getid3_lib::BigEndian2Int($byteword, false, $signed); + } + +} diff --git a/extensions/TimedMediaHandler/libs/getid3/module.audio.ac3.php b/extensions/TimedMediaHandler/libs/getid3/module.audio.ac3.php new file mode 100644 index 00000000..9834feb5 --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/module.audio.ac3.php @@ -0,0 +1,473 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.ac3.php // +// module for analyzing AC-3 (aka Dolby Digital) audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_ac3 extends getid3_handler +{ + private $AC3header = array(); + private $BSIoffset = 0; + + const syncword = "\x0B\x77"; + + public function Analyze() { + $info = &$this->getid3->info; + + ///AH + $info['ac3']['raw']['bsi'] = array(); + $thisfile_ac3 = &$info['ac3']; + $thisfile_ac3_raw = &$thisfile_ac3['raw']; + $thisfile_ac3_raw_bsi = &$thisfile_ac3_raw['bsi']; + + + // http://www.atsc.org/standards/a_52a.pdf + + $info['fileformat'] = 'ac3'; + + // An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames + // Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256 + // new audio samples per channel. A synchronization information (SI) header at the beginning + // of each frame contains information needed to acquire and maintain synchronization. A + // bit stream information (BSI) header follows SI, and contains parameters describing the coded + // audio service. The coded audio blocks may be followed by an auxiliary data (Aux) field. At the + // end of each frame is an error check field that includes a CRC word for error detection. An + // additional CRC word is located in the SI header, the use of which, by a decoder, is optional. + // + // syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC + + // syncinfo() { + // syncword 16 + // crc1 16 + // fscod 2 + // frmsizecod 6 + // } /* end of syncinfo */ + + $this->fseek($info['avdataoffset']); + $this->AC3header['syncinfo'] = $this->fread(5); + + if (strpos($this->AC3header['syncinfo'], self::syncword) === 0) { + $thisfile_ac3_raw['synchinfo']['synchword'] = self::syncword; + $offset = 2; + } else { + if (!$this->isDependencyFor('matroska')) { + unset($info['fileformat'], $info['ac3']); + return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($this->AC3header['syncinfo'], 0, 2)).'"'); + } + $offset = 0; + $this->fseek(-2, SEEK_CUR); + } + + $info['audio']['dataformat'] = 'ac3'; + $info['audio']['bitrate_mode'] = 'cbr'; + $info['audio']['lossless'] = false; + + $thisfile_ac3_raw['synchinfo']['crc1'] = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], $offset, 2)); + $ac3_synchinfo_fscod_frmsizecod = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], ($offset + 2), 1)); + $thisfile_ac3_raw['synchinfo']['fscod'] = ($ac3_synchinfo_fscod_frmsizecod & 0xC0) >> 6; + $thisfile_ac3_raw['synchinfo']['frmsizecod'] = ($ac3_synchinfo_fscod_frmsizecod & 0x3F); + + $thisfile_ac3['sample_rate'] = self::sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']); + if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) { + $info['audio']['sample_rate'] = $thisfile_ac3['sample_rate']; + } + + $thisfile_ac3['frame_length'] = self::frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']); + $thisfile_ac3['bitrate'] = self::bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']); + $info['audio']['bitrate'] = $thisfile_ac3['bitrate']; + + $this->AC3header['bsi'] = getid3_lib::BigEndian2Bin($this->fread(15)); + $ac3_bsi_offset = 0; + + $thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5); + if ($thisfile_ac3_raw_bsi['bsid'] > 8) { + // Decoders which can decode version 8 will thus be able to decode version numbers less than 8. + // If this standard is extended by the addition of additional elements or features, a value of bsid greater than 8 will be used. + // Decoders built to this version of the standard will not be able to decode versions with bsid greater than 8. + $this->error('Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8'); + unset($info['ac3']); + return false; + } + + $thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3); + $thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3); + + $thisfile_ac3['service_type'] = self::serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']); + $ac3_coding_mode = self::audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']); + foreach($ac3_coding_mode as $key => $value) { + $thisfile_ac3[$key] = $value; + } + switch ($thisfile_ac3_raw_bsi['acmod']) { + case 0: + case 1: + $info['audio']['channelmode'] = 'mono'; + break; + case 3: + case 4: + $info['audio']['channelmode'] = 'stereo'; + break; + default: + $info['audio']['channelmode'] = 'surround'; + break; + } + $info['audio']['channels'] = $thisfile_ac3['num_channels']; + + if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) { + // If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream. + $thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2); + $thisfile_ac3['center_mix_level'] = self::centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']); + } + + if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) { + // If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream. + $thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2); + $thisfile_ac3['surround_mix_level'] = self::surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']); + } + + if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) { + // When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround. + $thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2); + $thisfile_ac3['dolby_surround_mode'] = self::dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']); + } + + $thisfile_ac3_raw_bsi['lfeon'] = (bool) $this->readHeaderBSI(1); + $thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['lfeon']; + if ($thisfile_ac3_raw_bsi['lfeon']) { + //$info['audio']['channels']++; + $info['audio']['channels'] .= '.1'; + } + + $thisfile_ac3['channels_enabled'] = self::channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']); + + // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31. + // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent. + $thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5); + $thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB'; + + $thisfile_ac3_raw_bsi['compre_flag'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['compre_flag']) { + $thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8); + $thisfile_ac3['heavy_compression'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr']); + } + + $thisfile_ac3_raw_bsi['langcode_flag'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['langcode_flag']) { + $thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8); + } + + $thisfile_ac3_raw_bsi['audprodie'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['audprodie']) { + $thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5); + $thisfile_ac3_raw_bsi['roomtyp'] = $this->readHeaderBSI(2); + + $thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB'; + $thisfile_ac3['room_type'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']); + } + + if ($thisfile_ac3_raw_bsi['acmod'] == 0x00) { + // If acmod is 0, then two completely independent program channels (dual mono) + // are encoded into the bit stream, and are referenced as Ch1, Ch2. In this case, + // a number of additional items are present in BSI or audblk to fully describe Ch2. + + // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1-31. + // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent. + $thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5); + $thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB'; + + $thisfile_ac3_raw_bsi['compre_flag2'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['compre_flag2']) { + $thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8); + $thisfile_ac3['heavy_compression2'] = self::heavyCompression($thisfile_ac3_raw_bsi['compr2']); + } + + $thisfile_ac3_raw_bsi['langcode_flag2'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['langcode_flag2']) { + $thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8); + } + + $thisfile_ac3_raw_bsi['audprodie2'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['audprodie2']) { + $thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5); + $thisfile_ac3_raw_bsi['roomtyp2'] = $this->readHeaderBSI(2); + + $thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB'; + $thisfile_ac3['room_type2'] = self::roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']); + } + + } + + $thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1); + + $thisfile_ac3_raw_bsi['original'] = (bool) $this->readHeaderBSI(1); + + $thisfile_ac3_raw_bsi['timecode1_flag'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['timecode1_flag']) { + $thisfile_ac3_raw_bsi['timecode1'] = $this->readHeaderBSI(14); + } + + $thisfile_ac3_raw_bsi['timecode2_flag'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['timecode2_flag']) { + $thisfile_ac3_raw_bsi['timecode2'] = $this->readHeaderBSI(14); + } + + $thisfile_ac3_raw_bsi['addbsi_flag'] = (bool) $this->readHeaderBSI(1); + if ($thisfile_ac3_raw_bsi['addbsi_flag']) { + $thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6); + + $this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin($this->fread($thisfile_ac3_raw_bsi['addbsi_length'])); + + $thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8); + $this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8; + } + + return true; + } + + private function readHeaderBSI($length) { + $data = substr($this->AC3header['bsi'], $this->BSIoffset, $length); + $this->BSIoffset += $length; + + return bindec($data); + } + + public static function sampleRateCodeLookup($fscod) { + static $sampleRateCodeLookup = array( + 0 => 48000, + 1 => 44100, + 2 => 32000, + 3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute. + ); + return (isset($sampleRateCodeLookup[$fscod]) ? $sampleRateCodeLookup[$fscod] : false); + } + + public static function serviceTypeLookup($bsmod, $acmod) { + static $serviceTypeLookup = array(); + if (empty($serviceTypeLookup)) { + for ($i = 0; $i <= 7; $i++) { + $serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)'; + $serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)'; + $serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)'; + $serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)'; + $serviceTypeLookup[4][$i] = 'associated service: dialogue (D)'; + $serviceTypeLookup[5][$i] = 'associated service: commentary (C)'; + $serviceTypeLookup[6][$i] = 'associated service: emergency (E)'; + } + + $serviceTypeLookup[7][1] = 'associated service: voice over (VO)'; + for ($i = 2; $i <= 7; $i++) { + $serviceTypeLookup[7][$i] = 'main audio service: karaoke'; + } + } + return (isset($serviceTypeLookup[$bsmod][$acmod]) ? $serviceTypeLookup[$bsmod][$acmod] : false); + } + + public static function audioCodingModeLookup($acmod) { + // array(channel configuration, # channels (not incl LFE), channel order) + static $audioCodingModeLookup = array ( + 0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'), + 1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'), + 2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'), + 3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'), + 4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'), + 5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'), + 6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'), + 7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR'), + ); + return (isset($audioCodingModeLookup[$acmod]) ? $audioCodingModeLookup[$acmod] : false); + } + + public static function centerMixLevelLookup($cmixlev) { + static $centerMixLevelLookup; + if (empty($centerMixLevelLookup)) { + $centerMixLevelLookup = array( + 0 => pow(2, -3.0 / 6), // 0.707 (-3.0 dB) + 1 => pow(2, -4.5 / 6), // 0.595 (-4.5 dB) + 2 => pow(2, -6.0 / 6), // 0.500 (-6.0 dB) + 3 => 'reserved' + ); + } + return (isset($centerMixLevelLookup[$cmixlev]) ? $centerMixLevelLookup[$cmixlev] : false); + } + + public static function surroundMixLevelLookup($surmixlev) { + static $surroundMixLevelLookup; + if (empty($surroundMixLevelLookup)) { + $surroundMixLevelLookup = array( + 0 => pow(2, -3.0 / 6), + 1 => pow(2, -6.0 / 6), + 2 => 0, + 3 => 'reserved' + ); + } + return (isset($surroundMixLevelLookup[$surmixlev]) ? $surroundMixLevelLookup[$surmixlev] : false); + } + + public static function dolbySurroundModeLookup($dsurmod) { + static $dolbySurroundModeLookup = array( + 0 => 'not indicated', + 1 => 'Not Dolby Surround encoded', + 2 => 'Dolby Surround encoded', + 3 => 'reserved' + ); + return (isset($dolbySurroundModeLookup[$dsurmod]) ? $dolbySurroundModeLookup[$dsurmod] : false); + } + + public static function channelsEnabledLookup($acmod, $lfeon) { + $lookup = array( + 'ch1'=>(bool) ($acmod == 0), + 'ch2'=>(bool) ($acmod == 0), + 'left'=>(bool) ($acmod > 1), + 'right'=>(bool) ($acmod > 1), + 'center'=>(bool) ($acmod & 0x01), + 'surround_mono'=>false, + 'surround_left'=>false, + 'surround_right'=>false, + 'lfe'=>$lfeon); + switch ($acmod) { + case 4: + case 5: + $lookup['surround_mono'] = true; + break; + case 6: + case 7: + $lookup['surround_left'] = true; + $lookup['surround_right'] = true; + break; + } + return $lookup; + } + + public static function heavyCompression($compre) { + // The first four bits indicate gain changes in 6.02dB increments which can be + // implemented with an arithmetic shift operation. The following four bits + // indicate linear gain changes, and require a 5-bit multiply. + // We will represent the two 4-bit fields of compr as follows: + // X0 X1 X2 X3 . Y4 Y5 Y6 Y7 + // The meaning of the X values is most simply described by considering X to represent a 4-bit + // signed integer with values from -8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The + // following table shows this in detail. + + // Meaning of 4 msb of compr + // 7 +48.16 dB + // 6 +42.14 dB + // 5 +36.12 dB + // 4 +30.10 dB + // 3 +24.08 dB + // 2 +18.06 dB + // 1 +12.04 dB + // 0 +6.02 dB + // -1 0 dB + // -2 -6.02 dB + // -3 -12.04 dB + // -4 -18.06 dB + // -5 -24.08 dB + // -6 -30.10 dB + // -7 -36.12 dB + // -8 -42.14 dB + + $fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT); + if ($fourbit{0} == '1') { + $log_gain = -8 + bindec(substr($fourbit, 1)); + } else { + $log_gain = bindec(substr($fourbit, 1)); + } + $log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2); + + // The value of Y is a linear representation of a gain change of up to -6 dB. Y is considered to + // be an unsigned fractional integer, with a leading value of 1, or: 0.1 Y4 Y5 Y6 Y7 (base 2). Y can + // represent values between 0.111112 (or 31/32) and 0.100002 (or 1/2). Thus, Y can represent gain + // changes from -0.28 dB to -6.02 dB. + + $lin_gain = (16 + ($compre & 0x0F)) / 32; + + // The combination of X and Y values allows compr to indicate gain changes from + // 48.16 - 0.28 = +47.89 dB, to + // -42.14 - 6.02 = -48.16 dB. + + return $log_gain - $lin_gain; + } + + public static function roomTypeLookup($roomtyp) { + static $roomTypeLookup = array( + 0 => 'not indicated', + 1 => 'large room, X curve monitor', + 2 => 'small room, flat monitor', + 3 => 'reserved' + ); + return (isset($roomTypeLookup[$roomtyp]) ? $roomTypeLookup[$roomtyp] : false); + } + + public static function frameSizeLookup($frmsizecod, $fscod) { + $padding = (bool) ($frmsizecod % 2); + $framesizeid = floor($frmsizecod / 2); + + static $frameSizeLookup = array(); + if (empty($frameSizeLookup)) { + $frameSizeLookup = array ( + 0 => array(128, 138, 192), + 1 => array(40, 160, 174, 240), + 2 => array(48, 192, 208, 288), + 3 => array(56, 224, 242, 336), + 4 => array(64, 256, 278, 384), + 5 => array(80, 320, 348, 480), + 6 => array(96, 384, 416, 576), + 7 => array(112, 448, 486, 672), + 8 => array(128, 512, 556, 768), + 9 => array(160, 640, 696, 960), + 10 => array(192, 768, 834, 1152), + 11 => array(224, 896, 974, 1344), + 12 => array(256, 1024, 1114, 1536), + 13 => array(320, 1280, 1392, 1920), + 14 => array(384, 1536, 1670, 2304), + 15 => array(448, 1792, 1950, 2688), + 16 => array(512, 2048, 2228, 3072), + 17 => array(576, 2304, 2506, 3456), + 18 => array(640, 2560, 2786, 3840) + ); + } + if (($fscod == 1) && $padding) { + // frame lengths are padded by 1 word (16 bits) at 44100 + $frameSizeLookup[$frmsizecod] += 2; + } + return (isset($frameSizeLookup[$framesizeid][$fscod]) ? $frameSizeLookup[$framesizeid][$fscod] : false); + } + + public static function bitrateLookup($frmsizecod) { + $framesizeid = floor($frmsizecod / 2); + + static $bitrateLookup = array( + 0 => 32000, + 1 => 40000, + 2 => 48000, + 3 => 56000, + 4 => 64000, + 5 => 80000, + 6 => 96000, + 7 => 112000, + 8 => 128000, + 9 => 160000, + 10 => 192000, + 11 => 224000, + 12 => 256000, + 13 => 320000, + 14 => 384000, + 15 => 448000, + 16 => 512000, + 17 => 576000, + 18 => 640000 + ); + return (isset($bitrateLookup[$framesizeid]) ? $bitrateLookup[$framesizeid] : false); + } + + +} diff --git a/extensions/TimedMediaHandler/libs/getid3/module.audio.dts.php b/extensions/TimedMediaHandler/libs/getid3/module.audio.dts.php new file mode 100644 index 00000000..1c6b36f9 --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/module.audio.dts.php @@ -0,0 +1,291 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.dts.php // +// module for analyzing DTS Audio files // +// dependencies: NONE // +// // +///////////////////////////////////////////////////////////////// + + +/** +* @tutorial http://wiki.multimedia.cx/index.php?title=DTS +*/ + +class getid3_dts extends getid3_handler +{ + /** + * Default DTS syncword used in native .cpt or .dts formats + */ + const syncword = "\x7F\xFE\x80\x01"; + + private $readBinDataOffset = 0; + + /** + * Possible syncwords indicating bitstream encoding + */ + public static $syncwords = array( + 0 => "\x7F\xFE\x80\x01", // raw big-endian + 1 => "\xFE\x7F\x01\x80", // raw little-endian + 2 => "\x1F\xFF\xE8\x00", // 14-bit big-endian + 3 => "\xFF\x1F\x00\xE8"); // 14-bit little-endian + + public function Analyze() { + $info = &$this->getid3->info; + $info['fileformat'] = 'dts'; + + $this->fseek($info['avdataoffset']); + $DTSheader = $this->fread(20); // we only need 2 words magic + 6 words frame header, but these words may be normal 16-bit words OR 14-bit words with 2 highest bits set to zero, so 8 words can be either 8*16/8 = 16 bytes OR 8*16*(16/14)/8 = 18.3 bytes + + // check syncword + $sync = substr($DTSheader, 0, 4); + if (($encoding = array_search($sync, self::$syncwords)) !== false) { + + $info['dts']['raw']['magic'] = $sync; + $this->readBinDataOffset = 32; + + } elseif ($this->isDependencyFor('matroska')) { + + // Matroska contains DTS without syncword encoded as raw big-endian format + $encoding = 0; + $this->readBinDataOffset = 0; + + } else { + + unset($info['fileformat']); + return $this->error('Expecting "'.implode('| ', array_map('getid3_lib::PrintHexBytes', self::$syncwords)).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($sync).'"'); + + } + + // decode header + $fhBS = ''; + for ($word_offset = 0; $word_offset <= strlen($DTSheader); $word_offset += 2) { + switch ($encoding) { + case 0: // raw big-endian + $fhBS .= getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) ); + break; + case 1: // raw little-endian + $fhBS .= getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))); + break; + case 2: // 14-bit big-endian + $fhBS .= substr(getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) ), 2, 14); + break; + case 3: // 14-bit little-endian + $fhBS .= substr(getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))), 2, 14); + break; + } + } + + $info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, 1); + $info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, 5); + $info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, 7); + $info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, 14); + $info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, 6); + $info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, 4); + $info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, 5); + $info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, 3); + $info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, 2); + $info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, 1); + if ($info['dts']['flags']['crc_present']) { + $info['dts']['raw']['crc16'] = $this->readBinData($fhBS, 16); + } + $info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, 4); + $info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, 2); + $info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, 2); + $info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, 1); + $info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, 4); + + + $info['dts']['bitrate'] = self::bitrateLookup($info['dts']['raw']['bitrate']); + $info['dts']['bits_per_sample'] = self::bitPerSampleLookup($info['dts']['raw']['bits_per_sample']); + $info['dts']['sample_rate'] = self::sampleRateLookup($info['dts']['raw']['sample_frequency']); + $info['dts']['dialog_normalization'] = self::dialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']); + $info['dts']['flags']['lossless'] = (($info['dts']['raw']['bitrate'] == 31) ? true : false); + $info['dts']['bitrate_mode'] = (($info['dts']['raw']['bitrate'] == 30) ? 'vbr' : 'cbr'); + $info['dts']['channels'] = self::numChannelsLookup($info['dts']['raw']['channel_arrangement']); + $info['dts']['channel_arrangement'] = self::channelArrangementLookup($info['dts']['raw']['channel_arrangement']); + + $info['audio']['dataformat'] = 'dts'; + $info['audio']['lossless'] = $info['dts']['flags']['lossless']; + $info['audio']['bitrate_mode'] = $info['dts']['bitrate_mode']; + $info['audio']['bits_per_sample'] = $info['dts']['bits_per_sample']; + $info['audio']['sample_rate'] = $info['dts']['sample_rate']; + $info['audio']['channels'] = $info['dts']['channels']; + $info['audio']['bitrate'] = $info['dts']['bitrate']; + if (isset($info['avdataend']) && !empty($info['dts']['bitrate']) && is_numeric($info['dts']['bitrate'])) { + $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8); + if (($encoding == 2) || ($encoding == 3)) { + // 14-bit data packed into 16-bit words, so the playtime is wrong because only (14/16) of the bytes in the data portion of the file are used at the specified bitrate + $info['playtime_seconds'] *= (14 / 16); + } + } + return true; + } + + private function readBinData($bin, $length) { + $data = substr($bin, $this->readBinDataOffset, $length); + $this->readBinDataOffset += $length; + + return bindec($data); + } + + public static function bitrateLookup($index) { + static $lookup = array( + 0 => 32000, + 1 => 56000, + 2 => 64000, + 3 => 96000, + 4 => 112000, + 5 => 128000, + 6 => 192000, + 7 => 224000, + 8 => 256000, + 9 => 320000, + 10 => 384000, + 11 => 448000, + 12 => 512000, + 13 => 576000, + 14 => 640000, + 15 => 768000, + 16 => 960000, + 17 => 1024000, + 18 => 1152000, + 19 => 1280000, + 20 => 1344000, + 21 => 1408000, + 22 => 1411200, + 23 => 1472000, + 24 => 1536000, + 25 => 1920000, + 26 => 2048000, + 27 => 3072000, + 28 => 3840000, + 29 => 'open', + 30 => 'variable', + 31 => 'lossless', + ); + return (isset($lookup[$index]) ? $lookup[$index] : false); + } + + public static function sampleRateLookup($index) { + static $lookup = array( + 0 => 'invalid', + 1 => 8000, + 2 => 16000, + 3 => 32000, + 4 => 'invalid', + 5 => 'invalid', + 6 => 11025, + 7 => 22050, + 8 => 44100, + 9 => 'invalid', + 10 => 'invalid', + 11 => 12000, + 12 => 24000, + 13 => 48000, + 14 => 'invalid', + 15 => 'invalid', + ); + return (isset($lookup[$index]) ? $lookup[$index] : false); + } + + public static function bitPerSampleLookup($index) { + static $lookup = array( + 0 => 16, + 1 => 20, + 2 => 24, + 3 => 24, + ); + return (isset($lookup[$index]) ? $lookup[$index] : false); + } + + public static function numChannelsLookup($index) { + switch ($index) { + case 0: + return 1; + break; + case 1: + case 2: + case 3: + case 4: + return 2; + break; + case 5: + case 6: + return 3; + break; + case 7: + case 8: + return 4; + break; + case 9: + return 5; + break; + case 10: + case 11: + case 12: + return 6; + break; + case 13: + return 7; + break; + case 14: + case 15: + return 8; + break; + } + return false; + } + + public static function channelArrangementLookup($index) { + static $lookup = array( + 0 => 'A', + 1 => 'A + B (dual mono)', + 2 => 'L + R (stereo)', + 3 => '(L+R) + (L-R) (sum-difference)', + 4 => 'LT + RT (left and right total)', + 5 => 'C + L + R', + 6 => 'L + R + S', + 7 => 'C + L + R + S', + 8 => 'L + R + SL + SR', + 9 => 'C + L + R + SL + SR', + 10 => 'CL + CR + L + R + SL + SR', + 11 => 'C + L + R+ LR + RR + OV', + 12 => 'CF + CR + LF + RF + LR + RR', + 13 => 'CL + C + CR + L + R + SL + SR', + 14 => 'CL + CR + L + R + SL1 + SL2 + SR1 + SR2', + 15 => 'CL + C+ CR + L + R + SL + S + SR', + ); + return (isset($lookup[$index]) ? $lookup[$index] : 'user-defined'); + } + + public static function dialogNormalization($index, $version) { + switch ($version) { + case 7: + return 0 - $index; + break; + case 6: + return 0 - 16 - $index; + break; + } + return false; + } + +} diff --git a/extensions/TimedMediaHandler/libs/getid3/module.audio.flac.php b/extensions/TimedMediaHandler/libs/getid3/module.audio.flac.php new file mode 100644 index 00000000..6b9598c7 --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/module.audio.flac.php @@ -0,0 +1,442 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// 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'); + } + +} diff --git a/extensions/TimedMediaHandler/libs/getid3/module.audio.mp3.php b/extensions/TimedMediaHandler/libs/getid3/module.audio.mp3.php new file mode 100644 index 00000000..e6ffea94 --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/module.audio.mp3.php @@ -0,0 +1,2009 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.mp3.php // +// module for analyzing MP3 files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +// number of frames to scan to determine if MPEG-audio sequence is valid +// Lower this number to 5-20 for faster scanning +// Increase this number to 50+ for most accurate detection of valid VBR/CBR +// mpeg-audio streams +define('GETID3_MP3_VALID_CHECK_FRAMES', 35); + + +class getid3_mp3 extends getid3_handler +{ + + public $allow_bruteforce = false; // forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, unrecommended, but may provide data from otherwise-unusuable files + + public function Analyze() { + $info = &$this->getid3->info; + + $initialOffset = $info['avdataoffset']; + + if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) { + if ($this->allow_bruteforce) { + $info['error'][] = 'Rescanning file in BruteForce mode'; + $this->getOnlyMPEGaudioInfoBruteForce($this->getid3->fp, $info); + } + } + + + if (isset($info['mpeg']['audio']['bitrate_mode'])) { + $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']); + } + + if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) { + + $synchoffsetwarning = 'Unknown data before synch '; + if (isset($info['id3v2']['headerlength'])) { + $synchoffsetwarning .= '(ID3v2 header ends at '.$info['id3v2']['headerlength'].', then '.($info['avdataoffset'] - $info['id3v2']['headerlength']).' bytes garbage, '; + } elseif ($initialOffset > 0) { + $synchoffsetwarning .= '(should be at '.$initialOffset.', '; + } else { + $synchoffsetwarning .= '(should be at beginning of file, '; + } + $synchoffsetwarning .= 'synch detected at '.$info['avdataoffset'].')'; + if (isset($info['audio']['bitrate_mode']) && ($info['audio']['bitrate_mode'] == 'cbr')) { + + if (!empty($info['id3v2']['headerlength']) && (($info['avdataoffset'] - $info['id3v2']['headerlength']) == $info['mpeg']['audio']['framelength'])) { + + $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90-3.92) DLL in CBR mode.'; + $info['audio']['codec'] = 'LAME'; + $CurrentDataLAMEversionString = 'LAME3.'; + + } elseif (empty($info['id3v2']['headerlength']) && ($info['avdataoffset'] == $info['mpeg']['audio']['framelength'])) { + + $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90 - 3.92) DLL in CBR mode.'; + $info['audio']['codec'] = 'LAME'; + $CurrentDataLAMEversionString = 'LAME3.'; + + } + + } + $info['warning'][] = $synchoffsetwarning; + + } + + if (isset($info['mpeg']['audio']['LAME'])) { + $info['audio']['codec'] = 'LAME'; + if (!empty($info['mpeg']['audio']['LAME']['long_version'])) { + $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['long_version'], "\x00"); + } elseif (!empty($info['mpeg']['audio']['LAME']['short_version'])) { + $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['short_version'], "\x00"); + } + } + + $CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : (isset($info['audio']['encoder']) ? $info['audio']['encoder'] : '')); + if (!empty($CurrentDataLAMEversionString) && (substr($CurrentDataLAMEversionString, 0, 6) == 'LAME3.') && !preg_match('[0-9\)]', substr($CurrentDataLAMEversionString, -1))) { + // a version number of LAME that does not end with a number like "LAME3.92" + // or with a closing parenthesis like "LAME3.88 (alpha)" + // or a version of LAME with the LAMEtag-not-filled-in-DLL-mode bug (3.90-3.92) + + // not sure what the actual last frame length will be, but will be less than or equal to 1441 + $PossiblyLongerLAMEversion_FrameLength = 1441; + + // Not sure what version of LAME this is - look in padding of last frame for longer version string + $PossibleLAMEversionStringOffset = $info['avdataend'] - $PossiblyLongerLAMEversion_FrameLength; + fseek($this->getid3->fp, $PossibleLAMEversionStringOffset); + $PossiblyLongerLAMEversion_Data = fread($this->getid3->fp, $PossiblyLongerLAMEversion_FrameLength); + switch (substr($CurrentDataLAMEversionString, -1)) { + case 'a': + case 'b': + // "LAME3.94a" will have a longer version string of "LAME3.94 (alpha)" for example + // need to trim off "a" to match longer string + $CurrentDataLAMEversionString = substr($CurrentDataLAMEversionString, 0, -1); + break; + } + if (($PossiblyLongerLAMEversion_String = strstr($PossiblyLongerLAMEversion_Data, $CurrentDataLAMEversionString)) !== false) { + if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) { + $PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)" + if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) { + $info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString; + } + } + } + } + if (!empty($info['audio']['encoder'])) { + $info['audio']['encoder'] = rtrim($info['audio']['encoder'], "\x00 "); + } + + switch (isset($info['mpeg']['audio']['layer']) ? $info['mpeg']['audio']['layer'] : '') { + case 1: + case 2: + $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer']; + break; + } + if (isset($info['fileformat']) && ($info['fileformat'] == 'mp3')) { + switch ($info['audio']['dataformat']) { + case 'mp1': + case 'mp2': + case 'mp3': + $info['fileformat'] = $info['audio']['dataformat']; + break; + + default: + $info['warning'][] = 'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"'; + break; + } + } + + if (empty($info['fileformat'])) { + unset($info['fileformat']); + unset($info['audio']['bitrate_mode']); + unset($info['avdataoffset']); + unset($info['avdataend']); + return false; + } + + $info['mime_type'] = 'audio/mpeg'; + $info['audio']['lossless'] = false; + + // Calculate playtime + if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) { + $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['audio']['bitrate']; + } + + $info['audio']['encoder_options'] = $this->GuessEncoderOptions(); + + return true; + } + + + public function GuessEncoderOptions() { + // shortcuts + $info = &$this->getid3->info; + if (!empty($info['mpeg']['audio'])) { + $thisfile_mpeg_audio = &$info['mpeg']['audio']; + if (!empty($thisfile_mpeg_audio['LAME'])) { + $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME']; + } + } + + $encoder_options = ''; + static $NamedPresetBitrates = array(16, 24, 40, 56, 112, 128, 160, 192, 256); + + if (isset($thisfile_mpeg_audio['VBR_method']) && ($thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) { + + $encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality']; + + } elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) { + + $encoder_options = $thisfile_mpeg_audio_lame['preset_used']; + + } elseif (!empty($thisfile_mpeg_audio_lame['vbr_quality'])) { + + static $KnownEncoderValues = array(); + if (empty($KnownEncoderValues)) { + + //$KnownEncoderValues[abrbitrate_minbitrate][vbr_quality][raw_vbr_method][raw_noise_shaping][raw_stereo_mode][ath_type][lowpass_frequency] = 'preset name'; + $KnownEncoderValues[0xFF][58][1][1][3][2][20500] = '--alt-preset insane'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues[0xFF][58][1][1][3][2][20600] = '--alt-preset insane'; // 3.90.2, 3.90.3, 3.91 + $KnownEncoderValues[0xFF][57][1][1][3][4][20500] = '--alt-preset insane'; // 3.94, 3.95 + $KnownEncoderValues['**'][78][3][2][3][2][19500] = '--alt-preset extreme'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues['**'][78][3][2][3][2][19600] = '--alt-preset extreme'; // 3.90.2, 3.91 + $KnownEncoderValues['**'][78][3][1][3][2][19600] = '--alt-preset extreme'; // 3.90.3 + $KnownEncoderValues['**'][78][4][2][3][2][19500] = '--alt-preset fast extreme'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues['**'][78][4][2][3][2][19600] = '--alt-preset fast extreme'; // 3.90.2, 3.90.3, 3.91 + $KnownEncoderValues['**'][78][3][2][3][4][19000] = '--alt-preset standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues['**'][78][3][1][3][4][19000] = '--alt-preset standard'; // 3.90.3 + $KnownEncoderValues['**'][78][4][2][3][4][19000] = '--alt-preset fast standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues['**'][78][4][1][3][4][19000] = '--alt-preset fast standard'; // 3.90.3 + $KnownEncoderValues['**'][88][4][1][3][3][19500] = '--r3mix'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues['**'][88][4][1][3][3][19600] = '--r3mix'; // 3.90.2, 3.90.3, 3.91 + $KnownEncoderValues['**'][67][4][1][3][4][18000] = '--r3mix'; // 3.94, 3.95 + $KnownEncoderValues['**'][68][3][2][3][4][18000] = '--alt-preset medium'; // 3.90.3 + $KnownEncoderValues['**'][68][4][2][3][4][18000] = '--alt-preset fast medium'; // 3.90.3 + + $KnownEncoderValues[0xFF][99][1][1][1][2][0] = '--preset studio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0xFF][58][2][1][3][2][20600] = '--preset studio'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0xFF][58][2][1][3][2][20500] = '--preset studio'; // 3.93 + $KnownEncoderValues[0xFF][57][2][1][3][4][20500] = '--preset studio'; // 3.94, 3.95 + $KnownEncoderValues[0xC0][88][1][1][1][2][0] = '--preset cd'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0xC0][58][2][2][3][2][19600] = '--preset cd'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0xC0][58][2][2][3][2][19500] = '--preset cd'; // 3.93 + $KnownEncoderValues[0xC0][57][2][1][3][4][19500] = '--preset cd'; // 3.94, 3.95 + $KnownEncoderValues[0xA0][78][1][1][3][2][18000] = '--preset hifi'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0xA0][58][2][2][3][2][18000] = '--preset hifi'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0xA0][57][2][1][3][4][18000] = '--preset hifi'; // 3.94, 3.95 + $KnownEncoderValues[0x80][67][1][1][3][2][18000] = '--preset tape'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0x80][67][1][1][3][2][15000] = '--preset radio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0x70][67][1][1][3][2][15000] = '--preset fm'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0x70][58][2][2][3][2][16000] = '--preset tape/radio/fm'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0x70][57][2][1][3][4][16000] = '--preset tape/radio/fm'; // 3.94, 3.95 + $KnownEncoderValues[0x38][58][2][2][0][2][10000] = '--preset voice'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0x38][57][2][1][0][4][15000] = '--preset voice'; // 3.94, 3.95 + $KnownEncoderValues[0x38][57][2][1][0][4][16000] = '--preset voice'; // 3.94a14 + $KnownEncoderValues[0x28][65][1][1][0][2][7500] = '--preset mw-us'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues[0x28][65][1][1][0][2][7600] = '--preset mw-us'; // 3.90.2, 3.91 + $KnownEncoderValues[0x28][58][2][2][0][2][7000] = '--preset mw-us'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0x28][57][2][1][0][4][10500] = '--preset mw-us'; // 3.94, 3.95 + $KnownEncoderValues[0x28][57][2][1][0][4][11200] = '--preset mw-us'; // 3.94a14 + $KnownEncoderValues[0x28][57][2][1][0][4][8800] = '--preset mw-us'; // 3.94a15 + $KnownEncoderValues[0x18][58][2][2][0][2][4000] = '--preset phon+/lw/mw-eu/sw'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0x18][58][2][2][0][2][3900] = '--preset phon+/lw/mw-eu/sw'; // 3.93 + $KnownEncoderValues[0x18][57][2][1][0][4][5900] = '--preset phon+/lw/mw-eu/sw'; // 3.94, 3.95 + $KnownEncoderValues[0x18][57][2][1][0][4][6200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a14 + $KnownEncoderValues[0x18][57][2][1][0][4][3200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a15 + $KnownEncoderValues[0x10][58][2][2][0][2][3800] = '--preset phone'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0x10][58][2][2][0][2][3700] = '--preset phone'; // 3.93 + $KnownEncoderValues[0x10][57][2][1][0][4][5600] = '--preset phone'; // 3.94, 3.95 + } + + if (isset($KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) { + + $encoder_options = $KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']]; + + } elseif (isset($KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) { + + $encoder_options = $KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']]; + + } elseif ($info['audio']['bitrate_mode'] == 'vbr') { + + // http://gabriel.mp3-tech.org/mp3infotag.html + // int Quality = (100 - 10 * gfp->VBR_q - gfp->quality)h + + + $LAME_V_value = 10 - ceil($thisfile_mpeg_audio_lame['vbr_quality'] / 10); + $LAME_q_value = 100 - $thisfile_mpeg_audio_lame['vbr_quality'] - ($LAME_V_value * 10); + $encoder_options = '-V'.$LAME_V_value.' -q'.$LAME_q_value; + + } elseif ($info['audio']['bitrate_mode'] == 'cbr') { + + $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); + + } else { + + $encoder_options = strtoupper($info['audio']['bitrate_mode']); + + } + + } elseif (!empty($thisfile_mpeg_audio_lame['bitrate_abr'])) { + + $encoder_options = 'ABR'.$thisfile_mpeg_audio_lame['bitrate_abr']; + + } elseif (!empty($info['audio']['bitrate'])) { + + if ($info['audio']['bitrate_mode'] == 'cbr') { + $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); + } else { + $encoder_options = strtoupper($info['audio']['bitrate_mode']); + } + + } + if (!empty($thisfile_mpeg_audio_lame['bitrate_min'])) { + $encoder_options .= ' -b'.$thisfile_mpeg_audio_lame['bitrate_min']; + } + + if (!empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']) || !empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'])) { + $encoder_options .= ' --nogap'; + } + + if (!empty($thisfile_mpeg_audio_lame['lowpass_frequency'])) { + $ExplodedOptions = explode(' ', $encoder_options, 4); + if ($ExplodedOptions[0] == '--r3mix') { + $ExplodedOptions[1] = 'r3mix'; + } + switch ($ExplodedOptions[0]) { + case '--preset': + case '--alt-preset': + case '--r3mix': + if ($ExplodedOptions[1] == 'fast') { + $ExplodedOptions[1] .= ' '.$ExplodedOptions[2]; + } + switch ($ExplodedOptions[1]) { + case 'portable': + case 'medium': + case 'standard': + case 'extreme': + case 'insane': + case 'fast portable': + case 'fast medium': + case 'fast standard': + case 'fast extreme': + case 'fast insane': + case 'r3mix': + static $ExpectedLowpass = array( + 'insane|20500' => 20500, + 'insane|20600' => 20600, // 3.90.2, 3.90.3, 3.91 + 'medium|18000' => 18000, + 'fast medium|18000' => 18000, + 'extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95 + 'extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1 + 'fast extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95 + 'fast extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1 + 'standard|19000' => 19000, + 'fast standard|19000' => 19000, + 'r3mix|19500' => 19500, // 3.90, 3.90.1, 3.92 + 'r3mix|19600' => 19600, // 3.90.2, 3.90.3, 3.91 + 'r3mix|18000' => 18000, // 3.94, 3.95 + ); + if (!isset($ExpectedLowpass[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio_lame['lowpass_frequency']]) && ($thisfile_mpeg_audio_lame['lowpass_frequency'] < 22050) && (round($thisfile_mpeg_audio_lame['lowpass_frequency'] / 1000) < round($thisfile_mpeg_audio['sample_rate'] / 2000))) { + $encoder_options .= ' --lowpass '.$thisfile_mpeg_audio_lame['lowpass_frequency']; + } + break; + + default: + break; + } + break; + } + } + + if (isset($thisfile_mpeg_audio_lame['raw']['source_sample_freq'])) { + if (($thisfile_mpeg_audio['sample_rate'] == 44100) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 1)) { + $encoder_options .= ' --resample 44100'; + } elseif (($thisfile_mpeg_audio['sample_rate'] == 48000) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 2)) { + $encoder_options .= ' --resample 48000'; + } elseif ($thisfile_mpeg_audio['sample_rate'] < 44100) { + switch ($thisfile_mpeg_audio_lame['raw']['source_sample_freq']) { + case 0: // <= 32000 + // may or may not be same as source frequency - ignore + break; + case 1: // 44100 + case 2: // 48000 + case 3: // 48000+ + $ExplodedOptions = explode(' ', $encoder_options, 4); + switch ($ExplodedOptions[0]) { + case '--preset': + case '--alt-preset': + switch ($ExplodedOptions[1]) { + case 'fast': + case 'portable': + case 'medium': + case 'standard': + case 'extreme': + case 'insane': + $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; + break; + + default: + static $ExpectedResampledRate = array( + 'phon+/lw/mw-eu/sw|16000' => 16000, + 'mw-us|24000' => 24000, // 3.95 + 'mw-us|32000' => 32000, // 3.93 + 'mw-us|16000' => 16000, // 3.92 + 'phone|16000' => 16000, + 'phone|11025' => 11025, // 3.94a15 + 'radio|32000' => 32000, // 3.94a15 + 'fm/radio|32000' => 32000, // 3.92 + 'fm|32000' => 32000, // 3.90 + 'voice|32000' => 32000); + if (!isset($ExpectedResampledRate[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio['sample_rate']])) { + $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; + } + break; + } + break; + + case '--r3mix': + default: + $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; + break; + } + break; + } + } + } + if (empty($encoder_options) && !empty($info['audio']['bitrate']) && !empty($info['audio']['bitrate_mode'])) { + //$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000); + $encoder_options = strtoupper($info['audio']['bitrate_mode']); + } + + return $encoder_options; + } + + + public function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) { + static $MPEGaudioVersionLookup; + static $MPEGaudioLayerLookup; + static $MPEGaudioBitrateLookup; + static $MPEGaudioFrequencyLookup; + static $MPEGaudioChannelModeLookup; + static $MPEGaudioModeExtensionLookup; + static $MPEGaudioEmphasisLookup; + if (empty($MPEGaudioVersionLookup)) { + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); + } + + if (fseek($this->getid3->fp, $offset, SEEK_SET) != 0) { + $info['error'][] = 'decodeMPEGaudioHeader() failed to seek to next offset at '.$offset; + return false; + } + //$headerstring = fread($this->getid3->fp, 1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame + $headerstring = fread($this->getid3->fp, 226); // LAME header at offset 36 + 190 bytes of Xing/LAME data + + // MP3 audio frame structure: + // $aa $aa $aa $aa [$bb $bb] $cc... + // where $aa..$aa is the four-byte mpeg-audio header (below) + // $bb $bb is the optional 2-byte CRC + // and $cc... is the audio data + + $head4 = substr($headerstring, 0, 4); + + static $MPEGaudioHeaderDecodeCache = array(); + if (isset($MPEGaudioHeaderDecodeCache[$head4])) { + $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4]; + } else { + $MPEGheaderRawArray = self::MPEGaudioHeaderDecode($head4); + $MPEGaudioHeaderDecodeCache[$head4] = $MPEGheaderRawArray; + } + + static $MPEGaudioHeaderValidCache = array(); + if (!isset($MPEGaudioHeaderValidCache[$head4])) { // Not in cache + //$MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true); // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1) + $MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false); + } + + // shortcut + if (!isset($info['mpeg']['audio'])) { + $info['mpeg']['audio'] = array(); + } + $thisfile_mpeg_audio = &$info['mpeg']['audio']; + + + if ($MPEGaudioHeaderValidCache[$head4]) { + $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray; + } else { + $info['error'][] = 'Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset; + return false; + } + + if (!$FastMPEGheaderScan) { + $thisfile_mpeg_audio['version'] = $MPEGaudioVersionLookup[$thisfile_mpeg_audio['raw']['version']]; + $thisfile_mpeg_audio['layer'] = $MPEGaudioLayerLookup[$thisfile_mpeg_audio['raw']['layer']]; + + $thisfile_mpeg_audio['channelmode'] = $MPEGaudioChannelModeLookup[$thisfile_mpeg_audio['raw']['channelmode']]; + $thisfile_mpeg_audio['channels'] = (($thisfile_mpeg_audio['channelmode'] == 'mono') ? 1 : 2); + $thisfile_mpeg_audio['sample_rate'] = $MPEGaudioFrequencyLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['raw']['sample_rate']]; + $thisfile_mpeg_audio['protection'] = !$thisfile_mpeg_audio['raw']['protection']; + $thisfile_mpeg_audio['private'] = (bool) $thisfile_mpeg_audio['raw']['private']; + $thisfile_mpeg_audio['modeextension'] = $MPEGaudioModeExtensionLookup[$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['modeextension']]; + $thisfile_mpeg_audio['copyright'] = (bool) $thisfile_mpeg_audio['raw']['copyright']; + $thisfile_mpeg_audio['original'] = (bool) $thisfile_mpeg_audio['raw']['original']; + $thisfile_mpeg_audio['emphasis'] = $MPEGaudioEmphasisLookup[$thisfile_mpeg_audio['raw']['emphasis']]; + + $info['audio']['channels'] = $thisfile_mpeg_audio['channels']; + $info['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate']; + + if ($thisfile_mpeg_audio['protection']) { + $thisfile_mpeg_audio['crc'] = getid3_lib::BigEndian2Int(substr($headerstring, 4, 2)); + } + } + + if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) { + // http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0 + $info['warning'][] = 'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1'; + $thisfile_mpeg_audio['raw']['bitrate'] = 0; + } + $thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding']; + $thisfile_mpeg_audio['bitrate'] = $MPEGaudioBitrateLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['bitrate']]; + + if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $info['avdataoffset'])) { + // only skip multiple frame check if free-format bitstream found at beginning of file + // otherwise is quite possibly simply corrupted data + $recursivesearch = false; + } + + // For Layer 2 there are some combinations of bitrate and mode which are not allowed. + if (!$FastMPEGheaderScan && ($thisfile_mpeg_audio['layer'] == '2')) { + + $info['audio']['dataformat'] = 'mp2'; + switch ($thisfile_mpeg_audio['channelmode']) { + + case 'mono': + if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) { + // these are ok + } else { + $info['error'][] = $thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; + return false; + } + break; + + case 'stereo': + case 'joint stereo': + case 'dual channel': + if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) { + // these are ok + } else { + $info['error'][] = intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; + return false; + } + break; + + } + + } + + + if ($info['audio']['sample_rate'] > 0) { + $thisfile_mpeg_audio['framelength'] = self::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $info['audio']['sample_rate']); + } + + $nextframetestoffset = $offset + 1; + if ($thisfile_mpeg_audio['bitrate'] != 'free') { + + $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; + + if (isset($thisfile_mpeg_audio['framelength'])) { + $nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength']; + } else { + $info['error'][] = 'Frame at offset('.$offset.') is has an invalid frame length.'; + return false; + } + + } + + $ExpectedNumberOfAudioBytes = 0; + + //////////////////////////////////////////////////////////////////////////////////// + // Variable-bitrate headers + + if (substr($headerstring, 4 + 32, 4) == 'VBRI') { + // Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36) + // specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html + + $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; + $thisfile_mpeg_audio['VBR_method'] = 'Fraunhofer'; + $info['audio']['codec'] = 'Fraunhofer'; + + $SideInfoData = substr($headerstring, 4 + 2, 32); + + $FraunhoferVBROffset = 36; + + $thisfile_mpeg_audio['VBR_encoder_version'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 4, 2)); // VbriVersion + $thisfile_mpeg_audio['VBR_encoder_delay'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 6, 2)); // VbriDelay + $thisfile_mpeg_audio['VBR_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 8, 2)); // VbriQuality + $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4)); // VbriStreamBytes + $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4)); // VbriStreamFrames + $thisfile_mpeg_audio['VBR_seek_offsets'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2)); // VbriTableSize + $thisfile_mpeg_audio['VBR_seek_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 2)); // VbriTableScale + $thisfile_mpeg_audio['VBR_entry_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 22, 2)); // VbriEntryBytes + $thisfile_mpeg_audio['VBR_entry_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2)); // VbriEntryFrames + + $ExpectedNumberOfAudioBytes = $thisfile_mpeg_audio['VBR_bytes']; + + $previousbyteoffset = $offset; + for ($i = 0; $i < $thisfile_mpeg_audio['VBR_seek_offsets']; $i++) { + $Fraunhofer_OffsetN = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, $thisfile_mpeg_audio['VBR_entry_bytes'])); + $FraunhoferVBROffset += $thisfile_mpeg_audio['VBR_entry_bytes']; + $thisfile_mpeg_audio['VBR_offsets_relative'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']); + $thisfile_mpeg_audio['VBR_offsets_absolute'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']) + $previousbyteoffset; + $previousbyteoffset += $Fraunhofer_OffsetN; + } + + + } else { + + // Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36) + // depending on MPEG layer and number of channels + + $VBRidOffset = self::XingVBRidOffset($thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['channelmode']); + $SideInfoData = substr($headerstring, 4 + 2, $VBRidOffset - 4); + + if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) { + // 'Xing' is traditional Xing VBR frame + // 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.) + // 'Info' *can* legally be used to specify a VBR file as well, however. + + // http://www.multiweb.cz/twoinches/MP3inside.htm + //00..03 = "Xing" or "Info" + //04..07 = Flags: + // 0x01 Frames Flag set if value for number of frames in file is stored + // 0x02 Bytes Flag set if value for filesize in bytes is stored + // 0x04 TOC Flag set if values for TOC are stored + // 0x08 VBR Scale Flag set if values for VBR scale is stored + //08..11 Frames: Number of frames in file (including the first Xing/Info one) + //12..15 Bytes: File length in Bytes + //16..115 TOC (Table of Contents): + // Contains of 100 indexes (one Byte length) for easier lookup in file. Approximately solves problem with moving inside file. + // Each Byte has a value according this formula: + // (TOC[i] / 256) * fileLenInBytes + // So if song lasts eg. 240 sec. and you want to jump to 60. sec. (and file is 5 000 000 Bytes length) you can use: + // TOC[(60/240)*100] = TOC[25] + // and corresponding Byte in file is then approximately at: + // (TOC[25]/256) * 5000000 + //116..119 VBR Scale + + + // should be safe to leave this at 'vbr' and let it be overriden to 'cbr' if a CBR preset/mode is used by LAME +// if (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Xing') { + $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; + $thisfile_mpeg_audio['VBR_method'] = 'Xing'; +// } else { +// $ScanAsCBR = true; +// $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; +// } + + $thisfile_mpeg_audio['xing_flags_raw'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4)); + + $thisfile_mpeg_audio['xing_flags']['frames'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000001); + $thisfile_mpeg_audio['xing_flags']['bytes'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000002); + $thisfile_mpeg_audio['xing_flags']['toc'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000004); + $thisfile_mpeg_audio['xing_flags']['vbr_scale'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000008); + + if ($thisfile_mpeg_audio['xing_flags']['frames']) { + $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 8, 4)); + //$thisfile_mpeg_audio['VBR_frames']--; // don't count header Xing/Info frame + } + if ($thisfile_mpeg_audio['xing_flags']['bytes']) { + $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4)); + } + + //if (($thisfile_mpeg_audio['bitrate'] == 'free') && !empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) { + if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) { + + $framelengthfloat = $thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']; + + if ($thisfile_mpeg_audio['layer'] == '1') { + // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 + //$info['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; + $info['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 12; + } else { + // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 + //$info['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; + $info['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 144; + } + $thisfile_mpeg_audio['framelength'] = floor($framelengthfloat); + } + + if ($thisfile_mpeg_audio['xing_flags']['toc']) { + $LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100); + for ($i = 0; $i < 100; $i++) { + $thisfile_mpeg_audio['toc'][$i] = ord($LAMEtocData{$i}); + } + } + if ($thisfile_mpeg_audio['xing_flags']['vbr_scale']) { + $thisfile_mpeg_audio['VBR_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4)); + } + + + // http://gabriel.mp3-tech.org/mp3infotag.html + if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') { + + // shortcut + $thisfile_mpeg_audio['LAME'] = array(); + $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME']; + + + $thisfile_mpeg_audio_lame['long_version'] = substr($headerstring, $VBRidOffset + 120, 20); + $thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9); + + if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') { + + // extra 11 chars are not part of version string when LAMEtag present + unset($thisfile_mpeg_audio_lame['long_version']); + + // It the LAME tag was only introduced in LAME v3.90 + // http://www.hydrogenaudio.org/?act=ST&f=15&t=9933 + + // Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html + // are assuming a 'Xing' identifier offset of 0x24, which is the case for + // MPEG-1 non-mono, but not for other combinations + $LAMEtagOffsetContant = $VBRidOffset - 0x24; + + // shortcuts + $thisfile_mpeg_audio_lame['RGAD'] = array('track'=>array(), 'album'=>array()); + $thisfile_mpeg_audio_lame_RGAD = &$thisfile_mpeg_audio_lame['RGAD']; + $thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track']; + $thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album']; + $thisfile_mpeg_audio_lame['raw'] = array(); + $thisfile_mpeg_audio_lame_raw = &$thisfile_mpeg_audio_lame['raw']; + + // byte $9B VBR Quality + // This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications. + // Actually overwrites original Xing bytes + unset($thisfile_mpeg_audio['VBR_scale']); + $thisfile_mpeg_audio_lame['vbr_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1)); + + // bytes $9C-$A4 Encoder short VersionString + $thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9); + + // byte $A5 Info Tag revision + VBR method + $LAMEtagRevisionVBRmethod = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1)); + + $thisfile_mpeg_audio_lame['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4; + $thisfile_mpeg_audio_lame_raw['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F; + $thisfile_mpeg_audio_lame['vbr_method'] = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']); + $thisfile_mpeg_audio['bitrate_mode'] = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr' + + // byte $A6 Lowpass filter value + $thisfile_mpeg_audio_lame['lowpass_frequency'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100; + + // bytes $A7-$AE Replay Gain + // http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html + // bytes $A7-$AA : 32 bit floating point "Peak signal amplitude" + if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') { + // LAME 3.94a16 and later - 9.23 fixed point + // ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375 + $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608); + } else { + // LAME 3.94a15 and earlier - 32-bit floating point + // Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15 + $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = getid3_lib::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4)); + } + if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) { + unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']); + } else { + $thisfile_mpeg_audio_lame_RGAD['peak_db'] = getid3_lib::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']); + } + + $thisfile_mpeg_audio_lame_raw['RGAD_track'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2)); + $thisfile_mpeg_audio_lame_raw['RGAD_album'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2)); + + + if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) { + + $thisfile_mpeg_audio_lame_RGAD_track['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13; + $thisfile_mpeg_audio_lame_RGAD_track['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10; + $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9; + $thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF; + $thisfile_mpeg_audio_lame_RGAD_track['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']); + $thisfile_mpeg_audio_lame_RGAD_track['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']); + $thisfile_mpeg_audio_lame_RGAD_track['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']); + + if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { + $info['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; + } + $info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator']; + $info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db']; + } else { + unset($thisfile_mpeg_audio_lame_RGAD['track']); + } + if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) { + + $thisfile_mpeg_audio_lame_RGAD_album['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13; + $thisfile_mpeg_audio_lame_RGAD_album['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10; + $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9; + $thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF; + $thisfile_mpeg_audio_lame_RGAD_album['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']); + $thisfile_mpeg_audio_lame_RGAD_album['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']); + $thisfile_mpeg_audio_lame_RGAD_album['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']); + + if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { + $info['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; + } + $info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator']; + $info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db']; + } else { + unset($thisfile_mpeg_audio_lame_RGAD['album']); + } + if (empty($thisfile_mpeg_audio_lame_RGAD)) { + unset($thisfile_mpeg_audio_lame['RGAD']); + } + + + // byte $AF Encoding flags + ATH Type + $EncodingFlagsATHtype = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1)); + $thisfile_mpeg_audio_lame['encoding_flags']['nspsytune'] = (bool) ($EncodingFlagsATHtype & 0x10); + $thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20); + $thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'] = (bool) ($EncodingFlagsATHtype & 0x40); + $thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] = (bool) ($EncodingFlagsATHtype & 0x80); + $thisfile_mpeg_audio_lame['ath_type'] = $EncodingFlagsATHtype & 0x0F; + + // byte $B0 if ABR {specified bitrate} else {minimal bitrate} + $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1)); + if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR) + $thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']; + } elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR) + // ignore + } elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate + $thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']; + } + + // bytes $B1-$B3 Encoder delays + $EncoderDelays = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3)); + $thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12; + $thisfile_mpeg_audio_lame['end_padding'] = $EncoderDelays & 0x000FFF; + + // byte $B4 Misc + $MiscByte = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1)); + $thisfile_mpeg_audio_lame_raw['noise_shaping'] = ($MiscByte & 0x03); + $thisfile_mpeg_audio_lame_raw['stereo_mode'] = ($MiscByte & 0x1C) >> 2; + $thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5; + $thisfile_mpeg_audio_lame_raw['source_sample_freq'] = ($MiscByte & 0xC0) >> 6; + $thisfile_mpeg_audio_lame['noise_shaping'] = $thisfile_mpeg_audio_lame_raw['noise_shaping']; + $thisfile_mpeg_audio_lame['stereo_mode'] = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']); + $thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality']; + $thisfile_mpeg_audio_lame['source_sample_freq'] = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']); + + // byte $B5 MP3 Gain + $thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true); + $thisfile_mpeg_audio_lame['mp3_gain_db'] = (getid3_lib::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain']; + $thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6)); + + // bytes $B6-$B7 Preset and surround info + $PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2)); + // Reserved = ($PresetSurroundBytes & 0xC000); + $thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800); + $thisfile_mpeg_audio_lame['surround_info'] = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']); + $thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF); + $thisfile_mpeg_audio_lame['preset_used'] = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame); + if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) { + $info['warning'][] = 'Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org'; + } + if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) { + // this may change if 3.90.4 ever comes out + $thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3'; + } + + // bytes $B8-$BB MusicLength + $thisfile_mpeg_audio_lame['audio_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4)); + $ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']); + + // bytes $BC-$BD MusicCRC + $thisfile_mpeg_audio_lame['music_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2)); + + // bytes $BE-$BF CRC-16 of Info Tag + $thisfile_mpeg_audio_lame['lame_tag_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2)); + + + // LAME CBR + if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { + + $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; + $thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']); + $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; + //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) { + // $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min']; + //} + + } + + } + } + + } else { + + // not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header) + $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; + if ($recursivesearch) { + $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; + if ($this->RecursiveFrameScanning($offset, $nextframetestoffset, true)) { + $recursivesearch = false; + $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; + } + if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') { + $info['warning'][] = 'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.'; + } + } + + } + + } + + if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($info['avdataend'] - $info['avdataoffset']))) { + if ($ExpectedNumberOfAudioBytes > ($info['avdataend'] - $info['avdataoffset'])) { + if (isset($info['fileformat']) && ($info['fileformat'] == 'riff')) { + // ignore, audio data is broken into chunks so will always be data "missing" + } elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) { + $info['warning'][] = 'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)'; + } else { + $info['warning'][] = 'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)'; + } + } else { + if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) { + // $prenullbytefileoffset = ftell($this->getid3->fp); + // fseek($this->getid3->fp, $info['avdataend'], SEEK_SET); + // $PossibleNullByte = fread($this->getid3->fp, 1); + // fseek($this->getid3->fp, $prenullbytefileoffset, SEEK_SET); + // if ($PossibleNullByte === "\x00") { + $info['avdataend']--; + // $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; + // } else { + // $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; + // } + } else { + $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; + } + } + } + + if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($info['audio']['bitrate'])) { + if (($offset == $info['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) { + $framebytelength = $this->FreeFormatFrameLength($offset, true); + if ($framebytelength > 0) { + $thisfile_mpeg_audio['framelength'] = $framebytelength; + if ($thisfile_mpeg_audio['layer'] == '1') { + // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 + $info['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; + } else { + // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 + $info['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; + } + } else { + $info['error'][] = 'Error calculating frame length of free-format MP3 without Xing/LAME header'; + } + } + } + + if (isset($thisfile_mpeg_audio['VBR_frames']) ? $thisfile_mpeg_audio['VBR_frames'] : '') { + switch ($thisfile_mpeg_audio['bitrate_mode']) { + case 'vbr': + case 'abr': + $bytes_per_frame = 1152; + if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) { + $bytes_per_frame = 384; + } elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) { + $bytes_per_frame = 576; + } + $thisfile_mpeg_audio['VBR_bitrate'] = (isset($thisfile_mpeg_audio['VBR_bytes']) ? (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($info['audio']['sample_rate'] / $bytes_per_frame) : 0); + if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) { + $info['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; + $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion + } + break; + } + } + + // End variable-bitrate headers + //////////////////////////////////////////////////////////////////////////////////// + + if ($recursivesearch) { + + if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) { + return false; + } + + } + + + //if (false) { + // // experimental side info parsing section - not returning anything useful yet + // + // $SideInfoBitstream = getid3_lib::BigEndian2Bin($SideInfoData); + // $SideInfoOffset = 0; + // + // if ($thisfile_mpeg_audio['version'] == '1') { + // if ($thisfile_mpeg_audio['channelmode'] == 'mono') { + // // MPEG-1 (mono) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // $SideInfoOffset += 5; + // } else { + // // MPEG-1 (stereo, joint-stereo, dual-channel) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // $SideInfoOffset += 3; + // } + // } else { // 2 or 2.5 + // if ($thisfile_mpeg_audio['channelmode'] == 'mono') { + // // MPEG-2, MPEG-2.5 (mono) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8); + // $SideInfoOffset += 8; + // $SideInfoOffset += 1; + // } else { + // // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8); + // $SideInfoOffset += 8; + // $SideInfoOffset += 2; + // } + // } + // + // if ($thisfile_mpeg_audio['version'] == '1') { + // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) { + // for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) { + // $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 2; + // } + // } + // } + // for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) { + // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) { + // $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12); + // $SideInfoOffset += 12; + // $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // $thisfile_mpeg_audio['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8); + // $SideInfoOffset += 8; + // if ($thisfile_mpeg_audio['version'] == '1') { + // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4); + // $SideInfoOffset += 4; + // } else { + // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // } + // $thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // + // if ($thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] == '1') { + // + // $thisfile_mpeg_audio['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2); + // $SideInfoOffset += 2; + // $thisfile_mpeg_audio['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // + // for ($region = 0; $region < 2; $region++) { + // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5); + // $SideInfoOffset += 5; + // } + // $thisfile_mpeg_audio['table_select'][$granule][$channel][2] = 0; + // + // for ($window = 0; $window < 3; $window++) { + // $thisfile_mpeg_audio['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3); + // $SideInfoOffset += 3; + // } + // + // } else { + // + // for ($region = 0; $region < 3; $region++) { + // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5); + // $SideInfoOffset += 5; + // } + // + // $thisfile_mpeg_audio['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4); + // $SideInfoOffset += 4; + // $thisfile_mpeg_audio['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3); + // $SideInfoOffset += 3; + // $thisfile_mpeg_audio['block_type'][$granule][$channel] = 0; + // } + // + // if ($thisfile_mpeg_audio['version'] == '1') { + // $thisfile_mpeg_audio['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // } + // $thisfile_mpeg_audio['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // $thisfile_mpeg_audio['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // } + // } + //} + + return true; + } + + public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) { + $info = &$this->getid3->info; + $firstframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); + $this->decodeMPEGaudioHeader($offset, $firstframetestarray, false); + + for ($i = 0; $i < GETID3_MP3_VALID_CHECK_FRAMES; $i++) { + // check next GETID3_MP3_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch + if (($nextframetestoffset + 4) >= $info['avdataend']) { + // end of file + return true; + } + + $nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); + if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) { + if ($ScanAsCBR) { + // force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header + if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) { + return false; + } + } + + + // next frame is OK, get ready to check the one after that + if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) { + $nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength']; + } else { + $info['error'][] = 'Frame at offset ('.$offset.') is has an invalid frame length.'; + return false; + } + + } elseif (!empty($firstframetestarray['mpeg']['audio']['framelength']) && (($nextframetestoffset + $firstframetestarray['mpeg']['audio']['framelength']) > $info['avdataend'])) { + + // it's not the end of the file, but there's not enough data left for another frame, so assume it's garbage/padding and return OK + return true; + + } else { + + // next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence + $info['warning'][] = 'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.'; + + return false; + } + } + return true; + } + + public function FreeFormatFrameLength($offset, $deepscan=false) { + $info = &$this->getid3->info; + + fseek($this->getid3->fp, $offset, SEEK_SET); + $MPEGaudioData = fread($this->getid3->fp, 32768); + + $SyncPattern1 = substr($MPEGaudioData, 0, 4); + // may be different pattern due to padding + $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) | 0x02).$SyncPattern1{3}; + if ($SyncPattern2 === $SyncPattern1) { + $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) & 0xFD).$SyncPattern1{3}; + } + + $framelength = false; + $framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4); + $framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4); + if ($framelength1 > 4) { + $framelength = $framelength1; + } + if (($framelength2 > 4) && ($framelength2 < $framelength1)) { + $framelength = $framelength2; + } + if (!$framelength) { + + // LAME 3.88 has a different value for modeextension on the first frame vs the rest + $framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4); + $framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4); + + if ($framelength1 > 4) { + $framelength = $framelength1; + } + if (($framelength2 > 4) && ($framelength2 < $framelength1)) { + $framelength = $framelength2; + } + if (!$framelength) { + $info['error'][] = 'Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset; + return false; + } else { + $info['warning'][] = 'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)'; + $info['audio']['codec'] = 'LAME'; + $info['audio']['encoder'] = 'LAME3.88'; + $SyncPattern1 = substr($SyncPattern1, 0, 3); + $SyncPattern2 = substr($SyncPattern2, 0, 3); + } + } + + if ($deepscan) { + + $ActualFrameLengthValues = array(); + $nextoffset = $offset + $framelength; + while ($nextoffset < ($info['avdataend'] - 6)) { + fseek($this->getid3->fp, $nextoffset - 1, SEEK_SET); + $NextSyncPattern = fread($this->getid3->fp, 6); + if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) { + // good - found where expected + $ActualFrameLengthValues[] = $framelength; + } elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) { + // ok - found one byte earlier than expected (last frame wasn't padded, first frame was) + $ActualFrameLengthValues[] = ($framelength - 1); + $nextoffset--; + } elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) { + // ok - found one byte later than expected (last frame was padded, first frame wasn't) + $ActualFrameLengthValues[] = ($framelength + 1); + $nextoffset++; + } else { + $info['error'][] = 'Did not find expected free-format sync pattern at offset '.$nextoffset; + return false; + } + $nextoffset += $framelength; + } + if (count($ActualFrameLengthValues) > 0) { + $framelength = intval(round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues))); + } + } + return $framelength; + } + + public function getOnlyMPEGaudioInfoBruteForce() { + $MPEGaudioHeaderDecodeCache = array(); + $MPEGaudioHeaderValidCache = array(); + $MPEGaudioHeaderLengthCache = array(); + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); + $LongMPEGversionLookup = array(); + $LongMPEGlayerLookup = array(); + $LongMPEGbitrateLookup = array(); + $LongMPEGpaddingLookup = array(); + $LongMPEGfrequencyLookup = array(); + $Distribution['bitrate'] = array(); + $Distribution['frequency'] = array(); + $Distribution['layer'] = array(); + $Distribution['version'] = array(); + $Distribution['padding'] = array(); + + $info = &$this->getid3->info; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + + $max_frames_scan = 5000; + $frames_scanned = 0; + + $previousvalidframe = $info['avdataoffset']; + while (ftell($this->getid3->fp) < $info['avdataend']) { + set_time_limit(30); + $head4 = fread($this->getid3->fp, 4); + if (strlen($head4) < 4) { + break; + } + if ($head4{0} != "\xFF") { + for ($i = 1; $i < 4; $i++) { + if ($head4{$i} == "\xFF") { + fseek($this->getid3->fp, $i - 4, SEEK_CUR); + continue 2; + } + } + continue; + } + if (!isset($MPEGaudioHeaderDecodeCache[$head4])) { + $MPEGaudioHeaderDecodeCache[$head4] = self::MPEGaudioHeaderDecode($head4); + } + if (!isset($MPEGaudioHeaderValidCache[$head4])) { + $MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false); + } + if ($MPEGaudioHeaderValidCache[$head4]) { + + if (!isset($MPEGaudioHeaderLengthCache[$head4])) { + $LongMPEGversionLookup[$head4] = $MPEGaudioVersionLookup[$MPEGaudioHeaderDecodeCache[$head4]['version']]; + $LongMPEGlayerLookup[$head4] = $MPEGaudioLayerLookup[$MPEGaudioHeaderDecodeCache[$head4]['layer']]; + $LongMPEGbitrateLookup[$head4] = $MPEGaudioBitrateLookup[$LongMPEGversionLookup[$head4]][$LongMPEGlayerLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['bitrate']]; + $LongMPEGpaddingLookup[$head4] = (bool) $MPEGaudioHeaderDecodeCache[$head4]['padding']; + $LongMPEGfrequencyLookup[$head4] = $MPEGaudioFrequencyLookup[$LongMPEGversionLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['sample_rate']]; + $MPEGaudioHeaderLengthCache[$head4] = self::MPEGaudioFrameLength( + $LongMPEGbitrateLookup[$head4], + $LongMPEGversionLookup[$head4], + $LongMPEGlayerLookup[$head4], + $LongMPEGpaddingLookup[$head4], + $LongMPEGfrequencyLookup[$head4]); + } + if ($MPEGaudioHeaderLengthCache[$head4] > 4) { + $WhereWeWere = ftell($this->getid3->fp); + fseek($this->getid3->fp, $MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR); + $next4 = fread($this->getid3->fp, 4); + if ($next4{0} == "\xFF") { + if (!isset($MPEGaudioHeaderDecodeCache[$next4])) { + $MPEGaudioHeaderDecodeCache[$next4] = self::MPEGaudioHeaderDecode($next4); + } + if (!isset($MPEGaudioHeaderValidCache[$next4])) { + $MPEGaudioHeaderValidCache[$next4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false); + } + if ($MPEGaudioHeaderValidCache[$next4]) { + fseek($this->getid3->fp, -4, SEEK_CUR); + + getid3_lib::safe_inc($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]); + getid3_lib::safe_inc($Distribution['layer'][$LongMPEGlayerLookup[$head4]]); + getid3_lib::safe_inc($Distribution['version'][$LongMPEGversionLookup[$head4]]); + getid3_lib::safe_inc($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]); + getid3_lib::safe_inc($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]); + if ($max_frames_scan && (++$frames_scanned >= $max_frames_scan)) { + $pct_data_scanned = (ftell($this->getid3->fp) - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']); + $info['warning'][] = 'too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; + foreach ($Distribution as $key1 => $value1) { + foreach ($value1 as $key2 => $value2) { + $Distribution[$key1][$key2] = round($value2 / $pct_data_scanned); + } + } + break; + } + continue; + } + } + unset($next4); + fseek($this->getid3->fp, $WhereWeWere - 3, SEEK_SET); + } + + } + } + foreach ($Distribution as $key => $value) { + ksort($Distribution[$key], SORT_NUMERIC); + } + ksort($Distribution['version'], SORT_STRING); + $info['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate']; + $info['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency']; + $info['mpeg']['audio']['layer_distribution'] = $Distribution['layer']; + $info['mpeg']['audio']['version_distribution'] = $Distribution['version']; + $info['mpeg']['audio']['padding_distribution'] = $Distribution['padding']; + if (count($Distribution['version']) > 1) { + $info['error'][] = 'Corrupt file - more than one MPEG version detected'; + } + if (count($Distribution['layer']) > 1) { + $info['error'][] = 'Corrupt file - more than one MPEG layer detected'; + } + if (count($Distribution['frequency']) > 1) { + $info['error'][] = 'Corrupt file - more than one MPEG sample rate detected'; + } + + + $bittotal = 0; + foreach ($Distribution['bitrate'] as $bitratevalue => $bitratecount) { + if ($bitratevalue != 'free') { + $bittotal += ($bitratevalue * $bitratecount); + } + } + $info['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']); + if ($info['mpeg']['audio']['frame_count'] == 0) { + $info['error'][] = 'no MPEG audio frames found'; + return false; + } + $info['mpeg']['audio']['bitrate'] = ($bittotal / $info['mpeg']['audio']['frame_count']); + $info['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr'); + $info['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true); + + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + $info['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true); + $info['fileformat'] = $info['audio']['dataformat']; + + return true; + } + + + public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) { + // looks for synch, decodes MPEG audio header + + $info = &$this->getid3->info; + + static $MPEGaudioVersionLookup; + static $MPEGaudioLayerLookup; + static $MPEGaudioBitrateLookup; + if (empty($MPEGaudioVersionLookup)) { + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); + + } + + fseek($this->getid3->fp, $avdataoffset, SEEK_SET); + $sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset); + if ($sync_seek_buffer_size <= 0) { + $info['error'][] = 'Invalid $sync_seek_buffer_size at offset '.$avdataoffset; + return false; + } + $header = fread($this->getid3->fp, $sync_seek_buffer_size); + $sync_seek_buffer_size = strlen($header); + $SynchSeekOffset = 0; + while ($SynchSeekOffset < $sync_seek_buffer_size) { + if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !feof($this->getid3->fp)) { + + if ($SynchSeekOffset > $sync_seek_buffer_size) { + // if a synch's not found within the first 128k bytes, then give up + $info['error'][] = 'Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB'; + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); + } + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); + } + if (empty($info['mpeg'])) { + unset($info['mpeg']); + } + return false; + + } elseif (feof($this->getid3->fp)) { + + $info['error'][] = 'Could not find valid MPEG audio synch before end of file'; + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); + } + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); + } + if (isset($info['mpeg']) && (!is_array($info['mpeg']) || (count($info['mpeg']) == 0))) { + unset($info['mpeg']); + } + return false; + } + } + + if (($SynchSeekOffset + 1) >= strlen($header)) { + $info['error'][] = 'Could not find valid MPEG synch before end of file'; + return false; + } + + if (($header{$SynchSeekOffset} == "\xFF") && ($header{($SynchSeekOffset + 1)} > "\xE0")) { // synch detected + if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) { + $FirstFrameThisfileInfo = $info; + $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset; + if (!$this->decodeMPEGaudioHeader($FirstFrameAVDataOffset, $FirstFrameThisfileInfo, false)) { + // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's + // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below + unset($FirstFrameThisfileInfo); + } + } + + $dummy = $info; // only overwrite real data if valid header found + if ($this->decodeMPEGaudioHeader($avdataoffset + $SynchSeekOffset, $dummy, true)) { + $info = $dummy; + $info['avdataoffset'] = $avdataoffset + $SynchSeekOffset; + switch (isset($info['fileformat']) ? $info['fileformat'] : '') { + case '': + case 'id3': + case 'ape': + case 'mp3': + $info['fileformat'] = 'mp3'; + $info['audio']['dataformat'] = 'mp3'; + break; + } + if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) { + if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) { + // If there is garbage data between a valid VBR header frame and a sequence + // of valid MPEG-audio frames the VBR data is no longer discarded. + $info = $FirstFrameThisfileInfo; + $info['avdataoffset'] = $FirstFrameAVDataOffset; + $info['fileformat'] = 'mp3'; + $info['audio']['dataformat'] = 'mp3'; + $dummy = $info; + unset($dummy['mpeg']['audio']); + $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength']; + $GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset; + if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) { + $info = $dummy; + $info['avdataoffset'] = $GarbageOffsetEnd; + $info['warning'][] = 'apparently-valid VBR header not used because could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd; + } else { + $info['warning'][] = 'using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')'; + } + } + } + if (isset($info['mpeg']['audio']['bitrate_mode']) && ($info['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($info['mpeg']['audio']['VBR_method'])) { + // VBR file with no VBR header + $BitrateHistogram = true; + } + + if ($BitrateHistogram) { + + $info['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0); + $info['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0); + + if ($info['mpeg']['audio']['version'] == '1') { + if ($info['mpeg']['audio']['layer'] == 3) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0); + } elseif ($info['mpeg']['audio']['layer'] == 2) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0); + } elseif ($info['mpeg']['audio']['layer'] == 1) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0); + } + } elseif ($info['mpeg']['audio']['layer'] == 1) { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0); + } else { + $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0); + } + + $dummy = array('error'=>$info['error'], 'warning'=>$info['warning'], 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']); + $synchstartoffset = $info['avdataoffset']; + fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); + + // you can play with these numbers: + $max_frames_scan = 50000; + $max_scan_segments = 10; + + // don't play with these numbers: + $FastMode = false; + $SynchErrorsFound = 0; + $frames_scanned = 0; + $this_scan_segment = 0; + $frames_scan_per_segment = ceil($max_frames_scan / $max_scan_segments); + $pct_data_scanned = 0; + for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) { + $frames_scanned_this_segment = 0; + if (ftell($this->getid3->fp) >= $info['avdataend']) { + break; + } + $scan_start_offset[$current_segment] = max(ftell($this->getid3->fp), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments))); + if ($current_segment > 0) { + fseek($this->getid3->fp, $scan_start_offset[$current_segment], SEEK_SET); + $buffer_4k = fread($this->getid3->fp, 4096); + for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) { + if (($buffer_4k{$j} == "\xFF") && ($buffer_4k{($j + 1)} > "\xE0")) { // synch detected + if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) { + $calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength']; + if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) { + $scan_start_offset[$current_segment] += $j; + break; + } + } + } + } + } + $synchstartoffset = $scan_start_offset[$current_segment]; + while ($this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) { + $FastMode = true; + $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']]; + + if (empty($dummy['mpeg']['audio']['framelength'])) { + $SynchErrorsFound++; + $synchstartoffset++; + } else { + getid3_lib::safe_inc($info['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]); + getid3_lib::safe_inc($info['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]); + getid3_lib::safe_inc($info['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]); + $synchstartoffset += $dummy['mpeg']['audio']['framelength']; + } + $frames_scanned++; + if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) { + $this_pct_scanned = (ftell($this->getid3->fp) - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']); + if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) { + // file likely contains < $max_frames_scan, just scan as one segment + $max_scan_segments = 1; + $frames_scan_per_segment = $max_frames_scan; + } else { + $pct_data_scanned += $this_pct_scanned; + break; + } + } + } + } + if ($pct_data_scanned > 0) { + $info['warning'][] = 'too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; + foreach ($info['mpeg']['audio'] as $key1 => $value1) { + if (!preg_match('#_distribution$#i', $key1)) { + continue; + } + foreach ($value1 as $key2 => $value2) { + $info['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned); + } + } + } + + if ($SynchErrorsFound > 0) { + $info['warning'][] = 'Found '.$SynchErrorsFound.' synch errors in histogram analysis'; + //return false; + } + + $bittotal = 0; + $framecounter = 0; + foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) { + $framecounter += $bitratecount; + if ($bitratevalue != 'free') { + $bittotal += ($bitratevalue * $bitratecount); + } + } + if ($framecounter == 0) { + $info['error'][] = 'Corrupt MP3 file: framecounter == zero'; + return false; + } + $info['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter); + $info['mpeg']['audio']['bitrate'] = ($bittotal / $framecounter); + + $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; + + + // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently + $distinct_bitrates = 0; + foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) { + if ($bitrate_count > 0) { + $distinct_bitrates++; + } + } + if ($distinct_bitrates > 1) { + $info['mpeg']['audio']['bitrate_mode'] = 'vbr'; + } else { + $info['mpeg']['audio']['bitrate_mode'] = 'cbr'; + } + $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode']; + + } + + break; // exit while() + } + } + + $SynchSeekOffset++; + if (($avdataoffset + $SynchSeekOffset) >= $info['avdataend']) { + // end of file/data + + if (empty($info['mpeg']['audio'])) { + + $info['error'][] = 'could not find valid MPEG synch before end of file'; + if (isset($info['audio']['bitrate'])) { + unset($info['audio']['bitrate']); + } + if (isset($info['mpeg']['audio'])) { + unset($info['mpeg']['audio']); + } + if (isset($info['mpeg']) && (!is_array($info['mpeg']) || empty($info['mpeg']))) { + unset($info['mpeg']); + } + return false; + + } + break; + } + + } + $info['audio']['channels'] = $info['mpeg']['audio']['channels']; + $info['audio']['channelmode'] = $info['mpeg']['audio']['channelmode']; + $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; + return true; + } + + + public static function MPEGaudioVersionArray() { + static $MPEGaudioVersion = array('2.5', false, '2', '1'); + return $MPEGaudioVersion; + } + + public static function MPEGaudioLayerArray() { + static $MPEGaudioLayer = array(false, 3, 2, 1); + return $MPEGaudioLayer; + } + + public static function MPEGaudioBitrateArray() { + static $MPEGaudioBitrate; + if (empty($MPEGaudioBitrate)) { + $MPEGaudioBitrate = array ( + '1' => array (1 => array('free', 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000), + 2 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000), + 3 => array('free', 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000) + ), + + '2' => array (1 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000), + 2 => array('free', 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000), + ) + ); + $MPEGaudioBitrate['2'][3] = $MPEGaudioBitrate['2'][2]; + $MPEGaudioBitrate['2.5'] = $MPEGaudioBitrate['2']; + } + return $MPEGaudioBitrate; + } + + public static function MPEGaudioFrequencyArray() { + static $MPEGaudioFrequency; + if (empty($MPEGaudioFrequency)) { + $MPEGaudioFrequency = array ( + '1' => array(44100, 48000, 32000), + '2' => array(22050, 24000, 16000), + '2.5' => array(11025, 12000, 8000) + ); + } + return $MPEGaudioFrequency; + } + + public static function MPEGaudioChannelModeArray() { + static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono'); + return $MPEGaudioChannelMode; + } + + public static function MPEGaudioModeExtensionArray() { + static $MPEGaudioModeExtension; + if (empty($MPEGaudioModeExtension)) { + $MPEGaudioModeExtension = array ( + 1 => array('4-31', '8-31', '12-31', '16-31'), + 2 => array('4-31', '8-31', '12-31', '16-31'), + 3 => array('', 'IS', 'MS', 'IS+MS') + ); + } + return $MPEGaudioModeExtension; + } + + public static function MPEGaudioEmphasisArray() { + static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17'); + return $MPEGaudioEmphasis; + } + + public static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) { + return self::MPEGaudioHeaderValid(self::MPEGaudioHeaderDecode($head4), false, $allowBitrate15); + } + + public static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) { + if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) { + return false; + } + + static $MPEGaudioVersionLookup; + static $MPEGaudioLayerLookup; + static $MPEGaudioBitrateLookup; + static $MPEGaudioFrequencyLookup; + static $MPEGaudioChannelModeLookup; + static $MPEGaudioModeExtensionLookup; + static $MPEGaudioEmphasisLookup; + if (empty($MPEGaudioVersionLookup)) { + $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray(); + } + + if (isset($MPEGaudioVersionLookup[$rawarray['version']])) { + $decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']]; + } else { + echo ($echoerrors ? "\n".'invalid Version ('.$rawarray['version'].')' : ''); + return false; + } + if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) { + $decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']]; + } else { + echo ($echoerrors ? "\n".'invalid Layer ('.$rawarray['layer'].')' : ''); + return false; + } + if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) { + echo ($echoerrors ? "\n".'invalid Bitrate ('.$rawarray['bitrate'].')' : ''); + if ($rawarray['bitrate'] == 15) { + // known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0 + // let it go through here otherwise file will not be identified + if (!$allowBitrate15) { + return false; + } + } else { + return false; + } + } + if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) { + echo ($echoerrors ? "\n".'invalid Frequency ('.$rawarray['sample_rate'].')' : ''); + return false; + } + if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) { + echo ($echoerrors ? "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')' : ''); + return false; + } + if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) { + echo ($echoerrors ? "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')' : ''); + return false; + } + if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) { + echo ($echoerrors ? "\n".'invalid Emphasis ('.$rawarray['emphasis'].')' : ''); + return false; + } + // These are just either set or not set, you can't mess that up :) + // $rawarray['protection']; + // $rawarray['padding']; + // $rawarray['private']; + // $rawarray['copyright']; + // $rawarray['original']; + + return true; + } + + public static function MPEGaudioHeaderDecode($Header4Bytes) { + // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM + // A - Frame sync (all bits set) + // B - MPEG Audio version ID + // C - Layer description + // D - Protection bit + // E - Bitrate index + // F - Sampling rate frequency index + // G - Padding bit + // H - Private bit + // I - Channel Mode + // J - Mode extension (Only if Joint stereo) + // K - Copyright + // L - Original + // M - Emphasis + + if (strlen($Header4Bytes) != 4) { + return false; + } + + $MPEGrawHeader['synch'] = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4; + $MPEGrawHeader['version'] = (ord($Header4Bytes{1}) & 0x18) >> 3; // BB + $MPEGrawHeader['layer'] = (ord($Header4Bytes{1}) & 0x06) >> 1; // CC + $MPEGrawHeader['protection'] = (ord($Header4Bytes{1}) & 0x01); // D + $MPEGrawHeader['bitrate'] = (ord($Header4Bytes{2}) & 0xF0) >> 4; // EEEE + $MPEGrawHeader['sample_rate'] = (ord($Header4Bytes{2}) & 0x0C) >> 2; // FF + $MPEGrawHeader['padding'] = (ord($Header4Bytes{2}) & 0x02) >> 1; // G + $MPEGrawHeader['private'] = (ord($Header4Bytes{2}) & 0x01); // H + $MPEGrawHeader['channelmode'] = (ord($Header4Bytes{3}) & 0xC0) >> 6; // II + $MPEGrawHeader['modeextension'] = (ord($Header4Bytes{3}) & 0x30) >> 4; // JJ + $MPEGrawHeader['copyright'] = (ord($Header4Bytes{3}) & 0x08) >> 3; // K + $MPEGrawHeader['original'] = (ord($Header4Bytes{3}) & 0x04) >> 2; // L + $MPEGrawHeader['emphasis'] = (ord($Header4Bytes{3}) & 0x03); // MM + + return $MPEGrawHeader; + } + + public static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { + static $AudioFrameLengthCache = array(); + + if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) { + $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false; + if ($bitrate != 'free') { + + if ($version == '1') { + + if ($layer == '1') { + + // For Layer I slot is 32 bits long + $FrameLengthCoefficient = 48; + $SlotLength = 4; + + } else { // Layer 2 / 3 + + // for Layer 2 and Layer 3 slot is 8 bits long. + $FrameLengthCoefficient = 144; + $SlotLength = 1; + + } + + } else { // MPEG-2 / MPEG-2.5 + + if ($layer == '1') { + + // For Layer I slot is 32 bits long + $FrameLengthCoefficient = 24; + $SlotLength = 4; + + } elseif ($layer == '2') { + + // for Layer 2 and Layer 3 slot is 8 bits long. + $FrameLengthCoefficient = 144; + $SlotLength = 1; + + } else { // layer 3 + + // for Layer 2 and Layer 3 slot is 8 bits long. + $FrameLengthCoefficient = 72; + $SlotLength = 1; + + } + + } + + // FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding + if ($samplerate > 0) { + $NewFramelength = ($FrameLengthCoefficient * $bitrate) / $samplerate; + $NewFramelength = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer 2/3, 4 bytes for Layer I) + if ($padding) { + $NewFramelength += $SlotLength; + } + $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength; + } + } + } + return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate]; + } + + public static function ClosestStandardMP3Bitrate($bit_rate) { + static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000); + static $bit_rate_table = array (0=>'-'); + $round_bit_rate = intval(round($bit_rate, -3)); + if (!isset($bit_rate_table[$round_bit_rate])) { + if ($round_bit_rate > max($standard_bit_rates)) { + $bit_rate_table[$round_bit_rate] = round($bit_rate, 2 - strlen($bit_rate)); + } else { + $bit_rate_table[$round_bit_rate] = max($standard_bit_rates); + foreach ($standard_bit_rates as $standard_bit_rate) { + if ($round_bit_rate >= $standard_bit_rate + (($bit_rate_table[$round_bit_rate] - $standard_bit_rate) / 2)) { + break; + } + $bit_rate_table[$round_bit_rate] = $standard_bit_rate; + } + } + } + return $bit_rate_table[$round_bit_rate]; + } + + public static function XingVBRidOffset($version, $channelmode) { + static $XingVBRidOffsetCache = array(); + if (empty($XingVBRidOffset)) { + $XingVBRidOffset = array ( + '1' => array ('mono' => 0x15, // 4 + 17 = 21 + 'stereo' => 0x24, // 4 + 32 = 36 + 'joint stereo' => 0x24, + 'dual channel' => 0x24 + ), + + '2' => array ('mono' => 0x0D, // 4 + 9 = 13 + 'stereo' => 0x15, // 4 + 17 = 21 + 'joint stereo' => 0x15, + 'dual channel' => 0x15 + ), + + '2.5' => array ('mono' => 0x15, + 'stereo' => 0x15, + 'joint stereo' => 0x15, + 'dual channel' => 0x15 + ) + ); + } + return $XingVBRidOffset[$version][$channelmode]; + } + + public static function LAMEvbrMethodLookup($VBRmethodID) { + static $LAMEvbrMethodLookup = array( + 0x00 => 'unknown', + 0x01 => 'cbr', + 0x02 => 'abr', + 0x03 => 'vbr-old / vbr-rh', + 0x04 => 'vbr-new / vbr-mtrh', + 0x05 => 'vbr-mt', + 0x06 => 'vbr (full vbr method 4)', + 0x08 => 'cbr (constant bitrate 2 pass)', + 0x09 => 'abr (2 pass)', + 0x0F => 'reserved' + ); + return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : ''); + } + + public static function LAMEmiscStereoModeLookup($StereoModeID) { + static $LAMEmiscStereoModeLookup = array( + 0 => 'mono', + 1 => 'stereo', + 2 => 'dual mono', + 3 => 'joint stereo', + 4 => 'forced stereo', + 5 => 'auto', + 6 => 'intensity stereo', + 7 => 'other' + ); + return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : ''); + } + + public static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { + static $LAMEmiscSourceSampleFrequencyLookup = array( + 0 => '<= 32 kHz', + 1 => '44.1 kHz', + 2 => '48 kHz', + 3 => '> 48kHz' + ); + return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : ''); + } + + public static function LAMEsurroundInfoLookup($SurroundInfoID) { + static $LAMEsurroundInfoLookup = array( + 0 => 'no surround info', + 1 => 'DPL encoding', + 2 => 'DPL2 encoding', + 3 => 'Ambisonic encoding' + ); + return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved'); + } + + public static function LAMEpresetUsedLookup($LAMEtag) { + + if ($LAMEtag['preset_used_id'] == 0) { + // no preset used (LAME >=3.93) + // no preset recorded (LAME <3.93) + return ''; + } + $LAMEpresetUsedLookup = array(); + + ///// THIS PART CANNOT BE STATIC . + for ($i = 8; $i <= 320; $i++) { + switch ($LAMEtag['vbr_method']) { + case 'cbr': + $LAMEpresetUsedLookup[$i] = '--alt-preset '.$LAMEtag['vbr_method'].' '.$i; + break; + case 'abr': + default: // other VBR modes shouldn't be here(?) + $LAMEpresetUsedLookup[$i] = '--alt-preset '.$i; + break; + } + } + + // named old-style presets (studio, phone, voice, etc) are handled in GuessEncoderOptions() + + // named alt-presets + $LAMEpresetUsedLookup[1000] = '--r3mix'; + $LAMEpresetUsedLookup[1001] = '--alt-preset standard'; + $LAMEpresetUsedLookup[1002] = '--alt-preset extreme'; + $LAMEpresetUsedLookup[1003] = '--alt-preset insane'; + $LAMEpresetUsedLookup[1004] = '--alt-preset fast standard'; + $LAMEpresetUsedLookup[1005] = '--alt-preset fast extreme'; + $LAMEpresetUsedLookup[1006] = '--alt-preset medium'; + $LAMEpresetUsedLookup[1007] = '--alt-preset fast medium'; + + // LAME 3.94 additions/changes + $LAMEpresetUsedLookup[1010] = '--preset portable'; // 3.94a15 Oct 21 2003 + $LAMEpresetUsedLookup[1015] = '--preset radio'; // 3.94a15 Oct 21 2003 + + $LAMEpresetUsedLookup[320] = '--preset insane'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[410] = '-V9'; + $LAMEpresetUsedLookup[420] = '-V8'; + $LAMEpresetUsedLookup[440] = '-V6'; + $LAMEpresetUsedLookup[430] = '--preset radio'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[450] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'portable'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[460] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'medium'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[470] = '--r3mix'; // 3.94b1 Dec 18 2003 + $LAMEpresetUsedLookup[480] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'standard'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[490] = '-V1'; + $LAMEpresetUsedLookup[500] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'extreme'; // 3.94a15 Nov 12 2003 + + return (isset($LAMEpresetUsedLookup[$LAMEtag['preset_used_id']]) ? $LAMEpresetUsedLookup[$LAMEtag['preset_used_id']] : 'new/unknown preset: '.$LAMEtag['preset_used_id'].' - report to info@getid3.org'); + } + +} diff --git a/extensions/TimedMediaHandler/libs/getid3/module.audio.ogg.php b/extensions/TimedMediaHandler/libs/getid3/module.audio.ogg.php new file mode 100644 index 00000000..a2a35aad --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/module.audio.ogg.php @@ -0,0 +1,671 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// 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, 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'] = 'theora'; + $info['error'][] = 'Ogg Theora 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 + } + +} diff --git a/extensions/TimedMediaHandler/libs/getid3/module.tag.id3v1.php b/extensions/TimedMediaHandler/libs/getid3/module.tag.id3v1.php new file mode 100644 index 00000000..fd9069e0 --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/module.tag.id3v1.php @@ -0,0 +1,359 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// 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; + } + + fseek($this->getid3->fp, -256, SEEK_END); + $preid3v1 = fread($this->getid3->fp, 128); + $id3v1tag = fread($this->getid3->fp, 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; + } + +} diff --git a/extensions/TimedMediaHandler/libs/getid3/module.tag.id3v2.php b/extensions/TimedMediaHandler/libs/getid3/module.tag.id3v2.php new file mode 100644 index 00000000..b08f9f9a --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/module.tag.id3v2.php @@ -0,0 +1,3414 @@ + // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +/// // +// module.tag.id3v2.php // +// module for analyzing ID3v2 tags // +// dependencies: module.tag.id3v1.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); + +class getid3_id3v2 extends getid3_handler +{ + public $StartingOffset = 0; + + public function Analyze() { + $info = &$this->getid3->info; + + // Overall tag structure: + // +-----------------------------+ + // | Header (10 bytes) | + // +-----------------------------+ + // | Extended Header | + // | (variable length, OPTIONAL) | + // +-----------------------------+ + // | Frames (variable length) | + // +-----------------------------+ + // | Padding | + // | (variable length, OPTIONAL) | + // +-----------------------------+ + // | Footer (10 bytes, OPTIONAL) | + // +-----------------------------+ + + // Header + // ID3v2/file identifier "ID3" + // ID3v2 version $04 00 + // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x) + // ID3v2 size 4 * %0xxxxxxx + + + // shortcuts + $info['id3v2']['header'] = true; + $thisfile_id3v2 = &$info['id3v2']; + $thisfile_id3v2['flags'] = array(); + $thisfile_id3v2_flags = &$thisfile_id3v2['flags']; + + + fseek($this->getid3->fp, $this->StartingOffset, SEEK_SET); + $header = fread($this->getid3->fp, 10); + if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) { + + $thisfile_id3v2['majorversion'] = ord($header{3}); + $thisfile_id3v2['minorversion'] = ord($header{4}); + + // shortcut + $id3v2_majorversion = &$thisfile_id3v2['majorversion']; + + } else { + + unset($info['id3v2']); + return false; + + } + + if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists) + + $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']; + return false; + + } + + $id3_flags = ord($header{5}); + switch ($id3v2_majorversion) { + case 2: + // %ab000000 in v2.2 + $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation + $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression + break; + + case 3: + // %abc00000 in v2.3 + $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation + $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header + $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator + break; + + case 4: + // %abcd0000 in v2.4 + $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation + $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header + $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator + $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present + break; + } + + $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length + + $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset; + $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength']; + + + + // create 'encoding' key - used by getid3::HandleAllTags() + // in ID3v2 every field can have it's own encoding type + // so force everything to UTF-8 so it can be handled consistantly + $thisfile_id3v2['encoding'] = 'UTF-8'; + + + // Frames + + // All ID3v2 frames consists of one frame header followed by one or more + // fields containing the actual information. The header is always 10 + // bytes and laid out as follows: + // + // Frame ID $xx xx xx xx (four characters) + // Size 4 * %0xxxxxxx + // Flags $xx xx + + $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header + if (!empty($thisfile_id3v2['exthead']['length'])) { + $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4); + } + if (!empty($thisfile_id3v2_flags['isfooter'])) { + $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio + } + if ($sizeofframes > 0) { + + $framedata = fread($this->getid3->fp, $sizeofframes); // read all frames from file into $framedata variable + + // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x) + if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) { + $framedata = $this->DeUnsynchronise($framedata); + } + // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead + // of on tag level, making it easier to skip frames, increasing the streamability + // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that + // there exists an unsynchronised frame, while the new unsynchronisation flag in + // the frame header [S:4.1.2] indicates unsynchronisation. + + + //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present) + $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header + + + // Extended Header + if (!empty($thisfile_id3v2_flags['exthead'])) { + $extended_header_offset = 0; + + if ($id3v2_majorversion == 3) { + + // v2.3 definition: + //Extended header size $xx xx xx xx // 32-bit integer + //Extended Flags $xx xx + // %x0000000 %00000000 // v2.3 + // x - CRC data present + //Size of padding $xx xx xx xx + + $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0); + $extended_header_offset += 4; + + $thisfile_id3v2['exthead']['flag_bytes'] = 2; + $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes'])); + $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes']; + + $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000); + + $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4)); + $extended_header_offset += 4; + + if ($thisfile_id3v2['exthead']['flags']['crc']) { + $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4)); + $extended_header_offset += 4; + } + $extended_header_offset += $thisfile_id3v2['exthead']['padding_size']; + + } elseif ($id3v2_majorversion == 4) { + + // v2.4 definition: + //Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer + //Number of flag bytes $01 + //Extended Flags $xx + // %0bcd0000 // v2.4 + // b - Tag is an update + // Flag data length $00 + // c - CRC data present + // Flag data length $05 + // Total frame CRC 5 * %0xxxxxxx + // d - Tag restrictions + // Flag data length $01 + + $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true); + $extended_header_offset += 4; + + $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1 + $extended_header_offset += 1; + + $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes'])); + $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes']; + + $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40); + $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20); + $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10); + + if ($thisfile_id3v2['exthead']['flags']['update']) { + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0 + $extended_header_offset += 1; + } + + if ($thisfile_id3v2['exthead']['flags']['crc']) { + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5 + $extended_header_offset += 1; + $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false); + $extended_header_offset += $ext_header_chunk_length; + } + + if ($thisfile_id3v2['exthead']['flags']['restrictions']) { + $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1 + $extended_header_offset += 1; + + // %ppqrrstt + $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); + $extended_header_offset += 1; + $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions + $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions + + $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']); + $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']); + } + + if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) { + $info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')'; + } + } + + $framedataoffset += $extended_header_offset; + $framedata = substr($framedata, $extended_header_offset); + } // end extended header + + + while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse + if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) { + // insufficient room left in ID3v2 header for actual data - must be padding + $thisfile_id3v2['padding']['start'] = $framedataoffset; + $thisfile_id3v2['padding']['length'] = strlen($framedata); + $thisfile_id3v2['padding']['valid'] = true; + for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) { + if ($framedata{$i} != "\x00") { + $thisfile_id3v2['padding']['valid'] = false; + $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; + $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; + break; + } + } + break; // skip rest of ID3v2 header + } + if ($id3v2_majorversion == 2) { + // Frame ID $xx xx xx (three characters) + // Size $xx xx xx (24-bit integer) + // Flags $xx xx + + $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header + $framedata = substr($framedata, 6); // and leave the rest in $framedata + $frame_name = substr($frame_header, 0, 3); + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0); + $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs + + } elseif ($id3v2_majorversion > 2) { + + // Frame ID $xx xx xx xx (four characters) + // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+) + // Flags $xx xx + + $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header + $framedata = substr($framedata, 10); // and leave the rest in $framedata + + $frame_name = substr($frame_header, 0, 4); + if ($id3v2_majorversion == 3) { + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer + } else { // ID3v2.4+ + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value) + } + + if ($frame_size < (strlen($framedata) + 4)) { + $nextFrameID = substr($framedata, $frame_size, 4); + if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) { + // next frame is OK + } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) { + // MP3ext known broken frames - "ok" for the purposes of this test + } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) { + $info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3'; + $id3v2_majorversion = 3; + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer + } + } + + + $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2)); + } + + if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) { + // padding encountered + + $thisfile_id3v2['padding']['start'] = $framedataoffset; + $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata); + $thisfile_id3v2['padding']['valid'] = true; + + $len = strlen($framedata); + for ($i = 0; $i < $len; $i++) { + if ($framedata{$i} != "\x00") { + $thisfile_id3v2['padding']['valid'] = false; + $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; + $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; + break; + } + } + break; // skip rest of ID3v2 header + } + + if ($frame_name == 'COM ') { + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]'; + $frame_name = 'COMM'; + } + if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) { + + unset($parsedFrame); + $parsedFrame['frame_name'] = $frame_name; + $parsedFrame['frame_flags_raw'] = $frame_flags; + $parsedFrame['data'] = substr($framedata, 0, $frame_size); + $parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size); + $parsedFrame['dataoffset'] = $framedataoffset; + + $this->ParseID3v2Frame($parsedFrame); + $thisfile_id3v2[$frame_name][] = $parsedFrame; + + $framedata = substr($framedata, $frame_size); + + } else { // invalid frame length or FrameID + + if ($frame_size <= strlen($framedata)) { + + if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) { + + // next frame is valid, just skip the current frame + $framedata = substr($framedata, $frame_size); + $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.'; + + } else { + + // next frame is invalid too, abort processing + //unset($framedata); + $framedata = null; + $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.'; + + } + + } elseif ($frame_size == strlen($framedata)) { + + // this is the last frame, just skip + $info['warning'][] = 'This was the last ID3v2 frame.'; + + } else { + + // next frame is invalid too, abort processing + //unset($framedata); + $framedata = null; + $info['warning'][] = 'Invalid ID3v2 frame size, aborting.'; + + } + if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) { + + switch ($frame_name) { + case "\x00\x00".'MP': + case "\x00".'MP3': + case ' MP3': + case 'MP3e': + case "\x00".'MP': + case ' MP': + case 'MP3': + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]'; + break; + + default: + $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).'; + break; + } + + } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) { + + $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).'; + + } else { + + $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).'; + + } + + } + $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion)); + + } + + } + + + // Footer + + // The footer is a copy of the header, but with a different identifier. + // ID3v2 identifier "3DI" + // ID3v2 version $04 00 + // ID3v2 flags %abcd0000 + // ID3v2 size 4 * %0xxxxxxx + + if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) { + $footer = fread($this->getid3->fp, 10); + if (substr($footer, 0, 3) == '3DI') { + $thisfile_id3v2['footer'] = true; + $thisfile_id3v2['majorversion_footer'] = ord($footer{3}); + $thisfile_id3v2['minorversion_footer'] = ord($footer{4}); + } + if ($thisfile_id3v2['majorversion_footer'] <= 4) { + $id3_flags = ord(substr($footer{5})); + $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80); + $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40); + $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20); + $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10); + + $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1); + } + } // end footer + + if (isset($thisfile_id3v2['comments']['genre'])) { + foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) { + unset($thisfile_id3v2['comments']['genre'][$key]); + $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value))); + } + } + + if (isset($thisfile_id3v2['comments']['track'])) { + foreach ($thisfile_id3v2['comments']['track'] as $key => $value) { + if (strstr($value, '/')) { + list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]); + } + } + } + + if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) { + $thisfile_id3v2['comments']['year'] = array($matches[1]); + } + + + if (!empty($thisfile_id3v2['TXXX'])) { + // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames + foreach ($thisfile_id3v2['TXXX'] as $txxx_array) { + switch ($txxx_array['description']) { + case 'replaygain_track_gain': + if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) { + $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); + } + break; + case 'replaygain_track_peak': + if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) { + $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']); + } + break; + case 'replaygain_album_gain': + if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) { + $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); + } + break; + } + } + } + + + // Set avdataoffset + $info['avdataoffset'] = $thisfile_id3v2['headerlength']; + if (isset($thisfile_id3v2['footer'])) { + $info['avdataoffset'] += 10; + } + + return true; + } + + + public function ParseID3v2GenreString($genrestring) { + // Parse genres into arrays of genreName and genreID + // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)' + // ID3v2.4.x: '21' $00 'Eurodisco' $00 + $clean_genres = array(); + if (strpos($genrestring, "\x00") === false) { + $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring); + } + $genre_elements = explode("\x00", $genrestring); + foreach ($genre_elements as $element) { + $element = trim($element); + if ($element) { + if (preg_match('#^[0-9]{1,3}#', $element)) { + $clean_genres[] = getid3_id3v1::LookupGenreName($element); + } else { + $clean_genres[] = str_replace('((', '(', $element); + } + } + } + return $clean_genres; + } + + + public function ParseID3v2Frame(&$parsedFrame) { + + // shortcuts + $info = &$this->getid3->info; + $id3v2_majorversion = $info['id3v2']['majorversion']; + + $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']); + if (empty($parsedFrame['framenamelong'])) { + unset($parsedFrame['framenamelong']); + } + $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']); + if (empty($parsedFrame['framenameshort'])) { + unset($parsedFrame['framenameshort']); + } + + if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard + if ($id3v2_majorversion == 3) { + // Frame Header Flags + // %abc00000 %ijk00000 + $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation + $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation + $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only + $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression + $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption + $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity + + } elseif ($id3v2_majorversion == 4) { + // Frame Header Flags + // %0abc0000 %0h00kmnp + $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation + $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation + $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only + $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity + $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression + $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption + $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation + $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator + + // Frame-level de-unsynchronisation - ID3v2.4 + if ($parsedFrame['flags']['Unsynchronisation']) { + $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']); + } + + if ($parsedFrame['flags']['DataLengthIndicator']) { + $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1); + $parsedFrame['data'] = substr($parsedFrame['data'], 4); + } + } + + // Frame-level de-compression + if ($parsedFrame['flags']['compression']) { + $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4)); + if (!function_exists('gzuncompress')) { + $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'; + } else { + if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { + //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) { + $parsedFrame['data'] = $decompresseddata; + unset($decompresseddata); + } else { + $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'; + } + } + } + } + + if (!empty($parsedFrame['flags']['DataLengthIndicator'])) { + if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) { + $info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data'; + } + } + + if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) { + + $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion'; + switch ($parsedFrame['frame_name']) { + case 'WCOM': + $warning .= ' (this is known to happen with files tagged by RioPort)'; + break; + + default: + break; + } + $info['warning'][] = $warning; + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier + // There may be more than one 'UFID' frame in a tag, + // but only one with the same 'Owner identifier'. + //
+ // Owner identifier $00 + // Identifier + $exploded = explode("\x00", $parsedFrame['data'], 2); + $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : ''); + $parsedFrame['data'] = (isset($exploded[1]) ? $exploded[1] : ''); + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame + // There may be more than one 'TXXX' frame in each tag, + // but only one with the same description. + //
+ // Text encoding $xx + // Description $00 (00) + // Value + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['description'] = $frame_description; + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data'])); + } + //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain + + + } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame + // There may only be one text information frame of its kind in an tag. + //
+ // Text encoding $xx + // Information + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + // ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with / + // This of course breaks when an artist name contains slash character, e.g. "AC/DC" + // MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense + // getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user + switch ($parsedFrame['encoding']) { + case 'UTF-16': + case 'UTF-16BE': + case 'UTF-16LE': + $wordsize = 2; + break; + case 'ISO-8859-1': + case 'UTF-8': + default: + $wordsize = 1; + break; + } + $Txxx_elements = array(); + $Txxx_elements_start_offset = 0; + for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) { + if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) { + $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset); + $Txxx_elements_start_offset = $i + $wordsize; + } + } + $Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset); + foreach ($Txxx_elements as $Txxx_element) { + $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element); + if (!empty($string)) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string; + } + } + unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset); + } + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame + // There may be more than one 'WXXX' frame in each tag, + // but only one with the same description + //
+ // Text encoding $xx + // Description $00 (00) + // URL + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + if ($frame_terminatorpos) { + // there are null bytes after the data - this is not according to spec + // only use data up to first null byte + $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos); + } else { + // no null bytes following data, just use all data + $frame_urldata = (string) $parsedFrame['data']; + } + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['url'] = $frame_urldata; + $parsedFrame['description'] = $frame_description; + if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']); + } + unset($parsedFrame['data']); + + + } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames + // There may only be one URL link frame of its kind in a tag, + // except when stated otherwise in the frame description + //
+ // URL + + $parsedFrame['url'] = trim($parsedFrame['data']); + if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url']; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only) + // http://id3.org/id3v2.3.0#sec4.4 + // There may only be one 'IPL' frame in each tag + //
+ // Text encoding $xx + // People list strings + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); + $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset); + + // http://www.getid3.org/phpBB3/viewtopic.php?t=1369 + // "this tag typically contains null terminated strings, which are associated in pairs" + // "there are users that use the tag incorrectly" + $IPLS_parts = array(); + if (strpos($parsedFrame['data_raw'], "\x00") !== false) { + $IPLS_parts_unsorted = array(); + if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) { + // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding + $thisILPS = ''; + for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) { + $twobytes = substr($parsedFrame['data_raw'], $i, 2); + if ($twobytes === "\x00\x00") { + $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); + $thisILPS = ''; + } else { + $thisILPS .= $twobytes; + } + } + if (strlen($thisILPS) > 2) { // 2-byte BOM + $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); + } + } else { + // ISO-8859-1 or UTF-8 or other single-byte-null character set + $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']); + } + if (count($IPLS_parts_unsorted) == 1) { + // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson" + foreach ($IPLS_parts_unsorted as $key => $value) { + $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value); + $position = ''; + foreach ($IPLS_parts_sorted as $person) { + $IPLS_parts[] = array('position'=>$position, 'person'=>$person); + } + } + } elseif ((count($IPLS_parts_unsorted) % 2) == 0) { + $position = ''; + $person = ''; + foreach ($IPLS_parts_unsorted as $key => $value) { + if (($key % 2) == 0) { + $position = $value; + } else { + $person = $value; + $IPLS_parts[] = array('position'=>$position, 'person'=>$person); + $position = ''; + $person = ''; + } + } + } else { + foreach ($IPLS_parts_unsorted as $key => $value) { + $IPLS_parts[] = array($value); + } + } + + } else { + $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']); + } + $parsedFrame['data'] = $IPLS_parts; + + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; + } + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier + // There may only be one 'MCDI' frame in each tag + //
+ // CD TOC + + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; + } + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes + // There may only be one 'ETCO' frame in each tag + //
+ // Time stamp format $xx + // Where time stamp format is: + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + // Followed by a list of key events in the following format: + // Type of event $xx + // Time stamp $xx (xx ...) + // The 'Time stamp' is set to zero if directly at the beginning of the sound + // or after the previous event. All events MUST be sorted in chronological order. + + $frame_offset = 0; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + while ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1); + $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']); + $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table + // There may only be one 'MLLT' frame in each tag + //
+ // MPEG frames between reference $xx xx + // Bytes between reference $xx xx xx + // Milliseconds between reference $xx xx xx + // Bits for bytes deviation $xx + // Bits for milliseconds dev. $xx + // Then for every reference the following data is included; + // Deviation in bytes %xxx.... + // Deviation in milliseconds %xxx.... + + $frame_offset = 0; + $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2)); + $parsedFrame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3)); + $parsedFrame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3)); + $parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1)); + $parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1)); + $parsedFrame['data'] = substr($parsedFrame['data'], 10); + while ($frame_offset < strlen($parsedFrame['data'])) { + $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); + } + $reference_counter = 0; + while (strlen($deviationbitstream) > 0) { + $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation'])); + $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation'])); + $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']); + $reference_counter++; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes + // There may only be one 'SYTC' frame in each tag + //
+ // Time stamp format $xx + // Tempo data + // Where time stamp format is: + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + + $frame_offset = 0; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $timestamp_counter = 0; + while ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ($parsedFrame[$timestamp_counter]['tempo'] == 255) { + $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1)); + } + $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $timestamp_counter++; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription + // There may be more than one 'Unsynchronised lyrics/text transcription' frame + // in each tag, but only one with the same language and content descriptor. + //
+ // Text encoding $xx + // Language $xx xx xx + // Content descriptor $00 (00) + // Lyrics/text + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['data'] = $parsedFrame['data']; + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + $parsedFrame['description'] = $frame_description; + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text + // There may be more than one 'SYLT' frame in each tag, + // but only one with the same language and content descriptor. + //
+ // Text encoding $xx + // Language $xx xx xx + // Time stamp format $xx + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + // Content type $xx + // Content descriptor $00 (00) + // Terminated text to be synced (typically a syllable) + // Sync identifier (terminator to above string) $00 (00) + // Time stamp $xx (xx ...) + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + + $timestampindex = 0; + $frame_remainingdata = substr($parsedFrame['data'], $frame_offset); + while (strlen($frame_remainingdata)) { + $frame_offset = 0; + $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding)); + if ($frame_terminatorpos === false) { + $frame_remainingdata = ''; + } else { + if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); + + $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) { + // timestamp probably omitted for first data item + } else { + $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4)); + $frame_remainingdata = substr($frame_remainingdata, 4); + } + $timestampindex++; + } + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10 COMM Comments + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments + // There may be more than one comment frame in each tag, + // but only one with the same language and content descriptor. + //
+ // Text encoding $xx + // Language $xx xx xx + // Short content descrip. $00 (00) + // The actual text + + if (strlen($parsedFrame['data']) < 5) { + + $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']; + + } else { + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + $parsedFrame['description'] = $frame_description; + $parsedFrame['data'] = $frame_text; + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + } + + } + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only) + // There may be more than one 'RVA2' frame in each tag, + // but only one with the same identification string + //
+ // Identification $00 + // The 'identification' string is used to identify the situation and/or + // device where this adjustment should apply. The following is then + // repeated for every channel: + // Type of channel $xx + // Volume adjustment $xx xx + // Bits representing peak $xx + // Peak volume $xx (xx ...) + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00"); + $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos); + if (ord($frame_idstring) === 0) { + $frame_idstring = ''; + } + $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); + $parsedFrame['description'] = $frame_idstring; + $RVA2channelcounter = 0; + while (strlen($frame_remainingdata) >= 5) { + $frame_offset = 0; + $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1)); + $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid; + $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid); + $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed + $frame_offset += 2; + $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1)); + if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) { + $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value'; + break; + } + $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8); + $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume)); + $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume); + $RVA2channelcounter++; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only) + // There may only be one 'RVA' frame in each tag + //
+ // ID3v2.2 => Increment/decrement %000000ba + // ID3v2.3 => Increment/decrement %00fedcba + // Bits used for volume descr. $xx + // Relative volume change, right $xx xx (xx ...) // a + // Relative volume change, left $xx xx (xx ...) // b + // Peak volume right $xx xx (xx ...) + // Peak volume left $xx xx (xx ...) + // ID3v2.3 only, optional (not present in ID3v2.2): + // Relative volume change, right back $xx xx (xx ...) // c + // Relative volume change, left back $xx xx (xx ...) // d + // Peak volume right back $xx xx (xx ...) + // Peak volume left back $xx xx (xx ...) + // ID3v2.3 only, optional (not present in ID3v2.2): + // Relative volume change, center $xx xx (xx ...) // e + // Peak volume center $xx xx (xx ...) + // ID3v2.3 only, optional (not present in ID3v2.2): + // Relative volume change, bass $xx xx (xx ...) // f + // Peak volume bass $xx xx (xx ...) + + $frame_offset = 0; + $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1); + $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1); + $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8); + $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['right'] === false) { + $parsedFrame['volumechange']['right'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['left'] === false) { + $parsedFrame['volumechange']['left'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + if ($id3v2_majorversion == 3) { + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); + if (strlen($parsedFrame['data']) > 0) { + $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1); + $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1); + $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['rightrear'] === false) { + $parsedFrame['volumechange']['rightrear'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['leftrear'] === false) { + $parsedFrame['volumechange']['leftrear'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); + if (strlen($parsedFrame['data']) > 0) { + $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1); + $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['center'] === false) { + $parsedFrame['volumechange']['center'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); + if (strlen($parsedFrame['data']) > 0) { + $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1); + $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['bass'] === false) { + $parsedFrame['volumechange']['bass'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + } + } + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only) + // There may be more than one 'EQU2' frame in each tag, + // but only one with the same identification string + //
+ // Interpolation method $xx + // $00 Band + // $01 Linear + // Identification $00 + // The following is then repeated for every adjustment point + // Frequency $xx xx + // Volume adjustment $xx xx + + $frame_offset = 0; + $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_idstring) === 0) { + $frame_idstring = ''; + } + $parsedFrame['description'] = $frame_idstring; + $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); + while (strlen($frame_remainingdata)) { + $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2; + $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true); + $frame_remainingdata = substr($frame_remainingdata, 4); + } + $parsedFrame['interpolationmethod'] = $frame_interpolationmethod; + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12 EQUA Equalisation (ID3v2.3 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only) + // There may only be one 'EQUA' frame in each tag + //
+ // Adjustment bits $xx + // This is followed by 2 bytes + ('adjustment bits' rounded up to the + // nearest byte) for every equalisation band in the following format, + // giving a frequency range of 0 - 32767Hz: + // Increment/decrement %x (MSB of the Frequency) + // Frequency (lower 15 bits) + // Adjustment $xx (xx ...) + + $frame_offset = 0; + $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1); + $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8); + + $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset); + while (strlen($frame_remainingdata) > 0) { + $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2)); + $frame_incdec = (bool) substr($frame_frequencystr, 0, 1); + $frame_frequency = bindec(substr($frame_frequencystr, 1, 15)); + $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec; + $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes)); + if ($parsedFrame[$frame_frequency]['incdec'] === false) { + $parsedFrame[$frame_frequency]['adjustment'] *= -1; + } + $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes); + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13 RVRB Reverb + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb + // There may only be one 'RVRB' frame in each tag. + //
+ // Reverb left (ms) $xx xx + // Reverb right (ms) $xx xx + // Reverb bounces, left $xx + // Reverb bounces, right $xx + // Reverb feedback, left to left $xx + // Reverb feedback, left to right $xx + // Reverb feedback, right to right $xx + // Reverb feedback, right to left $xx + // Premix left to right $xx + // Premix right to left $xx + + $frame_offset = 0; + $parsedFrame['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14 APIC Attached picture + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture + // There may be several pictures attached to one file, + // each in their individual 'APIC' frame, but only one + // with the same content descriptor + //
+ // Text encoding $xx + // ID3v2.3+ => MIME type $00 + // ID3v2.2 => Image format $xx xx xx + // Picture type $xx + // Description $00 (00) + // Picture data + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + + if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) { + $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3); + if (strtolower($frame_imagetype) == 'ima') { + // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted + // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net) + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_mimetype) === 0) { + $frame_mimetype = ''; + } + $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype))); + if ($frame_imagetype == 'JPEG') { + $frame_imagetype = 'JPG'; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + } else { + $frame_offset += 3; + } + } + if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) { + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_mimetype) === 0) { + $frame_mimetype = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + } + + $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + if ($frame_offset >= $parsedFrame['datalength']) { + $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset); + } else { + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + if ($id3v2_majorversion == 2) { + $parsedFrame['imagetype'] = $frame_imagetype; + } else { + $parsedFrame['mime'] = $frame_mimetype; + } + $parsedFrame['picturetypeid'] = $frame_picturetype; + $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); + $parsedFrame['description'] = $frame_description; + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + $parsedFrame['datalength'] = strlen($parsedFrame['data']); + + $parsedFrame['image_mime'] = ''; + $imageinfo = array(); + $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo); + if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { + $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); + if ($imagechunkcheck[0]) { + $parsedFrame['image_width'] = $imagechunkcheck[0]; + } + if ($imagechunkcheck[1]) { + $parsedFrame['image_height'] = $imagechunkcheck[1]; + } + } + + do { + if ($this->getid3->option_save_attachments === false) { + // skip entirely + unset($parsedFrame['data']); + break; + } + if ($this->getid3->option_save_attachments === true) { + // great +/* + } elseif (is_int($this->getid3->option_save_attachments)) { + if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) { + // too big, skip + $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)'; + unset($parsedFrame['data']); + break; + } +*/ + } elseif (is_string($this->getid3->option_save_attachments)) { + $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR); + if (!is_dir($dir) || !is_writable($dir)) { + // cannot write, skip + $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)'; + unset($parsedFrame['data']); + break; + } + } + // if we get this far, must be OK + if (is_string($this->getid3->option_save_attachments)) { + $destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset; + if (!file_exists($destination_filename) || is_writable($destination_filename)) { + file_put_contents($destination_filename, $parsedFrame['data']); + } else { + $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)'; + } + $parsedFrame['data_filename'] = $destination_filename; + unset($parsedFrame['data']); + } else { + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + if (!isset($info['id3v2']['comments']['picture'])) { + $info['id3v2']['comments']['picture'] = array(); + } + $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']); + } + } + } while (false); + } + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object + // There may be more than one 'GEOB' frame in each tag, + // but only one with the same content descriptor + //
+ // Text encoding $xx + // MIME type $00 + // Filename $00 (00) + // Content description $00 (00) + // Encapsulated object + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_mimetype) === 0) { + $frame_mimetype = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_filename) === 0) { + $frame_filename = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['mime'] = $frame_mimetype; + $parsedFrame['filename'] = $frame_filename; + $parsedFrame['description'] = $frame_description; + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16 PCNT Play counter + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter + // There may only be one 'PCNT' frame in each tag. + // When the counter reaches all one's, one byte is inserted in + // front of the counter thus making the counter eight bits bigger + //
+ // Counter $xx xx xx xx (xx ...) + + $parsedFrame['data'] = getid3_lib::BigEndian2Int($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter + // There may be more than one 'POPM' frame in each tag, + // but only one with the same email address + //
+ // Email to user $00 + // Rating $xx + // Counter $xx xx xx xx (xx ...) + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_emailaddress) === 0) { + $frame_emailaddress = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); + $parsedFrame['email'] = $frame_emailaddress; + $parsedFrame['rating'] = $frame_rating; + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18 RBUF Recommended buffer size + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size + // There may only be one 'RBUF' frame in each tag + //
+ // Buffer size $xx xx xx + // Embedded info flag %0000000x + // Offset to next tag $xx xx xx xx + + $frame_offset = 0; + $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3)); + $frame_offset += 3; + + $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1); + $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only) + // There may be more than one 'CRM' frame in a tag, + // but only one with the same 'owner identifier' + //
+ // Owner identifier $00 (00) + // Content/explanation $00 (00) + // Encrypted datablock + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['description'] = $frame_description; + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19 AENC Audio encryption + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption + // There may be more than one 'AENC' frames in a tag, + // but only one with the same 'Owner identifier' + //
+ // Owner identifier $00 + // Preview start $xx xx + // Preview length $xx xx + // Encryption info + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid == ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset); + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information + // There may be more than one 'LINK' frame in a tag, + // but only one with the same contents + //
+ // ID3v2.3+ => Frame identifier $xx xx xx xx + // ID3v2.2 => Frame identifier $xx xx xx + // URL $00 + // ID and additional data + + $frame_offset = 0; + if ($id3v2_majorversion == 2) { + $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + } else { + $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4); + $frame_offset += 4; + } + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_url) === 0) { + $frame_url = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $parsedFrame['url'] = $frame_url; + + $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset); + if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']); + } + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only) + // There may only be one 'POSS' frame in each tag + // + // Time stamp format $xx + // Position $xx (xx ...) + + $frame_offset = 0; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['position'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only) + // There may be more than one 'Terms of use' frame in a tag, + // but only one with the same 'Language' + //
+ // Text encoding $xx + // Language $xx xx xx + // The actual text + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']); + } + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only) + // There may only be one 'OWNE' frame in a tag + //
+ // Text encoding $xx + // Price paid $00 + // Date of purch. + // Seller + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3); + $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']); + $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3); + + $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8); + if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) { + $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4)); + } + $frame_offset += 8; + + $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset); + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only) + // There may be more than one 'commercial frame' in a tag, + // but no two may be identical + //
+ // Text encoding $xx + // Price string $00 + // Valid until + // Contact URL $00 + // Received as $xx + // Name of seller $00 (00) + // Description $00 (00) + // Picture MIME type $00 + // Seller logo + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $frame_rawpricearray = explode('/', $frame_pricestring); + foreach ($frame_rawpricearray as $key => $val) { + $frame_currencyid = substr($val, 0, 3); + $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid); + $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3); + } + + $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8); + $frame_offset += 8; + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_sellername) === 0) { + $frame_sellername = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['pricevaliduntil'] = $frame_datestring; + $parsedFrame['contacturl'] = $frame_contacturl; + $parsedFrame['receivedasid'] = $frame_receivedasid; + $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid); + $parsedFrame['sellername'] = $frame_sellername; + $parsedFrame['description'] = $frame_description; + $parsedFrame['mime'] = $frame_mimetype; + $parsedFrame['logo'] = $frame_sellerlogo; + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only) + // There may be several 'ENCR' frames in a tag, + // but only one containing the same symbol + // and only one containing the same owner identifier + //
+ // Owner identifier $00 + // Method symbol $xx + // Encryption data + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only) + + // There may be several 'GRID' frames in a tag, + // but only one containing the same symbol + // and only one containing the same owner identifier + //
+ // Owner identifier $00 + // Group symbol $xx + // Group dependent data + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only) + // The tag may contain more than one 'PRIV' frame + // but only with different contents + //
+ // Owner identifier $00 + // The private data + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only) + // There may be more than one 'signature frame' in a tag, + // but no two may be identical + //
+ // Group symbol $xx + // Signature + + $frame_offset = 0; + $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only) + // There may only be one 'seek frame' in a tag + //
+ // Minimum offset to next tag $xx xx xx xx + + $frame_offset = 0; + $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only) + // There may only be one 'audio seek point index' frame in a tag + //
+ // Indexed data start (S) $xx xx xx xx + // Indexed data length (L) $xx xx xx xx + // Number of index points (N) $xx xx + // Bits per index point (b) $xx + // Then for every index point the following data is included: + // Fraction at index (Fi) $xx (xx) + + $frame_offset = 0; + $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8); + for ($i = 0; $i < $frame_indexpoints; $i++) { + $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint)); + $frame_offset += $frame_bytesperpoint; + } + unset($parsedFrame['data']); + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment + // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html + // There may only be one 'RGAD' frame in a tag + //
+ // Peak Amplitude $xx $xx $xx $xx + // Radio Replay Gain Adjustment %aaabbbcd %dddddddd + // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd + // a - name code + // b - originator code + // c - sign bit + // d - replay gain adjustment + + $frame_offset = 0; + $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['raw']['track']['name'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3)); + $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3)); + $parsedFrame['raw']['track']['signbit'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1)); + $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9)); + $parsedFrame['raw']['album']['name'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3)); + $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3)); + $parsedFrame['raw']['album']['signbit'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1)); + $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9)); + $parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']); + $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']); + $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']); + $parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']); + $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']); + $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']); + + $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude']; + $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator']; + $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; + $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; + $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; + + unset($parsedFrame['data']); + + } + + return true; + } + + + public function DeUnsynchronise($data) { + return str_replace("\xFF\x00", "\xFF", $data); + } + + public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) { + static $LookupExtendedHeaderRestrictionsTagSizeLimits = array( + 0x00 => 'No more than 128 frames and 1 MB total tag size', + 0x01 => 'No more than 64 frames and 128 KB total tag size', + 0x02 => 'No more than 32 frames and 40 KB total tag size', + 0x03 => 'No more than 32 frames and 4 KB total tag size', + ); + return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : ''); + } + + public function LookupExtendedHeaderRestrictionsTextEncodings($index) { + static $LookupExtendedHeaderRestrictionsTextEncodings = array( + 0x00 => 'No restrictions', + 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8', + ); + return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : ''); + } + + public function LookupExtendedHeaderRestrictionsTextFieldSize($index) { + static $LookupExtendedHeaderRestrictionsTextFieldSize = array( + 0x00 => 'No restrictions', + 0x01 => 'No string is longer than 1024 characters', + 0x02 => 'No string is longer than 128 characters', + 0x03 => 'No string is longer than 30 characters', + ); + return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : ''); + } + + public function LookupExtendedHeaderRestrictionsImageEncoding($index) { + static $LookupExtendedHeaderRestrictionsImageEncoding = array( + 0x00 => 'No restrictions', + 0x01 => 'Images are encoded only with PNG or JPEG', + ); + return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : ''); + } + + public function LookupExtendedHeaderRestrictionsImageSizeSize($index) { + static $LookupExtendedHeaderRestrictionsImageSizeSize = array( + 0x00 => 'No restrictions', + 0x01 => 'All images are 256x256 pixels or smaller', + 0x02 => 'All images are 64x64 pixels or smaller', + 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise', + ); + return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : ''); + } + + public function LookupCurrencyUnits($currencyid) { + + $begin = __LINE__; + + /** This is not a comment! + + + AED Dirhams + AFA Afghanis + ALL Leke + AMD Drams + ANG Guilders + AOA Kwanza + ARS Pesos + ATS Schillings + AUD Dollars + AWG Guilders + AZM Manats + BAM Convertible Marka + BBD Dollars + BDT Taka + BEF Francs + BGL Leva + BHD Dinars + BIF Francs + BMD Dollars + BND Dollars + BOB Bolivianos + BRL Brazil Real + BSD Dollars + BTN Ngultrum + BWP Pulas + BYR Rubles + BZD Dollars + CAD Dollars + CDF Congolese Francs + CHF Francs + CLP Pesos + CNY Yuan Renminbi + COP Pesos + CRC Colones + CUP Pesos + CVE Escudos + CYP Pounds + CZK Koruny + DEM Deutsche Marks + DJF Francs + DKK Kroner + DOP Pesos + DZD Algeria Dinars + EEK Krooni + EGP Pounds + ERN Nakfa + ESP Pesetas + ETB Birr + EUR Euro + FIM Markkaa + FJD Dollars + FKP Pounds + FRF Francs + GBP Pounds + GEL Lari + GGP Pounds + GHC Cedis + GIP Pounds + GMD Dalasi + GNF Francs + GRD Drachmae + GTQ Quetzales + GYD Dollars + HKD Dollars + HNL Lempiras + HRK Kuna + HTG Gourdes + HUF Forints + IDR Rupiahs + IEP Pounds + ILS New Shekels + IMP Pounds + INR Rupees + IQD Dinars + IRR Rials + ISK Kronur + ITL Lire + JEP Pounds + JMD Dollars + JOD Dinars + JPY Yen + KES Shillings + KGS Soms + KHR Riels + KMF Francs + KPW Won + KWD Dinars + KYD Dollars + KZT Tenge + LAK Kips + LBP Pounds + LKR Rupees + LRD Dollars + LSL Maloti + LTL Litai + LUF Francs + LVL Lati + LYD Dinars + MAD Dirhams + MDL Lei + MGF Malagasy Francs + MKD Denars + MMK Kyats + MNT Tugriks + MOP Patacas + MRO Ouguiyas + MTL Liri + MUR Rupees + MVR Rufiyaa + MWK Kwachas + MXN Pesos + MYR Ringgits + MZM Meticais + NAD Dollars + NGN Nairas + NIO Gold Cordobas + NLG Guilders + NOK Krone + NPR Nepal Rupees + NZD Dollars + OMR Rials + PAB Balboa + PEN Nuevos Soles + PGK Kina + PHP Pesos + PKR Rupees + PLN Zlotych + PTE Escudos + PYG Guarani + QAR Rials + ROL Lei + RUR Rubles + RWF Rwanda Francs + SAR Riyals + SBD Dollars + SCR Rupees + SDD Dinars + SEK Kronor + SGD Dollars + SHP Pounds + SIT Tolars + SKK Koruny + SLL Leones + SOS Shillings + SPL Luigini + SRG Guilders + STD Dobras + SVC Colones + SYP Pounds + SZL Emalangeni + THB Baht + TJR Rubles + TMM Manats + TND Dinars + TOP Pa'anga + TRL Liras + TTD Dollars + TVD Tuvalu Dollars + TWD New Dollars + TZS Shillings + UAH Hryvnia + UGX Shillings + USD Dollars + UYU Pesos + UZS Sums + VAL Lire + VEB Bolivares + VND Dong + VUV Vatu + WST Tala + XAF Francs + XAG Ounces + XAU Ounces + XCD Dollars + XDR Special Drawing Rights + XPD Ounces + XPF Francs + XPT Ounces + YER Rials + YUM New Dinars + ZAR Rand + ZMK Kwacha + ZWD Zimbabwe Dollars + + */ + + return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units'); + } + + + public function LookupCurrencyCountry($currencyid) { + + $begin = __LINE__; + + /** This is not a comment! + + AED United Arab Emirates + AFA Afghanistan + ALL Albania + AMD Armenia + ANG Netherlands Antilles + AOA Angola + ARS Argentina + ATS Austria + AUD Australia + AWG Aruba + AZM Azerbaijan + BAM Bosnia and Herzegovina + BBD Barbados + BDT Bangladesh + BEF Belgium + BGL Bulgaria + BHD Bahrain + BIF Burundi + BMD Bermuda + BND Brunei Darussalam + BOB Bolivia + BRL Brazil + BSD Bahamas + BTN Bhutan + BWP Botswana + BYR Belarus + BZD Belize + CAD Canada + CDF Congo/Kinshasa + CHF Switzerland + CLP Chile + CNY China + COP Colombia + CRC Costa Rica + CUP Cuba + CVE Cape Verde + CYP Cyprus + CZK Czech Republic + DEM Germany + DJF Djibouti + DKK Denmark + DOP Dominican Republic + DZD Algeria + EEK Estonia + EGP Egypt + ERN Eritrea + ESP Spain + ETB Ethiopia + EUR Euro Member Countries + FIM Finland + FJD Fiji + FKP Falkland Islands (Malvinas) + FRF France + GBP United Kingdom + GEL Georgia + GGP Guernsey + GHC Ghana + GIP Gibraltar + GMD Gambia + GNF Guinea + GRD Greece + GTQ Guatemala + GYD Guyana + HKD Hong Kong + HNL Honduras + HRK Croatia + HTG Haiti + HUF Hungary + IDR Indonesia + IEP Ireland (Eire) + ILS Israel + IMP Isle of Man + INR India + IQD Iraq + IRR Iran + ISK Iceland + ITL Italy + JEP Jersey + JMD Jamaica + JOD Jordan + JPY Japan + KES Kenya + KGS Kyrgyzstan + KHR Cambodia + KMF Comoros + KPW Korea + KWD Kuwait + KYD Cayman Islands + KZT Kazakstan + LAK Laos + LBP Lebanon + LKR Sri Lanka + LRD Liberia + LSL Lesotho + LTL Lithuania + LUF Luxembourg + LVL Latvia + LYD Libya + MAD Morocco + MDL Moldova + MGF Madagascar + MKD Macedonia + MMK Myanmar (Burma) + MNT Mongolia + MOP Macau + MRO Mauritania + MTL Malta + MUR Mauritius + MVR Maldives (Maldive Islands) + MWK Malawi + MXN Mexico + MYR Malaysia + MZM Mozambique + NAD Namibia + NGN Nigeria + NIO Nicaragua + NLG Netherlands (Holland) + NOK Norway + NPR Nepal + NZD New Zealand + OMR Oman + PAB Panama + PEN Peru + PGK Papua New Guinea + PHP Philippines + PKR Pakistan + PLN Poland + PTE Portugal + PYG Paraguay + QAR Qatar + ROL Romania + RUR Russia + RWF Rwanda + SAR Saudi Arabia + SBD Solomon Islands + SCR Seychelles + SDD Sudan + SEK Sweden + SGD Singapore + SHP Saint Helena + SIT Slovenia + SKK Slovakia + SLL Sierra Leone + SOS Somalia + SPL Seborga + SRG Suriname + STD São Tome and Principe + SVC El Salvador + SYP Syria + SZL Swaziland + THB Thailand + TJR Tajikistan + TMM Turkmenistan + TND Tunisia + TOP Tonga + TRL Turkey + TTD Trinidad and Tobago + TVD Tuvalu + TWD Taiwan + TZS Tanzania + UAH Ukraine + UGX Uganda + USD United States of America + UYU Uruguay + UZS Uzbekistan + VAL Vatican City + VEB Venezuela + VND Viet Nam + VUV Vanuatu + WST Samoa + XAF Communauté Financière Africaine + XAG Silver + XAU Gold + XCD East Caribbean + XDR International Monetary Fund + XPD Palladium + XPF Comptoirs Français du Pacifique + XPT Platinum + YER Yemen + YUM Yugoslavia + ZAR South Africa + ZMK Zambia + ZWD Zimbabwe + + */ + + return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country'); + } + + + + public static function LanguageLookup($languagecode, $casesensitive=false) { + + if (!$casesensitive) { + $languagecode = strtolower($languagecode); + } + + // http://www.id3.org/id3v2.4.0-structure.txt + // [4. ID3v2 frame overview] + // The three byte language field, present in several frames, is used to + // describe the language of the frame's content, according to ISO-639-2 + // [ISO-639-2]. The language should be represented in lower case. If the + // language is not known the string "XXX" should be used. + + + // ISO 639-2 - http://www.id3.org/iso639-2.html + + $begin = __LINE__; + + /** This is not a comment! + + XXX unknown + xxx unknown + aar Afar + abk Abkhazian + ace Achinese + ach Acoli + ada Adangme + afa Afro-Asiatic (Other) + afh Afrihili + afr Afrikaans + aka Akan + akk Akkadian + alb Albanian + ale Aleut + alg Algonquian Languages + amh Amharic + ang English, Old (ca. 450-1100) + apa Apache Languages + ara Arabic + arc Aramaic + arm Armenian + arn Araucanian + arp Arapaho + art Artificial (Other) + arw Arawak + asm Assamese + ath Athapascan Languages + ava Avaric + ave Avestan + awa Awadhi + aym Aymara + aze Azerbaijani + bad Banda + bai Bamileke Languages + bak Bashkir + bal Baluchi + bam Bambara + ban Balinese + baq Basque + bas Basa + bat Baltic (Other) + bej Beja + bel Byelorussian + bem Bemba + ben Bengali + ber Berber (Other) + bho Bhojpuri + bih Bihari + bik Bikol + bin Bini + bis Bislama + bla Siksika + bnt Bantu (Other) + bod Tibetan + bra Braj + bre Breton + bua Buriat + bug Buginese + bul Bulgarian + bur Burmese + cad Caddo + cai Central American Indian (Other) + car Carib + cat Catalan + cau Caucasian (Other) + ceb Cebuano + cel Celtic (Other) + ces Czech + cha Chamorro + chb Chibcha + che Chechen + chg Chagatai + chi Chinese + chm Mari + chn Chinook jargon + cho Choctaw + chr Cherokee + chu Church Slavic + chv Chuvash + chy Cheyenne + cop Coptic + cor Cornish + cos Corsican + cpe Creoles and Pidgins, English-based (Other) + cpf Creoles and Pidgins, French-based (Other) + cpp Creoles and Pidgins, Portuguese-based (Other) + cre Cree + crp Creoles and Pidgins (Other) + cus Cushitic (Other) + cym Welsh + cze Czech + dak Dakota + dan Danish + del Delaware + deu German + din Dinka + div Divehi + doi Dogri + dra Dravidian (Other) + dua Duala + dum Dutch, Middle (ca. 1050-1350) + dut Dutch + dyu Dyula + dzo Dzongkha + efi Efik + egy Egyptian (Ancient) + eka Ekajuk + ell Greek, Modern (1453-) + elx Elamite + eng English + enm English, Middle (ca. 1100-1500) + epo Esperanto + esk Eskimo (Other) + esl Spanish + est Estonian + eus Basque + ewe Ewe + ewo Ewondo + fan Fang + fao Faroese + fas Persian + fat Fanti + fij Fijian + fin Finnish + fiu Finno-Ugrian (Other) + fon Fon + fra French + fre French + frm French, Middle (ca. 1400-1600) + fro French, Old (842- ca. 1400) + fry Frisian + ful Fulah + gaa Ga + gae Gaelic (Scots) + gai Irish + gay Gayo + gdh Gaelic (Scots) + gem Germanic (Other) + geo Georgian + ger German + gez Geez + gil Gilbertese + glg Gallegan + gmh German, Middle High (ca. 1050-1500) + goh German, Old High (ca. 750-1050) + gon Gondi + got Gothic + grb Grebo + grc Greek, Ancient (to 1453) + gre Greek, Modern (1453-) + grn Guarani + guj Gujarati + hai Haida + hau Hausa + haw Hawaiian + heb Hebrew + her Herero + hil Hiligaynon + him Himachali + hin Hindi + hmo Hiri Motu + hun Hungarian + hup Hupa + hye Armenian + iba Iban + ibo Igbo + ice Icelandic + ijo Ijo + iku Inuktitut + ilo Iloko + ina Interlingua (International Auxiliary language Association) + inc Indic (Other) + ind Indonesian + ine Indo-European (Other) + ine Interlingue + ipk Inupiak + ira Iranian (Other) + iri Irish + iro Iroquoian uages + isl Icelandic + ita Italian + jav Javanese + jaw Javanese + jpn Japanese + jpr Judeo-Persian + jrb Judeo-Arabic + kaa Kara-Kalpak + kab Kabyle + kac Kachin + kal Greenlandic + kam Kamba + kan Kannada + kar Karen + kas Kashmiri + kat Georgian + kau Kanuri + kaw Kawi + kaz Kazakh + kha Khasi + khi Khoisan (Other) + khm Khmer + kho Khotanese + kik Kikuyu + kin Kinyarwanda + kir Kirghiz + kok Konkani + kom Komi + kon Kongo + kor Korean + kpe Kpelle + kro Kru + kru Kurukh + kua Kuanyama + kum Kumyk + kur Kurdish + kus Kusaie + kut Kutenai + lad Ladino + lah Lahnda + lam Lamba + lao Lao + lat Latin + lav Latvian + lez Lezghian + lin Lingala + lit Lithuanian + lol Mongo + loz Lozi + ltz Letzeburgesch + lub Luba-Katanga + lug Ganda + lui Luiseno + lun Lunda + luo Luo (Kenya and Tanzania) + mac Macedonian + mad Madurese + mag Magahi + mah Marshall + mai Maithili + mak Macedonian + mak Makasar + mal Malayalam + man Mandingo + mao Maori + map Austronesian (Other) + mar Marathi + mas Masai + max Manx + may Malay + men Mende + mga Irish, Middle (900 - 1200) + mic Micmac + min Minangkabau + mis Miscellaneous (Other) + mkh Mon-Kmer (Other) + mlg Malagasy + mlt Maltese + mni Manipuri + mno Manobo Languages + moh Mohawk + mol Moldavian + mon Mongolian + mos Mossi + mri Maori + msa Malay + mul Multiple Languages + mun Munda Languages + mus Creek + mwr Marwari + mya Burmese + myn Mayan Languages + nah Aztec + nai North American Indian (Other) + nau Nauru + nav Navajo + nbl Ndebele, South + nde Ndebele, North + ndo Ndongo + nep Nepali + new Newari + nic Niger-Kordofanian (Other) + niu Niuean + nla Dutch + nno Norwegian (Nynorsk) + non Norse, Old + nor Norwegian + nso Sotho, Northern + nub Nubian Languages + nya Nyanja + nym Nyamwezi + nyn Nyankole + nyo Nyoro + nzi Nzima + oci Langue d'Oc (post 1500) + oji Ojibwa + ori Oriya + orm Oromo + osa Osage + oss Ossetic + ota Turkish, Ottoman (1500 - 1928) + oto Otomian Languages + paa Papuan-Australian (Other) + pag Pangasinan + pal Pahlavi + pam Pampanga + pan Panjabi + pap Papiamento + pau Palauan + peo Persian, Old (ca 600 - 400 B.C.) + per Persian + phn Phoenician + pli Pali + pol Polish + pon Ponape + por Portuguese + pra Prakrit uages + pro Provencal, Old (to 1500) + pus Pushto + que Quechua + raj Rajasthani + rar Rarotongan + roa Romance (Other) + roh Rhaeto-Romance + rom Romany + ron Romanian + rum Romanian + run Rundi + rus Russian + sad Sandawe + sag Sango + sah Yakut + sai South American Indian (Other) + sal Salishan Languages + sam Samaritan Aramaic + san Sanskrit + sco Scots + scr Serbo-Croatian + sel Selkup + sem Semitic (Other) + sga Irish, Old (to 900) + shn Shan + sid Sidamo + sin Singhalese + sio Siouan Languages + sit Sino-Tibetan (Other) + sla Slavic (Other) + slk Slovak + slo Slovak + slv Slovenian + smi Sami Languages + smo Samoan + sna Shona + snd Sindhi + sog Sogdian + som Somali + son Songhai + sot Sotho, Southern + spa Spanish + sqi Albanian + srd Sardinian + srr Serer + ssa Nilo-Saharan (Other) + ssw Siswant + ssw Swazi + suk Sukuma + sun Sudanese + sus Susu + sux Sumerian + sve Swedish + swa Swahili + swe Swedish + syr Syriac + tah Tahitian + tam Tamil + tat Tatar + tel Telugu + tem Timne + ter Tereno + tgk Tajik + tgl Tagalog + tha Thai + tib Tibetan + tig Tigre + tir Tigrinya + tiv Tivi + tli Tlingit + tmh Tamashek + tog Tonga (Nyasa) + ton Tonga (Tonga Islands) + tru Truk + tsi Tsimshian + tsn Tswana + tso Tsonga + tuk Turkmen + tum Tumbuka + tur Turkish + tut Altaic (Other) + twi Twi + tyv Tuvinian + uga Ugaritic + uig Uighur + ukr Ukrainian + umb Umbundu + und Undetermined + urd Urdu + uzb Uzbek + vai Vai + ven Venda + vie Vietnamese + vol Volapük + vot Votic + wak Wakashan Languages + wal Walamo + war Waray + was Washo + wel Welsh + wen Sorbian Languages + wol Wolof + xho Xhosa + yao Yao + yap Yap + yid Yiddish + yor Yoruba + zap Zapotec + zen Zenaga + zha Zhuang + zho Chinese + zul Zulu + zun Zuni + + */ + + return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode'); + } + + + public static function ETCOEventLookup($index) { + if (($index >= 0x17) && ($index <= 0xDF)) { + return 'reserved for future use'; + } + if (($index >= 0xE0) && ($index <= 0xEF)) { + return 'not predefined synch 0-F'; + } + if (($index >= 0xF0) && ($index <= 0xFC)) { + return 'reserved for future use'; + } + + static $EventLookup = array( + 0x00 => 'padding (has no meaning)', + 0x01 => 'end of initial silence', + 0x02 => 'intro start', + 0x03 => 'main part start', + 0x04 => 'outro start', + 0x05 => 'outro end', + 0x06 => 'verse start', + 0x07 => 'refrain start', + 0x08 => 'interlude start', + 0x09 => 'theme start', + 0x0A => 'variation start', + 0x0B => 'key change', + 0x0C => 'time change', + 0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)', + 0x0E => 'sustained noise', + 0x0F => 'sustained noise end', + 0x10 => 'intro end', + 0x11 => 'main part end', + 0x12 => 'verse end', + 0x13 => 'refrain end', + 0x14 => 'theme end', + 0x15 => 'profanity', + 0x16 => 'profanity end', + 0xFD => 'audio end (start of silence)', + 0xFE => 'audio file ends', + 0xFF => 'one more byte of events follows' + ); + + return (isset($EventLookup[$index]) ? $EventLookup[$index] : ''); + } + + public static function SYTLContentTypeLookup($index) { + static $SYTLContentTypeLookup = array( + 0x00 => 'other', + 0x01 => 'lyrics', + 0x02 => 'text transcription', + 0x03 => 'movement/part name', // (e.g. 'Adagio') + 0x04 => 'events', // (e.g. 'Don Quijote enters the stage') + 0x05 => 'chord', // (e.g. 'Bb F Fsus') + 0x06 => 'trivia/\'pop up\' information', + 0x07 => 'URLs to webpages', + 0x08 => 'URLs to images' + ); + + return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : ''); + } + + public static function APICPictureTypeLookup($index, $returnarray=false) { + static $APICPictureTypeLookup = array( + 0x00 => 'Other', + 0x01 => '32x32 pixels \'file icon\' (PNG only)', + 0x02 => 'Other file icon', + 0x03 => 'Cover (front)', + 0x04 => 'Cover (back)', + 0x05 => 'Leaflet page', + 0x06 => 'Media (e.g. label side of CD)', + 0x07 => 'Lead artist/lead performer/soloist', + 0x08 => 'Artist/performer', + 0x09 => 'Conductor', + 0x0A => 'Band/Orchestra', + 0x0B => 'Composer', + 0x0C => 'Lyricist/text writer', + 0x0D => 'Recording Location', + 0x0E => 'During recording', + 0x0F => 'During performance', + 0x10 => 'Movie/video screen capture', + 0x11 => 'A bright coloured fish', + 0x12 => 'Illustration', + 0x13 => 'Band/artist logotype', + 0x14 => 'Publisher/Studio logotype' + ); + if ($returnarray) { + return $APICPictureTypeLookup; + } + return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : ''); + } + + public static function COMRReceivedAsLookup($index) { + static $COMRReceivedAsLookup = array( + 0x00 => 'Other', + 0x01 => 'Standard CD album with other songs', + 0x02 => 'Compressed audio on CD', + 0x03 => 'File over the Internet', + 0x04 => 'Stream over the Internet', + 0x05 => 'As note sheets', + 0x06 => 'As note sheets in a book with other sheets', + 0x07 => 'Music on other media', + 0x08 => 'Non-musical merchandise' + ); + + return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : ''); + } + + public static function RVA2ChannelTypeLookup($index) { + static $RVA2ChannelTypeLookup = array( + 0x00 => 'Other', + 0x01 => 'Master volume', + 0x02 => 'Front right', + 0x03 => 'Front left', + 0x04 => 'Back right', + 0x05 => 'Back left', + 0x06 => 'Front centre', + 0x07 => 'Back centre', + 0x08 => 'Subwoofer' + ); + + return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : ''); + } + + public static function FrameNameLongLookup($framename) { + + $begin = __LINE__; + + /** This is not a comment! + + AENC Audio encryption + APIC Attached picture + ASPI Audio seek point index + BUF Recommended buffer size + CNT Play counter + COM Comments + COMM Comments + COMR Commercial frame + CRA Audio encryption + CRM Encrypted meta frame + ENCR Encryption method registration + EQU Equalisation + EQU2 Equalisation (2) + EQUA Equalisation + ETC Event timing codes + ETCO Event timing codes + GEO General encapsulated object + GEOB General encapsulated object + GRID Group identification registration + IPL Involved people list + IPLS Involved people list + LINK Linked information + LNK Linked information + MCDI Music CD identifier + MCI Music CD Identifier + MLL MPEG location lookup table + MLLT MPEG location lookup table + OWNE Ownership frame + PCNT Play counter + PIC Attached picture + POP Popularimeter + POPM Popularimeter + POSS Position synchronisation frame + PRIV Private frame + RBUF Recommended buffer size + REV Reverb + RVA Relative volume adjustment + RVA2 Relative volume adjustment (2) + RVAD Relative volume adjustment + RVRB Reverb + SEEK Seek frame + SIGN Signature frame + SLT Synchronised lyric/text + STC Synced tempo codes + SYLT Synchronised lyric/text + SYTC Synchronised tempo codes + TAL Album/Movie/Show title + TALB Album/Movie/Show title + TBP BPM (Beats Per Minute) + TBPM BPM (beats per minute) + TCM Composer + TCMP Part of a compilation + TCO Content type + TCOM Composer + TCON Content type + TCOP Copyright message + TCP Part of a compilation + TCR Copyright message + TDA Date + TDAT Date + TDEN Encoding time + TDLY Playlist delay + TDOR Original release time + TDRC Recording time + TDRL Release time + TDTG Tagging time + TDY Playlist delay + TEN Encoded by + TENC Encoded by + TEXT Lyricist/Text writer + TFLT File type + TFT File type + TIM Time + TIME Time + TIPL Involved people list + TIT1 Content group description + TIT2 Title/songname/content description + TIT3 Subtitle/Description refinement + TKE Initial key + TKEY Initial key + TLA Language(s) + TLAN Language(s) + TLE Length + TLEN Length + TMCL Musician credits list + TMED Media type + TMOO Mood + TMT Media type + TOA Original artist(s)/performer(s) + TOAL Original album/movie/show title + TOF Original filename + TOFN Original filename + TOL Original Lyricist(s)/text writer(s) + TOLY Original lyricist(s)/text writer(s) + TOPE Original artist(s)/performer(s) + TOR Original release year + TORY Original release year + TOT Original album/Movie/Show title + TOWN File owner/licensee + TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group + TP2 Band/Orchestra/Accompaniment + TP3 Conductor/Performer refinement + TP4 Interpreted, remixed, or otherwise modified by + TPA Part of a set + TPB Publisher + TPE1 Lead performer(s)/Soloist(s) + TPE2 Band/orchestra/accompaniment + TPE3 Conductor/performer refinement + TPE4 Interpreted, remixed, or otherwise modified by + TPOS Part of a set + TPRO Produced notice + TPUB Publisher + TRC ISRC (International Standard Recording Code) + TRCK Track number/Position in set + TRD Recording dates + TRDA Recording dates + TRK Track number/Position in set + TRSN Internet radio station name + TRSO Internet radio station owner + TS2 Album-Artist sort order + TSA Album sort order + TSC Composer sort order + TSI Size + TSIZ Size + TSO2 Album-Artist sort order + TSOA Album sort order + TSOC Composer sort order + TSOP Performer sort order + TSOT Title sort order + TSP Performer sort order + TSRC ISRC (international standard recording code) + TSS Software/hardware and settings used for encoding + TSSE Software/Hardware and settings used for encoding + TSST Set subtitle + TST Title sort order + TT1 Content group description + TT2 Title/Songname/Content description + TT3 Subtitle/Description refinement + TXT Lyricist/text writer + TXX User defined text information frame + TXXX User defined text information frame + TYE Year + TYER Year + UFI Unique file identifier + UFID Unique file identifier + ULT Unsychronised lyric/text transcription + USER Terms of use + USLT Unsynchronised lyric/text transcription + WAF Official audio file webpage + WAR Official artist/performer webpage + WAS Official audio source webpage + WCM Commercial information + WCOM Commercial information + WCOP Copyright/Legal information + WCP Copyright/Legal information + WOAF Official audio file webpage + WOAR Official artist/performer webpage + WOAS Official audio source webpage + WORS Official Internet radio station homepage + WPAY Payment + WPB Publishers official webpage + WPUB Publishers official webpage + WXX User defined URL link frame + WXXX User defined URL link frame + TFEA Featured Artist + TSTU Recording Studio + rgad Replay Gain Adjustment + + */ + + return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long'); + + // Last three: + // from Helium2 [www.helium2.com] + // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html + } + + + public static function FrameNameShortLookup($framename) { + + $begin = __LINE__; + + /** This is not a comment! + + AENC audio_encryption + APIC attached_picture + ASPI audio_seek_point_index + BUF recommended_buffer_size + CNT play_counter + COM comment + COMM comment + COMR commercial_frame + CRA audio_encryption + CRM encrypted_meta_frame + ENCR encryption_method_registration + EQU equalisation + EQU2 equalisation + EQUA equalisation + ETC event_timing_codes + ETCO event_timing_codes + GEO general_encapsulated_object + GEOB general_encapsulated_object + GRID group_identification_registration + IPL involved_people_list + IPLS involved_people_list + LINK linked_information + LNK linked_information + MCDI music_cd_identifier + MCI music_cd_identifier + MLL mpeg_location_lookup_table + MLLT mpeg_location_lookup_table + OWNE ownership_frame + PCNT play_counter + PIC attached_picture + POP popularimeter + POPM popularimeter + POSS position_synchronisation_frame + PRIV private_frame + RBUF recommended_buffer_size + REV reverb + RVA relative_volume_adjustment + RVA2 relative_volume_adjustment + RVAD relative_volume_adjustment + RVRB reverb + SEEK seek_frame + SIGN signature_frame + SLT synchronised_lyric + STC synced_tempo_codes + SYLT synchronised_lyric + SYTC synchronised_tempo_codes + TAL album + TALB album + TBP bpm + TBPM bpm + TCM composer + TCMP part_of_a_compilation + TCO genre + TCOM composer + TCON genre + TCOP copyright_message + TCP part_of_a_compilation + TCR copyright_message + TDA date + TDAT date + TDEN encoding_time + TDLY playlist_delay + TDOR original_release_time + TDRC recording_time + TDRL release_time + TDTG tagging_time + TDY playlist_delay + TEN encoded_by + TENC encoded_by + TEXT lyricist + TFLT file_type + TFT file_type + TIM time + TIME time + TIPL involved_people_list + TIT1 content_group_description + TIT2 title + TIT3 subtitle + TKE initial_key + TKEY initial_key + TLA language + TLAN language + TLE length + TLEN length + TMCL musician_credits_list + TMED media_type + TMOO mood + TMT media_type + TOA original_artist + TOAL original_album + TOF original_filename + TOFN original_filename + TOL original_lyricist + TOLY original_lyricist + TOPE original_artist + TOR original_year + TORY original_year + TOT original_album + TOWN file_owner + TP1 artist + TP2 band + TP3 conductor + TP4 remixer + TPA part_of_a_set + TPB publisher + TPE1 artist + TPE2 band + TPE3 conductor + TPE4 remixer + TPOS part_of_a_set + TPRO produced_notice + TPUB publisher + TRC isrc + TRCK track_number + TRD recording_dates + TRDA recording_dates + TRK track_number + TRSN internet_radio_station_name + TRSO internet_radio_station_owner + TS2 album_artist_sort_order + TSA album_sort_order + TSC composer_sort_order + TSI size + TSIZ size + TSO2 album_artist_sort_order + TSOA album_sort_order + TSOC composer_sort_order + TSOP performer_sort_order + TSOT title_sort_order + TSP performer_sort_order + TSRC isrc + TSS encoder_settings + TSSE encoder_settings + TSST set_subtitle + TST title_sort_order + TT1 content_group_description + TT2 title + TT3 subtitle + TXT lyricist + TXX text + TXXX text + TYE year + TYER year + UFI unique_file_identifier + UFID unique_file_identifier + ULT unsychronised_lyric + USER terms_of_use + USLT unsynchronised_lyric + WAF url_file + WAR url_artist + WAS url_source + WCM commercial_information + WCOM commercial_information + WCOP copyright + WCP copyright + WOAF url_file + WOAR url_artist + WOAS url_source + WORS url_station + WPAY url_payment + WPB url_publisher + WPUB url_publisher + WXX url_user + WXXX url_user + TFEA featured_artist + TSTU recording_studio + rgad replay_gain_adjustment + + */ + + return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short'); + } + + public static function TextEncodingTerminatorLookup($encoding) { + // http://www.id3.org/id3v2.4.0-structure.txt + // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: + static $TextEncodingTerminatorLookup = array( + 0 => "\x00", // $00 ISO-8859-1. Terminated with $00. + 1 => "\x00\x00", // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00. + 2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00. + 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00. + 255 => "\x00\x00" + ); + return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : ''); + } + + public static function TextEncodingNameLookup($encoding) { + // http://www.id3.org/id3v2.4.0-structure.txt + // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: + static $TextEncodingNameLookup = array( + 0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00. + 1 => 'UTF-16', // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00. + 2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00. + 3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00. + 255 => 'UTF-16BE' + ); + return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1'); + } + + public static function IsValidID3v2FrameName($framename, $id3v2majorversion) { + switch ($id3v2majorversion) { + case 2: + return preg_match('#[A-Z][A-Z0-9]{2}#', $framename); + break; + + case 3: + case 4: + return preg_match('#[A-Z][A-Z0-9]{3}#', $framename); + break; + } + return false; + } + + public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { + for ($i = 0; $i < strlen($numberstring); $i++) { + if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) { + if (($numberstring{$i} == '.') && $allowdecimal) { + // allowed + } elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) { + // allowed + } else { + return false; + } + } + } + return true; + } + + public static function IsValidDateStampString($datestamp) { + if (strlen($datestamp) != 8) { + return false; + } + if (!self::IsANumber($datestamp, false)) { + return false; + } + $year = substr($datestamp, 0, 4); + $month = substr($datestamp, 4, 2); + $day = substr($datestamp, 6, 2); + if (($year == 0) || ($month == 0) || ($day == 0)) { + return false; + } + if ($month > 12) { + return false; + } + if ($day > 31) { + return false; + } + if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) { + return false; + } + if (($day > 29) && ($month == 2)) { + return false; + } + return true; + } + + public static function ID3v2HeaderLength($majorversion) { + return (($majorversion == 2) ? 6 : 10); + } + +} + diff --git a/extensions/TimedMediaHandler/libs/getid3/readme.txt b/extensions/TimedMediaHandler/libs/getid3/readme.txt new file mode 100644 index 00000000..ebd004eb --- /dev/null +++ b/extensions/TimedMediaHandler/libs/getid3/readme.txt @@ -0,0 +1,591 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// changelog.txt - part of getID3() // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + + This code is released under the GNU GPL: + http://www.gnu.org/copyleft/gpl.html + + +---------------------------------------------+ + | If you do use this code somewhere, send me | + | an email and tell me how/where you used it. | + | | + | If you want to donate, there is a link on | + | http://www.getid3.org for PayPal donations. | + +---------------------------------------------+ + + + +Quick Start +=========================================================================== + +Q: How can I check that getID3() works on my server/files? +A: Unzip getID3() to a directory, then access /demos/demo.browse.php + + + +Support +=========================================================================== + +Q: I have a question, or I found a bug. What do I do? +A: The preferred method of support requests and/or bug reports is the + forum at http://support.getid3.org/ + + + +Sourceforge Notification +=========================================================================== + +It's highly recommended that you sign up for notification from +Sourceforge for when new versions are released. Please visit: +http://sourceforge.net/project/showfiles.php?group_id=55859 +and click the little "monitor package" icon/link. If you're +previously signed up for the mailing list, be aware that it has +been discontinued, only the automated Sourceforge notification +will be used from now on. + + + +What does getID3() do? +=========================================================================== + +Reads & parses (to varying degrees): + € tags: + * APE (v1 and v2) + * ID3v1 (& ID3v1.1) + * ID3v2 (v2.4, v2.3, v2.2) + * Lyrics3 (v1 & v2) + + € audio-lossy: + * MP3/MP2/MP1 + * MPC / Musepack + * Ogg (Vorbis, OggFLAC, Speex) + * AC3 + * DTS + * RealAudio + * Speex + * DSS + * VQF + + € audio-lossless: + * AIFF + * AU + * Bonk + * CD-audio (*.cda) + * FLAC + * LA (Lossless Audio) + * LiteWave + * LPAC + * MIDI + * Monkey's Audio + * OptimFROG + * RKAU + * Shorten + * TTA + * VOC + * WAV (RIFF) + * WavPack + + € audio-video: + * ASF: ASF, Windows Media Audio (WMA), Windows Media Video (WMV) + * AVI (RIFF) + * Flash + * Matroska (MKV) + * MPEG-1 / MPEG-2 + * NSV (Nullsoft Streaming Video) + * Quicktime + * RealVideo + + € still image: + * BMP + * GIF + * JPEG + * PNG + * TIFF + * SWF (Flash) + * PhotoCD + + € data: + * ISO-9660 CD-ROM image (directory structure) + * SZIP (limited support) + * ZIP (directory structure) + * TAR + * CUE + + +Writes: + * ID3v1 (& ID3v1.1) + * ID3v2 (v2.3 & v2.4) + * VorbisComment on OggVorbis + * VorbisComment on FLAC (not OggFLAC) + * APE v2 + * Lyrics3 (delete only) + + + +Requirements +=========================================================================== + +* PHP 4.2.0 (or higher) for getID3() 1.7.x (and earlier) +* PHP 5.3.0 (or higher) for getID3() 1.8.x (and up) +* PHP 5.0.0 (or higher) for getID3() 2.0.x (and up) +* at least 4MB memory for PHP. 8MB is highly recommended. + 12MB is required with all modules loaded. + + + +Usage +=========================================================================== + +See /demos/demo.basic.php for a very basic use of getID3() with no +fancy output, just scanning one file. + +See structure.txt for the returned data structure. + +*> For an example of a complete directory-browsing, <* +*> file-scanning implementation of getID3(), please run <* +*> /demos/demo.browse.php <* + +See /demos/demo.mysql.php for a sample recursive scanning code that +scans every file in a given directory, and all sub-directories, stores +the results in a database and allows various analysis / maintenance +operations + +To analyze remote files over HTTP or FTP you need to copy the file +locally first before running getID3(). Your code would look something +like this: + +// Copy remote file locally to scan with getID3() +$remotefilename = 'http://www.example.com/filename.mp3'; +if ($fp_remote = fopen($remotefilename, 'rb')) { + $localtempfilename = tempnam('/tmp', 'getID3'); + if ($fp_local = fopen($localtempfilename, 'wb')) { + while ($buffer = fread($fp_remote, 8192)) { + fwrite($fp_local, $buffer); + } + fclose($fp_local); + + // Initialize getID3 engine + $getID3 = new getID3; + + $ThisFileInfo = $getID3->analyze($filename); + + // Delete temporary file + unlink($localtempfilename); + } + fclose($fp_remote); +} + + +See /demos/demo.write.php for how to write tags. + + + +What does the returned data structure look like? +=========================================================================== + +See structure.txt + +It is recommended that you look at the output of +/demos/demo.browse.php scanning the file(s) you're interested in to +confirm what data is actually returned for any particular filetype in +general, and your files in particular, as the actual data returned +may vary considerably depending on what information is available in +the file itself. + + + +Notes +=========================================================================== + +getID3() 1.x: +If the format parser encounters a critical problem, it will return +something in $fileinfo['error'], describing the encountered error. If +a less critical error or notice is generated it will appear in +$fileinfo['warning']. Both keys may contain more than one warning or +error. If something is returned in ['error'] then the file was not +correctly parsed and returned data may or may not be correct and/or +complete. If something is returned in ['warning'] (and not ['error']) +then the data that is returned is OK - usually getID3() is reporting +errors in the file that have been worked around due to known bugs in +other programs. Some warnings may indicate that the data that is +returned is OK but that some data could not be extracted due to +errors in the file. + +getID3() 2.x: +See above except errors are thrown (so you will only get one error). + + + +Disclaimer +=========================================================================== + +getID3() has been tested on many systems, on many types of files, +under many operating systems, and is generally believe to be stable +and safe. That being said, there is still the chance there is an +undiscovered and/or unfixed bug that may potentially corrupt your +file, especially within the writing functions. By using getID3() you +agree that it's not my fault if any of your files are corrupted. +In fact, I'm not liable for anything :) + + + +License +=========================================================================== + +GNU General Public License - see license.txt + +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 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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to: +Free Software Foundation, Inc. +59 Temple Place - Suite 330 +Boston, MA 02111-1307, USA. + +FAQ: +Q: Can I use getID3() in my program? Do I need a commercial license? +A: You're generally free to use getID3 however you see fit. The only + case in which you would require a commercial license is if you're + selling your closed-source program that integrates getID3. If you + sell your program including a copy of getID3, that's fine as long + as you include a copy of the sourcecode when you sell it. Or you + can distribute your code without getID3 and say "download it from + getid3.sourceforge.net" + + + +Why is it called "getID3()" if it does so much more than just that? +=========================================================================== + +v0.1 did in fact just do that. I don't have a copy of code that old, but I +could essentially write it today with a one-line function: + function getID3($filename) { return unpack('a3TAG/a30title/a30artist/a30album/a4year/a28comment/c1track/c1genreid', substr(file_get_contents($filename), -128)); } + + +Future Plans +=========================================================================== +http://www.getid3.org/phpBB3/viewforum.php?f=7 + +* Better support for MP4 container format +* Scan for appended ID3v2 tag at end of file per ID3v2.4 specs (Section 5.0) +* Support for JPEG-2000 (http://www.morgan-multimedia.com/jpeg2000_overview.htm) +* Support for MOD (mod/stm/s3m/it/xm/mtm/ult/669) +* Support for ACE (thanks Vince) +* Support for Ogg other than Vorbis, Speex and OggFlac (ie. Ogg+Xvid) +* Ability to create Xing/LAME VBR header for VBR MP3s that are missing VBR header +* Ability to "clean" ID3v2 padding (replace invalid padding with valid padding) +* Warn if MP3s change version mid-stream (in full-scan mode) +* check for corrupt/broken mid-file MP3 streams in histogram scan +* Support for lossless-compression formats + (http://www.firstpr.com.au/audiocomp/lossless/#Links) + (http://compression.ca/act-sound.html) + (http://web.inter.nl.net/users/hvdh/lossless/lossless.htm) +* Support for RIFF-INFO chunks + * http://lotto.st-andrews.ac.uk/~njh/tag_interchange.html + (thanks Nick Humfrey ) + * http://abcavi.narod.ru/sof/abcavi/infotags.htm + (thanks Kibi) +* Better support for Bink video +* http://www.hr/josip/DSP/AudioFile2.html +* http://www.pcisys.net/~melanson/codecs/ +* Detect mp3PRO +* Support for PSD +* Support for JPC +* Support for JP2 +* Support for JPX +* Support for JB2 +* Support for IFF +* Support for ICO +* Support for ANI +* Support for EXE (comments, author, etc) (thanks p*quaedackersØplanet*nl) +* Support for DVD-IFO (region, subtitles, aspect ratio, etc) + (thanks p*quaedackersØplanet*nl) +* More complete support for SWF - parsing encapsulated MP3 and/or JPEG content + (thanks n8n8Øyahoo*com) +* Support for a2b +* Optional scan-through-frames for AVI verification + (thanks rockcohenØmassive-interactive*nl) +* Support for TTF (thanks infoØbutterflyx*com) +* Support for DSS (http://www.getid3.org/phpBB3/viewtopic.php?t=171) +* Support for SMAF (http://smaf-yamaha.com/what/demo.html) + http://www.getid3.org/phpBB3/viewtopic.php?t=182 +* Support for AMR (http://www.getid3.org/phpBB3/viewtopic.php?t=195) +* Support for 3gpp (http://www.getid3.org/phpBB3/viewtopic.php?t=195) +* Support for ID4 (http://www.wackysoft.cjb.net grizlyY2KØhotmail*com) +* Parse XML data returned in Ogg comments +* Parse XML data from Quicktime SMIL metafiles (klausrathØmac*com) +* ID3v2 genre string creator function +* More complete parsing of JPG +* Support for all old-style ASF packets +* ASF/WMA/WMV tag writing +* Parse declared T??? ID3v2 text information frames, where appropriate + (thanks Christian Fritz for the idea) +* Recognize encoder: + http://www.guerillasoft.com/EncSpot2/index.html + http://ff123.net/identify.html + http://www.hydrogenaudio.org/?act=ST&f=16&t=9414 + http://www.hydrogenaudio.org/?showtopic=11785 +* Support for other OS/2 bitmap structures: Bitmap Array('BA'), + Color Icon('CI'), Color Pointer('CP'), Icon('IC'), Pointer ('PT') + http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm +* Support for WavPack RAW mode +* ASF/WMA/WMV data packet parsing +* ID3v2FrameFlagsLookupTagAlter() +* ID3v2FrameFlagsLookupFileAlter() +* obey ID3v2 tag alter/preserve/discard rules +* http://www.geocities.com/SiliconValley/Sector/9654/Softdoc/Illyrium/Aolyr.htm +* proper checking for LINK/LNK frame validity in ID3v2 writing +* proper checking for ASPI-TLEN frame validity in ID3v2 writing +* proper checking for COMR frame validity in ID3v2 writing +* http://www.geocities.co.jp/SiliconValley-Oakland/3664/index.html +* decode GEOB ID3v2 structure as encoded by RealJukebox, + decode NCON ID3v2 structure as encoded by MusicMatch + (probably won't happen - the formats are proprietary) + + + +Known Bugs/Issues in getID3() that may be fixed eventually +=========================================================================== +http://www.getid3.org/phpBB3/viewtopic.php?t=25 + +* Cannot determine bitrate for MPEG video with VBR video data + (need documentation) +* Interlace/progressive cannot be determined for MPEG video + (need documentation) +* MIDI playtime is sometimes inaccurate +* AAC-RAW mode files cannot be identified +* WavPack-RAW mode files cannot be identified +* mp4 files report lots of "Unknown QuickTime atom type" + (need documentation) +* Encrypted ASF/WMA/WMV files warn about "unhandled GUID + ASF_Content_Encryption_Object" +* Bitrate split between audio and video cannot be calculated for + NSV, only the total bitrate. (need documentation) +* All Ogg formats (Vorbis, OggFLAC, Speex) are affected by the + problem of large VorbisComments spanning multiple Ogg pages, but + but only OggVorbis files can be processed with vorbiscomment. +* The version of "head" supplied with Mac OS 10.2.8 (maybe other + versions too) does only understands a single option (-n) and + therefore fails. getID3 ignores this and returns wrong md5_data. + + + +Known Bugs/Issues in getID3() that cannot be fixed +-------------------------------------------------- +http://www.getid3.org/phpBB3/viewtopic.php?t=25 + +* 32-bit PHP installations only: + Files larger than 2GB cannot always be parsed fully by getID3() + due to limitations in the 32-bit PHP filesystem functions. + NOTE: Since v1.7.8b3 there is partial support for larger-than- + 2GB files, most of which will parse OK, as long as no critical + data is located beyond the 2GB offset. + Known will-work: + * all file formats on 64-bit PHP + * ZIP (format doesn't support files >2GB) + * FLAC (current encoders don't support files >2GB) + Known will-not-work: + * ID3v1 tags (always located at end-of-file) + * Lyrics3 tags (always located at end-of-file) + * APE tags (always located at end-of-file) + Maybe-will-work: + * Quicktime (will work if needed metadata is before 2GB offset, + that is if the file has been hinted/optimized for streaming) + * RIFF.WAV (should work fine, but gives warnings about not being + able to parse all chunks) + * RIFF.AVI (playtime will probably be wrong, is only based on + "movi" chunk that fits in the first 2GB, should issue error + to show that playtime is incorrect. Other data should be mostly + correct, assuming that data is constant throughout the file) + + + +Known Bugs/Issues in other programs +----------------------------------- +http://www.getid3.org/phpBB3/viewtopic.php?t=25 + +* Windows Media Player (up to v11) and iTunes (up to v10+) do + not correctly handle ID3v2.3 tags with UTF-16BE+BOM + encoding (they assume the data is UTF-16LE+BOM and either + crash (WMP) or output Asian character set (iTunes) +* Winamp (up to v2.80 at least) does not support ID3v2.4 tags, + only ID3v2.3 + see: http://forums.winamp.com/showthread.php?postid=387524 +* Some versions of Helium2 (www.helium2.com) do not write + ID3v2.4-compliant Frame Sizes, even though the tag is marked + as ID3v2.4) (detected by getID3()) +* MP3ext V3.3.17 places a non-compliant padding string at the end + of the ID3v2 header. This is supposedly fixed in v3.4b21 but + only if you manually add a registry key. This fix is not yet + confirmed. (detected by getID3()) +* CDex v1.40 (fixed by v1.50b7) writes non-compliant Ogg comment + strings, supposed to be in the format "NAME=value" but actually + written just "value" (detected by getID3()) +* Oggenc 0.9-rc3 flags the encoded file as ABR whether it's + actually ABR or VBR. +* iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably + other versions are too) writes ID3v2.3 comment tags using a + frame name 'COM ' which is not valid for ID3v2.3+ (it's an + ID3v2.2-style frame name) (detected by getID3()) +* MP2enc does not encode mono CBR MP2 files properly (half speed + sound and double playtime) +* MP2enc does not encode mono VBR MP2 files properly (actually + encoded as stereo) +* tooLAME does not encode mono VBR MP2 files properly (actually + encoded as stereo) +* AACenc encodes files in VBR mode (actually ABR) even if CBR is + specified +* AAC/ADIF - bitrate_mode = cbr for vbr files +* LAME 3.90-3.92 prepends one frame of null data (space for the + LAME/VBR header, but it never gets written) when encoding in CBR + mode with the DLL +* Ahead Nero encodes TwinVQF with a DSIZ value (which is supposed + to be the filesize in bytes) of "0" for TwinVQF v1.0 and "1" for + TwinVQF v2.0 (detected by getID3()) +* Ahead Nero encodes TwinVQF files 1 second shorter than they + should be +* AAC-ADTS files are always actually encoded VBR, even if CBR mode + is specified (the CBR-mode switches on the encoder enable ABR + mode, not CBR as such, but it's not possible to tell the + difference between such ABR files and true VBR) +* STREAMINFO.audio_signature in OggFLAC is always null. "The reason + it's like that is because there is no seeking support in + libOggFLAC yet, so it has no way to go back and write the + computed sum after encoding. Seeking support in Ogg FLAC is the + #1 item for the next release." - Josh Coalson (FLAC developer) + NOTE: getID3() will calculate md5_data in a method similar to + other file formats, but that value cannot be compared to the + md5_data value from FLAC data in a FLAC file format. +* STREAMINFO.audio_signature is not calculated in FLAC v0.3.0 & + v0.4.0 - getID3() will calculate md5_data in a method similar to + other file formats, but that value cannot be compared to the + md5_data value from FLAC v0.5.0+ +* RioPort (various versions including 2.0 and 3.11) tags ID3v2 with + a WCOM frame that has no data portion +* Earlier versions of Coolplayer adds illegal ID3 tags to Ogg Vorbis + files, thus making them corrupt. +* Meracl ID3 Tag Writer v1.3.4 (and older) incorrectly truncates the + last byte of data from an MP3 file when appending a new ID3v1 tag. + (detected by getID3()) +* Lossless-Audio files encoded with and without the -noseek switch + do actually differ internally and therefore cannot match md5_data +* iTunes has been known to append a new ID3v1 tag on the end of an + existing ID3v1 tag when ID3v2 tag is also present + (detected by getID3()) +* MediaMonkey may write a blank RGAD ID3v2 frame but put actual + replay gain adjustments in a series of user-defined TXXX frames + (detected and handled by getID3() since v1.9.2) + + + + +Reference material: +=========================================================================== + +[www.id3.org material now mirrored at http://id3lib.sourceforge.net/id3/] +* http://www.id3.org/id3v2.4.0-structure.txt +* http://www.id3.org/id3v2.4.0-frames.txt +* http://www.id3.org/id3v2.4.0-changes.txt +* http://www.id3.org/id3v2.3.0.txt +* http://www.id3.org/id3v2-00.txt +* http://www.id3.org/mp3frame.html +* http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html +* http://www.dv.co.yu/mpgscript/mpeghdr.htm +* http://www.mp3-tech.org/programmer/frame_header.html +* http://users.belgacom.net/gc247244/extra/tag.html +* http://gabriel.mp3-tech.org/mp3infotag.html +* http://www.id3.org/iso4217.html +* http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-1.TXT +* http://www.xiph.org/ogg/vorbis/doc/framing.html +* http://www.xiph.org/ogg/vorbis/doc/v-comment.html +* http://leknor.com/code/php/class.ogg.php.txt +* http://www.id3.org/iso639-2.html +* http://www.id3.org/lyrics3.html +* http://www.id3.org/lyrics3200.html +* http://www.psc.edu/general/software/packages/ieee/ieee.html +* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html +* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html +* http://www.jmcgowan.com/avi.html +* http://www.wotsit.org/ +* http://www.herdsoft.com/ti/davincie/davp3xo2.htm +* http://www.mathdogs.com/vorbis-illuminated/bitstream-appendix.html +* "Standard MIDI File Format" by Dustin Caldwell (from www.wotsit.org) +* http://midistudio.com/Help/GMSpecs_Patches.htm +* http://www.xiph.org/archives/vorbis/200109/0459.html +* http://www.replaygain.org/ +* http://www.lossless-audio.com/ +* http://download.microsoft.com/download/winmediatech40/Doc/1.0/WIN98MeXP/EN-US/ASF_Specification_v.1.0.exe +* http://mediaxw.sourceforge.net/files/doc/Active%20Streaming%20Format%20(ASF)%201.0%20Specification.pdf +* http://www.uni-jena.de/~pfk/mpp/sv8/ (archived at http://www.hydrogenaudio.org/musepack/klemm/www.personal.uni-jena.de/~pfk/mpp/sv8/) +* http://jfaul.de/atl/ +* http://www.uni-jena.de/~pfk/mpp/ (archived at http://www.hydrogenaudio.org/musepack/klemm/www.personal.uni-jena.de/~pfk/mpp/) +* http://www.libpng.org/pub/png/spec/png-1.2-pdg.html +* http://www.real.com/devzone/library/creating/rmsdk/doc/rmff.htm +* http://www.fastgraph.com/help/bmp_os2_header_format.html +* http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm +* http://flac.sourceforge.net/format.html +* http://www.research.att.com/projects/mpegaudio/mpeg2.html +* http://www.audiocoding.com/wiki/index.php?page=AAC +* http://libmpeg.org/mpeg4/doc/w2203tfs.pdf +* http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt +* http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/frameset.htm +* http://www.nullsoft.com/nsv/ +* http://www.wotsit.org/download.asp?f=iso9660 +* http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html +* http://www.cdroller.com/htm/readdata.html +* http://www.speex.org/manual/node10.html +* http://www.harmony-central.com/Computer/Programming/aiff-file-format.doc +* http://www.faqs.org/rfcs/rfc2361.html +* http://ghido.shelter.ro/ +* http://www.ebu.ch/tech_t3285.pdf +* http://www.sr.se/utveckling/tu/bwf +* http://ftp.aessc.org/pub/aes46-2002.pdf +* http://cartchunk.org:8080/ +* http://www.broadcastpapers.com/radio/cartchunk01.htm +* http://www.hr/josip/DSP/AudioFile2.html +* http://home.attbi.com/~chris.bagwell/AudioFormats-11.html +* http://www.pure-mac.com/extkey.html +* http://cesnet.dl.sourceforge.net/sourceforge/bonkenc/bonk-binary-format-0.9.txt +* http://www.headbands.com/gspot/ +* http://www.openswf.org/spec/SWFfileformat.html +* http://j-faul.virtualave.net/ +* http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html +* http://cui.unige.ch/OSG/info/AudioFormats/ap11.html +* http://sswf.sourceforge.net/SWFalexref.html +* http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt +* http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm +* http://developer.apple.com/quicktime/icefloe/dispatch012.html +* http://www.csdn.net/Dev/Format/graphics/PCD.htm +* http://tta.iszf.irk.ru/ +* http://www.atsc.org/standards/a_52a.pdf +* http://www.alanwood.net/unicode/ +* http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html +* http://www.its.msstate.edu/net/real/reports/config/tags.stats +* http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt +* http://brennan.young.net/Comp/LiveStage/things.html +* http://www.multiweb.cz/twoinches/MP3inside.htm +* http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended +* http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ +* http://www.unicode.org/unicode/faq/utf_bom.html +* http://tta.corecodec.org/?menu=format +* http://www.scvi.net/nsvformat.htm +* http://pda.etsi.org/pda/queryform.asp +* http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm +* http://trac.musepack.net/trac/wiki/SV8Specification +* http://wyday.com/cuesharp/specification.php +* http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html \ No newline at end of file diff --git a/extensions/TimedMediaHandler/maintenance/cleanupTranscodes.php b/extensions/TimedMediaHandler/maintenance/cleanupTranscodes.php new file mode 100644 index 00000000..01c7b004 --- /dev/null +++ b/extensions/TimedMediaHandler/maintenance/cleanupTranscodes.php @@ -0,0 +1,46 @@ +addOption( "key", "remove all transcodes for given key", false, true ); + $this->addOption( "all", "remove all transcodes", false, false ); + $this->mDescription = "cleanup transcodes left over after changing encoding profiles."; + } + public function execute() { + global $wgEnabledTranscodeSet; + + if ( $this->hasOption( "all" ) ) { + $where = array(); + } elseif ( $this->hasOption( "key" ) ) { + $where = array( 'transcode_key' => $this->getOption( 'key' ) ); + } else { + $where = 'transcode_key NOT IN ("'. implode('", "', $wgEnabledTranscodeSet ).'")'; + } + $this->output( "Cleanup transcodes:\n" ); + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'transcode', '*', $where, __METHOD__ ); + foreach ( $res as $row ) { + $this->output( + 'remove: '. $row->transcode_image_name . ' ' . $row->transcode_key . "\n" + ); + $title = Title::newFromText( $row->transcode_image_name, NS_FILE ); + $file = wfLocalFile( $title ); + WebVideoTranscode::removeTranscodes( $file, $row->transcode_key ); + } + $this->output( "Finished!\n" ); + } +} + +$maintClass = 'CleanupTranscodes'; // Tells it to run the class +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/extensions/TimedMediaHandler/maintenance/moveTranscoded.php b/extensions/TimedMediaHandler/maintenance/moveTranscoded.php new file mode 100644 index 00000000..1ff6717c --- /dev/null +++ b/extensions/TimedMediaHandler/maintenance/moveTranscoded.php @@ -0,0 +1,56 @@ +mDescription = "move transcoded files from thumb to transcoded container."; + } + public function execute() { + global $wgEnabledTranscodeSet; + + $this->output( "Move transcoded files:\n" ); + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'transcode', '*', array(), __METHOD__ ); + foreach ( $res as $row ) { + $title = Title::newFromText( $row->transcode_image_name, NS_FILE ); + $file = wfLocalFile( $title ); + if ( !$file ) { + continue; + } + $oldPath = $file->getThumbPath( $file->getName() . '.' . $row->transcode_key ); + + $newPath = WebVideoTranscode::getDerivativeFilePath( $file, $row->transcode_key ); + if ($oldPath != $newPath) { + if( $file->repo->fileExists( $oldPath ) ){ + if( $file->repo->fileExists( $newPath ) ){ + $res = $file->repo->quickPurge( $oldPath ); + if( !$res ){ + wfDebug( "Could not delete file $oldPath\n" ); + } else { + $this->output( "deleted $oldPath, exists in transcoded container\n" ); + } + } else { + $this->output( " $oldPath => $newPath\n" ); + $file->repo->quickImport( $oldPath, $newPath ); + $file->repo->quickPurge( $oldPath ); + } + } + } + + } + $this->output( "Finished!\n" ); + } +} + +$maintClass = 'MoveTranscoded'; // Tells it to run the class +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/extensions/TimedMediaHandler/maintenance/resetTranscodes.php b/extensions/TimedMediaHandler/maintenance/resetTranscodes.php new file mode 100644 index 00000000..ca3493ed --- /dev/null +++ b/extensions/TimedMediaHandler/maintenance/resetTranscodes.php @@ -0,0 +1,41 @@ +mDescription = "Reset stalled transcodes, that are no longer in the job queue."; + } + public function execute() { + global $wgEnabledTranscodeSet; + $where = array( + "transcode_time_startwork" => NULL, + "transcode_time_error" => NULL + ); + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'transcode', '*', $where, __METHOD__ ); + foreach ( $res as $row ) { + $title = Title::newFromText( $row->transcode_image_name, NS_FILE ); + // re-insert WebVideoTranscodeJob, + // will only be added if not in queue + // due to deduplication + $job = new WebVideoTranscodeJob( $title, array( + 'transcodeMode' => 'derivative', + 'transcodeKey' => $row->transcode_key, + ) ); + $job->insert(); + } + } +} + +$maintClass = 'ResetTranscodes'; // Tells it to run the class +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/extensions/TimedMediaHandler/maintenance/retryTranscodes.php b/extensions/TimedMediaHandler/maintenance/retryTranscodes.php new file mode 100644 index 00000000..be1ab9ee --- /dev/null +++ b/extensions/TimedMediaHandler/maintenance/retryTranscodes.php @@ -0,0 +1,55 @@ +addOption( "key", "retry all failed transcodes for given key", false, true ); + $this->addOption( "error", "retry all failed transcodes matching error substring", false, true); + $this->mDescription = "retry transcodes for given key or error"; + } + public function execute() { + if ( !$this->hasOption( "error" ) && !$this->hasOption( "key" ) ) { + $this->output("You have to provide --key and/or --error\n"); + return; + } + $dbw = wfGetDB( DB_MASTER ); + $cond = array(); + $cond[] = 'transcode_time_error IS NOT NULL'; + if ( $this->hasOption( "key" ) ) { + $cond['transcode_key'] = $this->getOption( 'key' ); + } + if ( $this->hasOption( "error" ) ) { + $cond[] = "transcode_error " . $dbw->buildLike( $dbw->anyString(), + $this->getOption( 'error' ), $dbw->anyString() ); + } + do { + $res = $dbw->select( 'transcode', 'transcode_id', + $cond, __METHOD__, array( 'LIMIT' => 100 ) ); + $ids = array(); + foreach ( $res as $row ) { + $ids[] = $row->transcode_id; + } + if ( $ids ) { + $dbw->delete( 'transcode', + array( 'transcode_id' => $ids ), __METHOD__ ); + wfWaitForSlaves(); + } + } while ($ids); + } +} + +$maintClass = 'RetryTranscodes'; // Tells it to run the class +require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/extensions/TimedMediaHandler/mwEmbedLoader.php b/extensions/TimedMediaHandler/mwEmbedLoader.php new file mode 100644 index 00000000..ae03a3dd --- /dev/null +++ b/extensions/TimedMediaHandler/mwEmbedLoader.php @@ -0,0 +1,24 @@ +') + .css( { + 'width':'99%', + 'height':'99%' + } ) + .text( $(this).attr('data-error') ) + } ) + .css( 'overflow', 'hidden' ); + return false; + }; + + // Old version. Need to keep for a little while in case of cached pages. + $( '.mw-filepage-transcodestatus .errorlink' ).click( errorPopup ); + // New version. + $errorLink = $( '.mw-filepage-transcodestatus .mw-tmh-pseudo-error-link' ); + $errorLink.wrapInner( function () { + var $this = $( this ); + return $( '' ).attr( { + href: '#', + title: $this.text(), + 'data-error': $this.attr( 'data-error' ) + } ).click( errorPopup ); + } ); + + // Reset transcode action: + $( '.mw-filepage-transcodereset a' ).click( function () { + var tKey = $( this ).attr( 'data-transcodekey' ), + buttons = {}; + + buttons[ mw.msg( 'mwe-ok' ) ] = function () { + var api, + _thisDialog = this, + cancelBtn = {}; + + // Only show cancel button while loading: + cancelBtn[ mw.msg( 'mwe-cancel' ) ] = function () { + $(this).dialog( 'close' ); + }; + $( _thisDialog ).dialog( 'option', 'buttons', cancelBtn ); + + $( this ).loadingSpinner(); + + api = new mw.Api(); + api.postWithEditToken( { + 'action' : 'transcodereset', + 'transcodekey' : tKey, + 'title' : mw.config.get('wgPageName') + } ).done( function () { + // Refresh the page + location.reload(); + } ).fail( function ( code, data ) { + if( data.error && data.error.info ){ + $( _thisDialog ).text( data.error.info ); + } else { + $( _thisDialog ).text( mw.msg( 'timedmedia-reset-error' ) ); + } + var okBtn = {}; + okBtn[ mw.msg('mwe-ok') ] = function() { $(this).dialog( 'close' ); }; + $( _thisDialog ).dialog( 'option', 'buttons', okBtn ); + } ); + }; + buttons[ mw.msg( 'mwe-cancel' ) ] = function () { + $( this ).dialog( 'close' ); + }; + // pop up dialog + mw.addDialog( { + 'width': '400', + 'height': '200', + 'title': mw.msg( 'timedmedia-reset' ), + 'content': mw.msg( 'timedmedia-reset-confirm' ), + 'buttons': buttons + } ) + .css( 'overflow', 'hidden' ); + return false; + }); + }); +} )( mediaWiki, jQuery ); diff --git a/extensions/TimedMediaHandler/resources/mw.MediaWikiPlayer.loader.js b/extensions/TimedMediaHandler/resources/mw.MediaWikiPlayer.loader.js new file mode 100644 index 00000000..6167418b --- /dev/null +++ b/extensions/TimedMediaHandler/resources/mw.MediaWikiPlayer.loader.js @@ -0,0 +1,8 @@ +( function ( mw, $ ) { + // Add MediaWikiSupportPlayer dependency on players with a mediaWiki title + $( mw ).bind( 'EmbedPlayerUpdateDependencies', function ( event, embedPlayer, dependencySet ) { + if ( $( embedPlayer ).attr( 'data-mwtitle' ) ) { + $.merge( dependencySet, ['mw.MediaWikiPlayerSupport'] ); + } + } ); +} )( mediaWiki, jQuery ); diff --git a/extensions/TimedMediaHandler/resources/mw.MediaWikiPlayerSupport.js b/extensions/TimedMediaHandler/resources/mw.MediaWikiPlayerSupport.js new file mode 100644 index 00000000..16ce367c --- /dev/null +++ b/extensions/TimedMediaHandler/resources/mw.MediaWikiPlayerSupport.js @@ -0,0 +1,380 @@ +( function ( mw, $ ) { + /** + * Merge in the default video attributes supported by embedPlayer: + */ + mw.mergeConfig( 'EmbedPlayer.Attributes', { + // A apiTitleKey for looking up subtitles, credits and related videos + 'data-mwtitle': null, + + // The apiProvider where to lookup the title key + 'data-mwprovider': null + } ); + + // Add mediaWiki player support to target embedPlayer + $( mw ).bind( 'EmbedPlayerNewPlayer', function ( event, embedPlayer ) { + mw.addMediaWikiPlayerSupport( embedPlayer ); + }); + + /** + * Closure function wraps mediaWiki embedPlayer bindings + */ + mw.addMediaWikiPlayerSupport = function ( embedPlayer ) { + var apiTitleKey, apiProvider, $creditsCache = false; + // Set some local variables: + if ( !embedPlayer['data-mwtitle'] ) { + return false; + } else { + apiTitleKey = embedPlayer['data-mwtitle']; + // legacy support ( set as attribute ) + embedPlayer.apiTitleKey = apiTitleKey; + } + // Set local apiProvider via config if not defined + apiProvider = embedPlayer['data-mwprovider']; + if ( !apiProvider ) { + apiProvider = mw.config.get( 'EmbedPlayer.ApiProvider' ); + } + + /** + * Loads mediaWiki sources for a given embedPlayer + * @param {function} callback Function called once player sources have been added + */ + function loadPlayerSources( callback ) { + // Setup the request + var request = { + 'prop': 'imageinfo', + // In case the user added File: or Image: to the apiKey: + 'titles': 'File:' + decodeURIComponent( apiTitleKey ).replace( /^(File:|Image:)/, '' ), + 'iiprop': 'url|size|dimensions|metadata', + 'iiurlwidth': embedPlayer.getWidth(), + 'redirects': true // automatically resolve redirects + }; + + // Run the request: + mw.getJSON( mw.getApiProviderURL( apiProvider ), request, function ( data ) { + var i, page, imageinfo; + if ( data.query.pages ) { + for ( i in data.query.pages ) { + if ( i === '-1' ) { + callback( false ); + return ; + } + page = data.query.pages[i]; + } + } else { + callback( false ); + return ; + } + // Make sure we have imageinfo: + if ( !page.imageinfo || !page.imageinfo[0] ) { + callback( false ); + return ; + } + imageinfo = page.imageinfo[0]; + + // TODO these should call public methods rather than update internals: + + // Update the poster + embedPlayer.poster = imageinfo.thumburl; + + // Add the media src + embedPlayer.mediaElement.tryAddSource( + $( '' ) + .attr( 'src', imageinfo.url ) + .get( 0 ) + ); + + // Set the duration + if ( imageinfo.metadata[2].name === 'length' ) { + embedPlayer.duration = imageinfo.metadata[2].value; + } + + // Set the width height + // Make sure we have an accurate aspect ratio + if ( imageinfo.height !== 0 && imageinfo.width !== 0 ) { + embedPlayer.height = Math.floor( embedPlayer.width * ( imageinfo.height / imageinfo.width ) ); + } + + // Update the css for the player interface + $( embedPlayer ).css( 'height', embedPlayer.height ); + + callback(); + }); + } + + /** + * Build a clip credit from the resource wikiText page + * + * TODO parse the resource page template + * + * @param {String} resourceHTML Resource wiki text page contents + */ + function doCreditLine( resourceHTML, articleUrl ) { + var authUrl, $page, $author, $authorText, $links, $date, $authorLink, + imgSize = {}, + // Get the title string ( again a "Title" like js object could help out here. ) + titleStr = embedPlayer.apiTitleKey.replace( /_/g, ' ' ), + // Setup the initial credits line: + $creditLine = $( '
' ); + + // Add the title: + $creditLine.append( + $( '' ).html( + mw.msg( 'mwe-embedplayer-credit-title', + // get the link + $( '
' ).append( + $( '' ).attr( { + 'href': articleUrl, + 'title': titleStr + } ) + .text( titleStr ) + )[0].innerHTML + ) + ) + ); + + // Parse some data from the page info template if possible: + $page = $( resourceHTML ); + + // Look for author: + $author = $page.find( '#fileinfotpl_aut' ); + if ( $author.length ) { + // Get the real author sibling of fileinfotpl_aut + $authorText = $author.next(); + // Remove white space: + $authorText.find( 'br' ).remove(); + + // Update link to be absolute per page url context: + $links = $authorText.find( 'a' ); + if ( $links.length ) { + $links.each( function ( i, authorLink ) { + $authorLink = $( authorLink ); + authUrl = $authorLink.attr( 'href' ); + authUrl = mw.absoluteUrl( authUrl, articleUrl ); + $authorLink.attr( 'href', authUrl ); + }); + } + $creditLine.append( $( '
' ), + mw.msg( 'mwe-embedplayer-credit-author', $authorText.html() ) + ); + } + + // Look for date: + $date = $page.find( '#fileinfotpl_date' ); + if ( $date.length ) { + // Get the real date sibling of fileinfotpl_date + $date = $date.next(); + + // remove white space: + $date.find( 'br' ).remove(); + $creditLine.append( $( '
' ), + mw.msg( 'mwe-embedplayer-credit-date', $date.html() ) + ); + } + + // Build out the image and credit line + if ( embedPlayer.isAudio() ) { + imgSize.height = imgSize.width = ( embedPlayer.controlBuilder.getOverlayWidth() < 250 ) ? 45 : 80; + } else { + imgSize.width = ( embedPlayer.controlBuilder.getOverlayWidth() < 250 ) ? 45 : 120; + imgSize.height = Math.floor( imgSize.width * ( embedPlayer.getHeight() / embedPlayer.getWidth() ) ); + } + return $( '
' ).addClass( 'creditline' ) + .append( + $( '' ).attr( { + 'href': articleUrl, + 'title': titleStr + } ).html( + $( '' ).attr( { + 'border': 0, + 'src': embedPlayer.poster + } ).css( imgSize ) + ) + ) + .append( + $creditLine + ); + } + + /** + * Issues a request to populate the credits box + */ + function showCredits( $target, callback ) { + var apiUrl, fileTitle, request; + if ( $creditsCache ) { + $target.html( $creditsCache ); + callback( true ); + return; + } + // Setup shortcuts: + apiUrl = mw.getApiProviderURL( apiProvider ); + fileTitle = 'File:' + decodeURIComponent( apiTitleKey ).replace( /^File:|^Image:/, ''); + + // Get the image page ( cache for 1 hour ) + request = { + 'action': 'parse', + 'page': fileTitle, + 'smaxage': 3600, + 'maxage': 3600 + }; + mw.getJSON( apiUrl, request, function ( data ) { + var descUrl = apiUrl.replace( 'api.php', 'index.php'); + descUrl += '?title=' + encodeURIComponent( fileTitle ); + if ( data && data.parse && data.parse.text && data.parse.text['*'] ) { + // TODO improve provider 'concept' to support page title link + $creditsCache = doCreditLine( data.parse.text['*'], descUrl ); + } else { + $creditsCache = doCreditLine( false, descUrl ); + } + $target.html( $creditsCache ); + callback( true ); + } ); + } + /** + * Adds embedPlayer Bindings + */ + + // Show credits when requested + $( embedPlayer ).bindQueueCallback( 'showCredits', function ( $target, callback ) { + if ( $target.data( 'playerId') !== embedPlayer.id ) { + // bad event trigger + return ; + } + // Only request the credits once: + showCredits( $target, callback ); + }); + + // Show credits on clip complete: + $( embedPlayer ).bind( 'onEndedDone', function ( event, id ) { + if ( embedPlayer.id !== id ) { + // possible event trigger error. ( skip ) + return ; + } + // dont show credits for audio elements, + // seek to begining instead + if ( embedPlayer.isAudio() ) { + embedPlayer.setCurrentTime( 0 ); + return ; + } + var cb = embedPlayer.controlBuilder; + cb.checkMenuOverlay(); + cb.showMenuOverlay(); + cb.showMenuItem( 'credits' ); + }); + + $( embedPlayer ).bind( 'showInlineDownloadLink', function () { + // Add recommend HTML5 player if we have non-native playback: + if ( embedPlayer.controlBuilder.checkNativeWarning( ) ) { + embedPlayer.controlBuilder.addWarningBinding( + 'EmbedPlayer.ShowNativeWarning', + mw.msg( 'mwe-embedplayer-for_best_experience', + $( '
' ).append( + $( '' ).attr({ + 'href': 'http://www.mediawiki.org/wiki/Extension:TimedMediaHandler/Client_download', + 'target': '_new' + }) + )[0].innerHTML + ), + true + ); + } + }); + + $( embedPlayer ).bind( 'TimedText_BuildCCMenu', function ( event, $menu, id ) { + var _this, + pageTitle, + addTextPage, + $li; + if ( id !== embedPlayer.id ) { + _this = $( '#' + id )[0].timedText; + embedPlayer = _this.embedPlayer; + } + // Put in the "Make Transcript" link if config enabled and we have an api key + if ( embedPlayer.apiTitleKey ) { + // check if not already there: + if ( $menu.find( '.add-timed-text' ).length ) { + // add text link already present + return ; + } + + pageTitle = 'TimedText:' + + decodeURIComponent( embedPlayer.apiTitleKey ).replace( /^File:|^Image:/, '' ); + addTextPage = mw.getApiProviderURL( apiProvider ) + .replace( 'api.php', 'index.php') + + '?title=' + encodeURIComponent( pageTitle ); + + $li = $.getLineItem( mw.msg( 'mwe-timedtext-upload-timed-text'), 'script', function () { + window.location = addTextPage; + }); + + $li.addClass( 'add-timed-text') + .find( 'a' ) + .attr( { + 'href': addTextPage, + 'target': '_new' + } ); + $menu.append( + $li + ); + } + }); + + $( embedPlayer ).bindQueueCallback( 'checkPlayerSourcesEvent', function ( callback ) { + // Only load source if none are available: + if ( embedPlayer.mediaElement.sources.length === 0 ) { + loadPlayerSources( callback ); + } else { + // No source to load, issue callback directly + callback(); + } + } ); + $( mw ).bindQueueCallback( 'TimedText_LoadTextSource', function ( source, callback ) { + if ( !source.mwtitle || !source.mwprovider ) { + callback(); + return ; + } + // Load via api + var apiUrl = mw.getApiProviderURL( source.mwprovider ), + // Get the image page ( cache for 1 hour ) + request = { + 'action': 'parse', + 'page': source.mwtitle, + 'smaxage': 3600, + 'maxage': 3600 + }; + mw.getJSON( apiUrl, request, function ( data ) { + if ( data && data.parse && data.parse.text && data.parse.text['*'] ) { + source.loaded = true; + source.mimeType = 'text/mw-srt'; + source.captions = source.getCaptions( data.parse.text['*'] ); + callback(); + } else { + mw.log( 'Error: MediaWiki api error in getting timed text:', data ); + callback(); + } + }); + }); + + $( embedPlayer ).bind( 'getShareIframeSrc', function ( event, callback, id ) { + if ( id !== embedPlayer.id ) { + embedPlayer = $( '#' + id )[0]; + } + var iframeUrl = false; + // Do a special check for wikimediacommons provider as a known shared reop + if ( embedPlayer['data-mwprovider'] === 'wikimediacommons' ) { + iframeUrl = '//commons.wikimedia.org/wiki/File:' + decodeURIComponent( embedPlayer.apiTitleKey ).replace( /^(File:|Image:)/, '' ); + } else { + // use the local wiki: + if ( mw.config.get( 'wgServer' ) && mw.config.get( 'wgArticlePath' ) ) { + iframeUrl = mw.config.get( 'wgServer' ) + + mw.config.get( 'wgArticlePath' ).replace( /\$1/, 'File:' + + decodeURIComponent( embedPlayer.apiTitleKey ).replace( /^(File:|Image:)/, '' ) ); + } + } + if ( iframeUrl ) { + iframeUrl += '?embedplayer=yes'; + } + callback( iframeUrl ); + }); + }; + +} )( mediaWiki, jQuery ); diff --git a/extensions/TimedMediaHandler/resources/mw.PopUpThumbVideo.js b/extensions/TimedMediaHandler/resources/mw.PopUpThumbVideo.js new file mode 100644 index 00000000..c0aa30df --- /dev/null +++ b/extensions/TimedMediaHandler/resources/mw.PopUpThumbVideo.js @@ -0,0 +1,44 @@ +/** +* Simple script to add pop-up video dialog link support for video thumbnails +*/ +( function ( mw, $ ) { + $( document ).ready( function () { + $('.PopUpMediaTransform a').each( function () { + var link, title, + parent = $( this ).parent(); + if ( parent.attr( 'videopayload' ) ) { + $( this ).click( function ( /*event*/ ) { + var thisref = this; + + mw.loader.using( 'mw.MwEmbedSupport', function () { + var $videoContainer = $( $( thisref ).parent().attr( 'videopayload' ) ); + mw.addDialog({ + 'width': 'auto', + 'height': 'auto', + 'title': mw.html.escape( $videoContainer.find( 'video, audio' ).attr( 'data-mwtitle' ) ), + 'content': $videoContainer, + 'close': function(){ + // On close destroy the dialog rather than just hiding it, + // so it doesn't eat up resources or keep playing. + $( this ).remove(); + return true; + }, + 'open': function() { + $( this ).find( 'video, audio' ).embedPlayer(); + } + }) + .css( 'overflow', 'hidden' ); + } ); + // don't follow file link + return false; + } ); + } else if ( parent.attr( 'data-videopayload' ) ) { + link = $( this ).attr( 'href' ); + title = mw.Title.newFromImg( { src: link } ); + if ( title && title.getPrefixedDb() !== mw.config.get( 'wgPageName' ) ) { + $( this ).attr( 'href', title.getUrl() ); + } + } /* else fall back to linking directly to media file */ + } ); + } ); +} )( mediaWiki, jQuery ); diff --git a/extensions/TimedMediaHandler/resources/mw.TMHGalleryHook.js b/extensions/TimedMediaHandler/resources/mw.TMHGalleryHook.js new file mode 100644 index 00000000..fd23690e --- /dev/null +++ b/extensions/TimedMediaHandler/resources/mw.TMHGalleryHook.js @@ -0,0 +1,46 @@ +/** +* Simple script to add pop-up video dialog link support for video thumbnails +*/ +( function ( mw ) { + // Hook to allow dynamically resizing videos in image galleries + mw.hook( 'mediawiki.page.gallery.resize' ).add( function ( info ) { + var $mwPlayerContainer, + $popUp, + $tmhVideo, + $mwContainer = info.$imageDiv.find( '.mediaContainer' ); + if ( info.resolved ) { + // Everything is already done here. + return; + } + + $mwContainer = info.$imageDiv.find( '.mediaContainer' ); + if ( $mwContainer.length ) { + // Add some padding, so caption doesn't overlap video controls if + // we are overlaying the caption on top of the image. + if ( !info.$outerDiv.parent().hasClass( 'mw-gallery-packed' ) ) { + info.$outerDiv.find( 'div.gallerytext' ).css( 'padding-bottom', '20px' ); + } + + info.$imageDiv.width( info.imgWidth ); + $mwContainer.width( info.imgWidth ); + $mwPlayerContainer = $mwContainer.children( '.mwPlayerContainer' ); + if ( $mwPlayerContainer.length ) { + // Case 1: HTML5 player already loaded + $mwPlayerContainer.width( info.imgWidth ).height( info.imgHeight ); + $mwPlayerContainer.find( 'img.playerPoster' ).width( info.imgWidth ).height( info.imgHeight ); + } else { + // Case 2: Raw video element + $tmhVideo = info.$imageDiv.find( 'video' ); + $tmhVideo.width( info.imgWidth ).height( info.imgHeight ); + } + info.resolved = true; + return; + } + + $popUp = info.$imageDiv.find( '.PopUpMediaTransform' ); + if ( $popUp.length ) { + info.$imageDiv.width( info.imgWidth ); + $popUp.add( $popUp.find( 'img' ) ).width( info.imgWidth ).height( info.imgHeight ); + } + } ); +} )( mediaWiki ); diff --git a/extensions/TimedMediaHandler/resources/player_big_play_button.png b/extensions/TimedMediaHandler/resources/player_big_play_button.png new file mode 100644 index 00000000..041fea9c Binary files /dev/null and b/extensions/TimedMediaHandler/resources/player_big_play_button.png differ diff --git a/extensions/TimedMediaHandler/resources/player_big_play_button_hover.png b/extensions/TimedMediaHandler/resources/player_big_play_button_hover.png new file mode 100644 index 00000000..3517563e Binary files /dev/null and b/extensions/TimedMediaHandler/resources/player_big_play_button_hover.png differ diff --git a/extensions/TimedMediaHandler/resources/transcodeTable.css b/extensions/TimedMediaHandler/resources/transcodeTable.css new file mode 100644 index 00000000..795ce134 --- /dev/null +++ b/extensions/TimedMediaHandler/resources/transcodeTable.css @@ -0,0 +1,14 @@ +.mw-filepage-transcodestatus .download-btn { + background-image: url('download_sprite.png'); + width: 49px; + height: 36px; + background-repeat:no-repeat; + background-position: -50px; +} +.mw-filepage-transcodestatus .download-btn:hover { + background-position: 0 0; +} +.mw-filepage-transcodestatus .download-btn span { + /* Text hidden by the above image */ + display: none; +} diff --git a/extensions/TimedMediaHandler/tests/phpunit/ApiTestCaseVideoUpload.php b/extensions/TimedMediaHandler/tests/phpunit/ApiTestCaseVideoUpload.php new file mode 100644 index 00000000..9dd4cb52 --- /dev/null +++ b/extensions/TimedMediaHandler/tests/phpunit/ApiTestCaseVideoUpload.php @@ -0,0 +1,130 @@ + 'application/ogg', + 'filePath' => dirname( __FILE__ ) . '/media/test5seconds.electricsheep.300x400.ogv', + "size" => 301477, + "width" => 400, + "height" => 300, + "mediatype" => "VIDEO", + "bandwidth" => 452216, + "framerate" => 30 + ) + ), + array( + array( + 'mime' => 'video/webm', + 'filePath' => dirname( __FILE__ ) . '/media/shuttle10seconds.1080x608.webm', + "size" => 699018, + "width" => 1080, + "height" => 608, + "mediatype" => "VIDEO", + "bandwidth" => 522142, + "framerate" => 29.97 + ) + ) + ); + } + /** + * Fixture -- run after every test + * Clean up temporary files etc. + * + */ + protected function tearDown() { + parent::tearDown(); + + $testMediaFiles = $this->mediaFilesProvider(); + foreach( $testMediaFiles as $file ){ + $file = $file[0]; + // Clean up and delete all files + $this->deleteFileByFilename( $file['filePath'] ); + } + } + + /** + * Do login + */ + protected function doLogin( $user = 'sysop' ) { + $user = self::$users['uploader']; + + $params = array( + 'action' => 'login', + 'lgname' => $user->username, + 'lgpassword' => $user->password + ); + list( $result, , $session ) = $this->doApiRequest( $params ); + $token = $result['login']['token']; + + $params = array( + 'action' => 'login', + 'lgtoken' => $token, + 'lgname' => $user->username, + 'lgpassword' => $user->password + ); + list( $result, , $session ) = $this->doApiRequest( $params, $session ); + return $session; + } + + /** + * uploads a file: + */ + public function uploadFile( $file ){ + global $wgUser; + // get a session object + $session = $this->doLogin(); + // Update the global user: + $wgUser = self::$users['uploader']->getUser(); + + // Upload the media file: + $fileName = basename( $file['filePath'] ); + + // remove if already in thd db: + $this->deleteFileByFileName( $fileName ); + $this->deleteFileByContent( $file['filePath'] ); + + if ( !$this->fakeUploadFile( 'file', $fileName, $file['mime'], $file['filePath'] ) ) { + $this->markTestIncomplete( "Couldn't upload file!\n" ); + } + + $params = array( + 'action' => 'upload', + 'filename' => $fileName, + 'file' => 'dummy content', + 'comment' => 'dummy comment', + 'text' => "This is the page text for $fileName", + // This uploadFile function supports video tests not a test upload warnings + 'ignorewarnings' => true + ); + + try{ + list( $result, , ) = $this->doApiRequestWithToken( $params, $session ); + } catch( Exception $e ) { + // Could not upload mark test that called uploadFile as incomplete + $this->markTestIncomplete( $e->getMessage() ); + } + + return $result; + + } + +} diff --git a/extensions/TimedMediaHandler/tests/phpunit/README b/extensions/TimedMediaHandler/tests/phpunit/README new file mode 100644 index 00000000..dd9eba39 --- /dev/null +++ b/extensions/TimedMediaHandler/tests/phpunit/README @@ -0,0 +1,11 @@ + +Some TimedMediaHandler testing notes: + + We have a base class ApiTestCaseVideoUpload that establishes the uploads. Actual uploading + is tested in the core uploading support so we focus these tests on timed media handler functionality + + We sometimes combine a few tests because fixtures don't play well with dataProvider + since the mediawiki test framework nukes the db every time you run the next test in + a given dataProvider data set. + + diff --git a/extensions/TimedMediaHandler/tests/phpunit/TestApiUploadVideo.php b/extensions/TimedMediaHandler/tests/phpunit/TestApiUploadVideo.php new file mode 100644 index 00000000..ebc38ddf --- /dev/null +++ b/extensions/TimedMediaHandler/tests/phpunit/TestApiUploadVideo.php @@ -0,0 +1,34 @@ +uploadFile( $file ); + + // Run asserts + $this->assertTrue( isset( $result['upload'] ) ); + $this->assertEquals( 'Success', $result['upload']['result'] ); + $this->assertEquals( filesize( $file['filePath'] ), ( int )$result['upload']['imageinfo']['size'] ); + $this->assertEquals( $file['mime'], $result['upload']['imageinfo']['mime'] ); + + } + +} diff --git a/extensions/TimedMediaHandler/tests/phpunit/TestOggHandler.php b/extensions/TimedMediaHandler/tests/phpunit/TestOggHandler.php new file mode 100644 index 00000000..3c058974 --- /dev/null +++ b/extensions/TimedMediaHandler/tests/phpunit/TestOggHandler.php @@ -0,0 +1,67 @@ +handler = new OggHandlerTMH; + } + + /** + * @dataProvider providerGetCommonMetaArray + * @param $filename String name of file + * @param $expected Array + */ + function testGetCommonMetaArray( $filename, $expected ) { + $testFile = $this->dataFile( $filename, 'application/ogg' ); + $this->assertEquals( $expected, $this->handler->getCommonMetaArray( $testFile ) ); + } + + function providerGetCommonMetaArray() { + return array( + array( 'test5seconds.electricsheep.300x400.ogv', + array( + 'Software' => array( 'Lavf53.21.1' ), + 'ObjectName' => array( 'Electric Sheep' ), + 'UserComment' => array( '🐑' ) + ) + ), + array( 'doubleTag.oga', + array( + 'Artist' => array( 'Brian', 'Bawolff' ), + 'Software' => array( 'Lavf55.10.2' ) + ) + ), + array( 'broken-file.ogg', + array() + ), + ); + } + + /** + * @dataProvider providerGetWebType + * @param $filename String name of file + * @param $expected String Mime type (including codecs) + */ + function testGetWebType( $filename, $expected ) { + $testFile = $this->dataFile( $filename, 'application/ogg' ); + $this->assertEquals( $expected, $this->handler->getWebType( $testFile ) ); + } + + function providerGetWebType() { + return array( + array( 'test5seconds.electricsheep.300x400.ogv', 'video/ogg; codecs="theora"' ), + array( 'doubleTag.oga', 'audio/ogg; codecs="vorbis"' ), + // XXX: This behaviour is somewhat questionable. It perhaps should be + // application/ogg in this case. + array( 'broken-file.ogg', 'audio/ogg' ), + ); + } + +} diff --git a/extensions/TimedMediaHandler/tests/phpunit/TestTimeParsing.php b/extensions/TimedMediaHandler/tests/phpunit/TestTimeParsing.php new file mode 100644 index 00000000..b4ec6ba9 --- /dev/null +++ b/extensions/TimedMediaHandler/tests/phpunit/TestTimeParsing.php @@ -0,0 +1,42 @@ +assertEquals( TimedMediaHandler::seconds2npt( 100 ), '0:1:40' ); + $this->assertEquals( TimedMediaHandler::seconds2npt( 0 ), '0:0:0' ); + $this->assertEquals( TimedMediaHandler::seconds2npt( 3601 ), '1:0:1' ); + + // Test failures: + $this->assertEquals( TimedMediaHandler::seconds2npt( 'foo' ), false ); + $this->assertEquals( TimedMediaHandler::seconds2npt( -1 ), false ); + } + + /** + * Test time parsing to seconds + */ + function testParseTimeString() { + // Some time conversions: + $this->assertEquals( TimedMediaHandler::parseTimeString( 100 ), 100 ); + $this->assertEquals( TimedMediaHandler::parseTimeString( '1:0:0' ), 3600 ); + $this->assertEquals( TimedMediaHandler::parseTimeString( -1 ), 0 ); + // Test longer than duration check ( should return time -1 ) + $this->assertEquals( TimedMediaHandler::parseTimeString( 10, 9 ), 8 ); + + // Test failures: + $this->assertEquals( TimedMediaHandler::parseTimeString( '1:1:1:1' ), false ); + $this->assertEquals( TimedMediaHandler::parseTimeString( 'abc' ), false ); + + } +} \ No newline at end of file diff --git a/extensions/TimedMediaHandler/tests/phpunit/TestTimedMediaHandler.php b/extensions/TimedMediaHandler/tests/phpunit/TestTimedMediaHandler.php new file mode 100644 index 00000000..2ca350a0 --- /dev/null +++ b/extensions/TimedMediaHandler/tests/phpunit/TestTimedMediaHandler.php @@ -0,0 +1,47 @@ +handler = new TimedMediaHandler; + parent::setUp(); + } + + /** + * @dataProvider providerParseParamString + * @param $str String a thumbnail parameter string + * @param $expected Array Expected thumbnailing parameters + */ + function testParseParamString( $str, $expected ) { + $result = $this->handler->parseParamString( $str ); + $this->assertEquals( $result, $expected ); + } + + function providerParseParamString() { + return array( + array( + 'mid', + array(), + ), + array( + '220px-', + array( 'width' => 220 ), + ), + array( + 'seek=30', + array( 'thumbtime' => 30.0 ), + ), + array( + 'seek=15.72', + array( 'thumbtime' => 15.72 ), + ), + array( + '180px-seek=15', + array( 'thumbtime' => 15, 'width' => 180 ), + ), + ); + + } +} diff --git a/extensions/TimedMediaHandler/tests/phpunit/TestTimedMediaTransformOutput.php b/extensions/TimedMediaHandler/tests/phpunit/TestTimedMediaTransformOutput.php new file mode 100644 index 00000000..9bcc89fb --- /dev/null +++ b/extensions/TimedMediaHandler/tests/phpunit/TestTimedMediaTransformOutput.php @@ -0,0 +1,142 @@ +repo ); + $reflectionProperty = $reflection->getProperty( 'transformVia404' ); + $reflectionProperty->setAccessible( true ); + $reflectionProperty->setValue( $this->repo, true ); + + $this->setMWGlobals( 'wgMinimumVideoPlayerSize', '400' ); + } + + /** + * @param $width int The requested width of the thumbnail + * @param $minVideoSize int The min width a non-pop up video is acceptable + * @param $expectPopup boolean Do we expect a pop up video + * + * @dataProvider providerIsPopUp + */ + function testIsPopUp( $width, $minVideoSize, $expectPopup ) { + $this->setMwGlobals( 'wgMinimumVideoPlayerSize', $minVideoSize ); + + // Note this file has a width of 400px and a height of 300px + $file = $this->dataFile( 'test5seconds.electricsheep.300x400.ogv', 'application/ogg' ); + $thumbnail = $file->transform( array( 'width' => $width ) ); + $this->assertTrue( $thumbnail && !$thumbnail->isError() ); + + $reflection = new ReflectionClass( $thumbnail ); + $reflMethod = $reflection->getMethod( 'useImagePopUp' ); + $reflMethod->setAccessible( true ); + + $actual = $reflMethod->invoke( $thumbnail ); + $this->assertEquals( $actual, $expectPopup ); + + } + + function providerIsPopUp() { + return array( + array( 400, 800, false ), + array( 300, 800, true ), + array( 300, 200, false ), + array( 300, 300, false ) + ); + } + + /** + * @param $thumbWidth int Requested width + * @param $sources array + * @param $sortedSources array + * @dataProvider providerSortMediaByBandwidth + */ + function testSortMediaByBandwidth( $thumbWidth, $sources, $sortedSources ) { + $params = array( + 'width' => $thumbWidth, + 'height' => $thumbWidth * 9 / 16, + 'isVideo' => true, + 'fillwindow' => false, + 'file' => new FakeDimensionFile( array( 1820, 1024 ) ) + ); + $this->thumbObj = new TimedMediaTransformOutput( $params ); + + $reflection = new ReflectionClass( $this->thumbObj ); + $this->sortMethod = $reflection->getMethod( 'sortMediaByBandwidth' ); + $this->sortMethod->setAccessible( true ); + + usort( $sources, array( $this, 'callSortMethodHelper' ) ); + $this->assertEquals( $sortedSources, $sources ); + } + + public function callSortMethodHelper( $a, $b ) { + return $this->sortMethod->invoke( $this->thumbObj, $a, $b ); + } + + + function providerSortMediaByBandwidth() { + return array( + array( + 600, + array( + array( 'width' => 1000, 'bandwidth' => 2000 ), + array( 'width' => 1000, 'bandwidth' => 7000 ), + array( 'width' => 1000, 'bandwidth' => 1000 ), + ), + array( + array( 'width' => 1000, 'bandwidth' => 1000 ), + array( 'width' => 1000, 'bandwidth' => 2000 ), + array( 'width' => 1000, 'bandwidth' => 7000 ), + ), + ), + array( + 600, + array( + array( 'width' => 200, 'bandwidth' => 2000 ), + array( 'width' => 1000, 'bandwidth' => 7000 ), + array( 'width' => 200, 'bandwidth' => 1000 ), + ), + array( + array( 'width' => 1000, 'bandwidth' => 7000 ), + array( 'width' => 200, 'bandwidth' => 1000 ), + array( 'width' => 200, 'bandwidth' => 2000 ), + ), + ), + array( + /* Pop up viewer in this case */ + 100, + array( + array( 'width' => 700, 'bandwidth' => 2000 ), + array( 'width' => 1000, 'bandwidth' => 7000 ), + array( 'width' => 700, 'bandwidth' => 1000 ), + ), + array( + array( 'width' => 1000, 'bandwidth' => 7000 ), + array( 'width' => 700, 'bandwidth' => 1000 ), + array( 'width' => 700, 'bandwidth' => 2000 ), + ), + ), + array( + 600, + array( + array( 'width' => 700, 'bandwidth' => 2000 ), + array( 'width' => 800, 'bandwidth' => 7000 ), + array( 'width' => 1000, 'bandwidth' => 1000 ), + ), + array( + array( 'width' => 1000, 'bandwidth' => 1000 ), + array( 'width' => 700, 'bandwidth' => 2000 ), + array( 'width' => 800, 'bandwidth' => 7000 ), + ), + ), + ); + } +} diff --git a/extensions/TimedMediaHandler/tests/phpunit/TestVideoThumbnail.php b/extensions/TimedMediaHandler/tests/phpunit/TestVideoThumbnail.php new file mode 100644 index 00000000..b3c9ef87 --- /dev/null +++ b/extensions/TimedMediaHandler/tests/phpunit/TestVideoThumbnail.php @@ -0,0 +1,80 @@ +uploadFile( $file); + + // Do a API request and check for valid thumbnails: + $fileName = basename( $file['filePath'] ); + $params = array( + 'action' => 'query', + 'titles' => 'File:' . $fileName, + 'prop' => 'imageinfo', + 'iiprop' => "url|size|thumbmime", + ); + + // Do a request for a small ( 200px ) thumbnail + list($result,,) = $this->doApiRequest( + array_merge( $params, array( + 'iiurlwidth' => '200' + ) + ) + ); + + // Check The thumbnail output: + $this->assertTrue( isset( $result['query'] ) ); + + $page = current( $result['query']['pages'] ); + $this->assertTrue( isset( $page['imageinfo'] ) ); + + $imageInfo = current( $page['imageinfo'] ); + + // Make sure we got a 200 wide pixel image: + $this->assertEquals( 200, ( int )$imageInfo['thumbwidth'] ); + + // Thumbnails should be image/jpeg: + $this->assertEquals( 'image/jpeg', $imageInfo['thumbmime'] ); + + // Make sure the thumbnail url is valid and the correct size ( assuming php has getimagesize function) + if( function_exists( 'getimagesize' ) ){ + list($width ,,,) = getimagesize ( $imageInfo[ 'thumburl'] ); + $this->assertEquals( 200, $width ); + } + + /** + * We combine tests because fixtures don't play well with dataProvider + * see README for more info + */ + + // Test a larger thumbnail with 1 second time offset + list( $result,, ) = $this->doApiRequest( + array_merge( $params, array( + 'iiurlwidth' => '600', + 'iiurlparam' => '1' + ) + ) + ); + $page = current( $result['query']['pages'] ); + $imageInfo = current( $page['imageinfo'] ); + // Thumb should max out at source size ( no upscale ) + $targetWidth = ( ( int )$file['width'] < 600 ) ? ( int )$file['width'] : 600; + $this->assertEquals( $targetWidth, ( int )$imageInfo['thumbwidth'] ); + if( function_exists( 'getimagesize' ) ){ + list( $srcImageWidth ,,,) = getimagesize ( $imageInfo[ 'thumburl'] ); + $this->assertEquals( $targetWidth, $srcImageWidth ); + } + } +} diff --git a/extensions/TimedMediaHandler/tests/phpunit/TestVideoTranscode.php b/extensions/TimedMediaHandler/tests/phpunit/TestVideoTranscode.php new file mode 100644 index 00000000..af0e6f1f --- /dev/null +++ b/extensions/TimedMediaHandler/tests/phpunit/TestVideoTranscode.php @@ -0,0 +1,121 @@ +uploadFile( $file ); + + // Check for derivatives ( should trigger adding jobs ) + $fileName = basename( $file['filePath'] ); + $params = array( + 'action' => 'query', + 'titles' => 'File:' . $fileName, + 'prop' => 'videoinfo', + 'viprop' => "derivatives", + ); + list($result,,) = $this->doApiRequest( $params ); + + // Get the $derivatives: + $derivatives = $this->getDerivativesFromResult( $result ); + // Only the "source" asset will be present at first: + $source = current( $derivatives ); + + // Check that the source matches the api bandwidth property: + $this->assertEquals( $file['bandwidth'], $source['bandwidth'] ); + + // Check if the transcode jobs were added: + // get results: query jobs table + $db = wfGetDB( DB_MASTER ); + $res = $db->select( 'transcode', '*', array( + 'transcode_image_name' => ucfirst( $fileName ) + ) ); + // Make sure we target at least one ogg and one webm: + $hasOgg = $hasWebM = false; + $targetEncodes = array(); + foreach( $res as $row ){ + $codec = WebVideoTranscode::$derivativeSettings[ $row->transcode_key ]['videoCodec']; + if( $codec == 'theora' ){ + $hasOgg = true; + } + if( $codec == 'vp8' ){ + $hasWebM = true; + } + $targetEncodes[ $row->transcode_key ] = $row; + }; + // Make sure we have ogg and webm: + $this->assertTrue( $hasOgg && $hasWebM ); + + // Now run the transcode job queue + $this->runTranscodeJobs(); + + $res = $db->select( 'transcode', '*', array( + 'transcode_image_name' => ucfirst( $fileName ) + ) ); + + // Now check if the derivatives were created: + list($result,,) = $this->doApiRequest( $params ); + $derivatives = $this->getDerivativesFromResult( $result ); + + // Check that every requested encode was encoded: + foreach( $targetEncodes as $transcodeKey => $row ){ + $targetEncodeFound = false; + foreach( $derivatives as $derv ){ + // The transcode key is always the last part of the file name: + if( substr( $derv['src'], -1 * strlen( $transcodeKey ) ) == $transcodeKey ){ + $targetEncodeFound = true; + } + } + // Test that target encode was found: + $this->assertTrue( $targetEncodeFound ); + } + + } + + // Run Transcode job + function runTranscodeJobs(){ + $dbw = wfGetDB( DB_MASTER ); + $type = 'webVideoTranscode'; + // Set the condition to only run the webVideoTranscode + $conds = array( "job_cmd" => $type ); + + while ( $dbw->selectField( 'job', 'job_id', $conds, 'runJobs.php' ) ) { + for ( ; ; ) { + $job = Job::pop_type( $type ); + if ( !$job ) + break; + + wfWaitForSlaves( 5 ); + $t = microtime( true ); + $offset = $job->id; + $status = $job->run(); + $t = microtime( true ) - $t; + $timeMs = intval( $t * 1000 ); + } + } + } + + function getDerivativesFromResult( $result ){ + // Only the source should be listed initially: + $this->assertTrue( isset( $result['query']['pages'] ) ); + $page = current( $result['query']['pages'] ); + + $videoInfo = current( $page['videoinfo'] ); + $this->assertTrue( isset( $videoInfo['derivatives'] ) ); + + return $videoInfo['derivatives']; + } +} diff --git a/extensions/TimedMediaHandler/tests/phpunit/TestWebMHandler.php b/extensions/TimedMediaHandler/tests/phpunit/TestWebMHandler.php new file mode 100644 index 00000000..2d73f46d --- /dev/null +++ b/extensions/TimedMediaHandler/tests/phpunit/TestWebMHandler.php @@ -0,0 +1,52 @@ +handler = new WebMHandler; + } + + /** + * @dataProvider providerGetStreamTypes + * @param $filename String name of file + * @param $expected array List of codecs in file + */ + function testGetStreamTypes( $filename, $expected ) { + $testFile = $this->dataFile( $filename, 'video/webm' ); + $this->assertEquals( $expected, $this->handler->getStreamTypes( $testFile ) ); + } + + function providerGetStreamTypes() { + return array( + array( 'shuttle10seconds.1080x608.webm', array( 'VP8' ) ), + array( 'VP9-tractor.webm', array( 'VP9' ) ), + array( 'bear-vp9-opus.webm', array( 'Opus', 'VP9' ) ) + ); + } + + + /** + * @dataProvider providerGetWebType + * @param $filename String name of file + * @param $expected String Mime type + */ + function testGetWebType( $filename, $expected ) { + $testFile = $this->dataFile( $filename, 'video/webm' ); + $this->assertEquals( $expected, $this->handler->getWebType( $testFile ) ); + } + + function providerGetWebType() { + return array( + array( 'shuttle10seconds.1080x608.webm', 'video/webm; codecs="vp8"' ), + array( 'VP9-tractor.webm', 'video/webm; codecs="vp9"' ), + array( 'bear-vp9-opus.webm', 'video/webm; codecs="opus, vp9"' ) + ); + } +} diff --git a/extensions/TimedMediaHandler/tests/phpunit/media/README b/extensions/TimedMediaHandler/tests/phpunit/media/README new file mode 100644 index 00000000..b21871ea --- /dev/null +++ b/extensions/TimedMediaHandler/tests/phpunit/media/README @@ -0,0 +1,38 @@ +doubleTag.oga and broken-file.ogg were created by Brian Wolff and is released into the public domain + +VP9-tractor.webm is from http://base-n.de/webm/VP9%20Sample.html and was encoded from a public domain +file located at http://media.xiph.org/video/derf/y4m/ + +bear-vp9-opus.webm comes from the chromnium project at the url +http://code.metager.de/source/raw/chromium/media/test/data/bear-vp9-opus.webm +which has the following copyright notice: + +1 // Copyright 2014 The Chromium Authors. All rights reserved. +2 // +3 // Redistribution and use in source and binary forms, with or without +4 // modification, are permitted provided that the following conditions are +5 // met: +6 // +7 // * Redistributions of source code must retain the above copyright +8 // notice, this list of conditions and the following disclaimer. +9 // * Redistributions in binary form must reproduce the above +10 // copyright notice, this list of conditions and the following disclaimer +11 // in the documentation and/or other materials provided with the +12 // distribution. +13 // * Neither the name of Google Inc. nor the names of its +14 // contributors may be used to endorse or promote products derived from +15 // this software without specific prior written permission. +16 // +17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + diff --git a/extensions/TimedMediaHandler/tests/phpunit/media/VP9-tractor.webm b/extensions/TimedMediaHandler/tests/phpunit/media/VP9-tractor.webm new file mode 100644 index 00000000..bbe96f5d Binary files /dev/null and b/extensions/TimedMediaHandler/tests/phpunit/media/VP9-tractor.webm differ diff --git a/extensions/TimedMediaHandler/tests/phpunit/media/bear-vp9-opus.webm b/extensions/TimedMediaHandler/tests/phpunit/media/bear-vp9-opus.webm new file mode 100644 index 00000000..80355cfa Binary files /dev/null and b/extensions/TimedMediaHandler/tests/phpunit/media/bear-vp9-opus.webm differ diff --git a/extensions/TimedMediaHandler/tests/phpunit/media/broken-file.ogg b/extensions/TimedMediaHandler/tests/phpunit/media/broken-file.ogg new file mode 100644 index 00000000..c9fc4fd5 Binary files /dev/null and b/extensions/TimedMediaHandler/tests/phpunit/media/broken-file.ogg differ diff --git a/extensions/TimedMediaHandler/tests/phpunit/media/doubleTag.oga b/extensions/TimedMediaHandler/tests/phpunit/media/doubleTag.oga new file mode 100644 index 00000000..f7fac168 Binary files /dev/null and b/extensions/TimedMediaHandler/tests/phpunit/media/doubleTag.oga differ diff --git a/extensions/TimedMediaHandler/tests/phpunit/media/shuttle10seconds.1080x608.webm b/extensions/TimedMediaHandler/tests/phpunit/media/shuttle10seconds.1080x608.webm new file mode 100644 index 00000000..a7b865b1 Binary files /dev/null and b/extensions/TimedMediaHandler/tests/phpunit/media/shuttle10seconds.1080x608.webm differ diff --git a/extensions/TimedMediaHandler/tests/phpunit/media/test5seconds.electricsheep.300x400.ogv b/extensions/TimedMediaHandler/tests/phpunit/media/test5seconds.electricsheep.300x400.ogv new file mode 100644 index 00000000..a73ec5bf Binary files /dev/null and b/extensions/TimedMediaHandler/tests/phpunit/media/test5seconds.electricsheep.300x400.ogv differ diff --git a/extensions/TimedMediaHandler/version b/extensions/TimedMediaHandler/version new file mode 100644 index 00000000..20e3a345 --- /dev/null +++ b/extensions/TimedMediaHandler/version @@ -0,0 +1,5 @@ +TimedMediaHandler: 7cab54e2e482d51df69e4b9c880182b23cabf0bc + +2016-01-27T22:15:53 + +7cab54e -- cgit v1.2.3