/tr> 22
+      keepSpecialComments: 0
23
+    }))
24
+    .pipe(rename({ extname: '.min.css' }))
25
+    .pipe(gulp.dest('./www/css/'))
26
+    .on('end', done);
27
+});
28
+
29
+gulp.task('watch', function() {
30
+  gulp.watch(paths.sass, ['sass']);
31
+});
32
+
33
+gulp.task('install', ['git-check'], function() {
34
+  return bower.commands.install()
35
+    .on('log', function(data) {
36
+      gutil.log('bower', gutil.colors.cyan(data.id), data.message);
37
+    });
38
+});
39
+
40
+gulp.task('git-check', function(done) {
41
+  if (!sh.which('git')) {
42
+    console.log(
43
+      '  ' + gutil.colors.red('Git is not installed.'),
44
+      '\n  Git, the version control system, is required to download Ionic.',
45
+      '\n  Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.',
46
+      '\n  Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.'
47
+    );
48
+    process.exit(1);
49
+  }
50
+  done();
51
+});

+ 83 - 0
hooks/README.md

@@ -0,0 +1,83 @@
1
+<!--
2
+#
3
+# Licensed to the Apache Software Foundation (ASF) under one
4
+# or more contributor license agreements.  See the NOTICE file
5
+# distributed with this work for additional information
6
+# regarding copyright ownership.  The ASF licenses this file
7
+# to you under the Apache License, Version 2.0 (the
8
+# "License"); you may not use this file except in compliance
9
+# with the License.  You may obtain a copy of the License at
10
+#
11
+# http://www.apache.org/licenses/LICENSE-2.0
12
+#
13
+# Unless required by applicable law or agreed to in writing,
14
+# software distributed under the License is distributed on an
15
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+#  KIND, either express or implied.  See the License for the
17
+# specific language governing permissions and limitations
18
+# under the License.
19
+#
20
+-->
21
+# Cordova Hooks
22
+
23
+This directory may contain scripts used to customize cordova commands. This
24
+directory used to exist at `.cordova/hooks`, but has now been moved to the
25
+project root. Any scripts you add to these directories will be executed before
26
+and after the commands corresponding to the directory name. Useful for
27
+integrating your own build systems or integrating with version control systems.
28
+
29
+__Remember__: Make your scripts executable.
30
+
31
+## Hook Directories
32
+The following subdirectories will be used for hooks:
33
+
34
+    after_build/
35
+    after_compile/
36
+    after_docs/
37
+    after_emulate/
38
+    after_platform_add/
39
+    after_platform_rm/
40
+    after_platform_ls/
41
+    after_plugin_add/
42
+    after_plugin_ls/
43
+    after_plugin_rm/
44
+    after_plugin_search/
45
+    after_prepare/
46
+    after_run/
47
+    after_serve/
48
+    before_build/
49
+    before_compile/
50
+    before_docs/
51
+    before_emulate/
52
+    before_platform_add/
53
+    before_platform_rm/
54
+    before_platform_ls/
55
+    before_plugin_add/
56
+    before_plugin_ls/
57
+    before_plugin_rm/
58
+    before_plugin_search/
59
+    before_prepare/
60
+    before_run/
61
+    before_serve/
62
+    pre_package/ <-- Windows 8 and Windows Phone only.
63
+
64
+## Script Interface
65
+
66
+All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables:
67
+
68
+* CORDOVA_VERSION - The version of the Cordova-CLI.
69
+* CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios).
70
+* CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer)
71
+* CORDOVA_HOOK - Path to the hook that is being executed.
72
+* CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate)
73
+
74
+If a script returns a non-zero exit code, then the parent cordova command will be aborted.
75
+
76
+
77
+## Writing hooks
78
+
79
+We highly recommend writting your hooks using Node.js so that they are
80
+cross-platform. Some good examples are shown here:
81
+
82
+[http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/)
83
+

+ 94 - 0
hooks/after_prepare/010_add_platform_class.js

@@ -0,0 +1,94 @@
1
+#!/usr/bin/env node
2
+
3
+// Add Platform Class
4
+// v1.0
5
+// Automatically adds the platform class to the body tag
6
+// after the `prepare` command. By placing the platform CSS classes
7
+// directly in the HTML built for the platform, it speeds up
8
+// rendering the correct layout/style for the specific platform
9
+// instead of waiting for the JS to figure out the correct classes.
10
+
11
+var fs = require('fs');
12
+var path = require('path');
13
+
14
+var rootdir = process.argv[2];
15
+
16
+function addPlatformBodyTag(indexPath, platform) {
17
+  // add the platform class to the body tag
18
+  try {
19
+    var platformClass = 'platform-' + platform;
20
+    var cordovaClass = 'platform-cordova platform-webview';
21
+
22
+    var html = fs.readFileSync(indexPath, 'utf8');
23
+
24
+    var bodyTag = findBodyTag(html);
25
+    if(!bodyTag) return; // no opening body tag, something's wrong
26
+
27
+    if(bodyTag.indexOf(platformClass) > -1) return; // already added
28
+
29
+    var newBodyTag = bodyTag;
30
+
31
+    var classAttr = findClassAttr(bodyTag);
32
+    if(classAttr) {
33
+      // body tag has existing class attribute, add the classname
34
+      var endingQuote = classAttr.substring(classAttr.length-1);
35
+      var newClassAttr = classAttr.substring(0, classAttr.length-1);
36
+      newClassAttr += ' ' + platformClass + ' ' + cordovaClass + endingQuote;
37
+      newBodyTag = bodyTag.replace(classAttr, newClassAttr);
38
+
39
+    } else {
40
+      // add class attribute to the body tag
41
+      newBodyTag = bodyTag.replace('>', ' class="' + platformClass + ' ' + cordovaClass + '">');
42
+    }
43
+
44
+    html = html.replace(bodyTag, newBodyTag);
45
+
46
+    fs.writeFileSync(indexPath, html, 'utf8');
47
+
48
+    process.stdout.write('add to body class: ' + platformClass + '\n');
49
+  } catch(e) {
50
+    process.stdout.write(e);
51
+  }
52
+}
53
+
54
+function findBodyTag(html) {
55
+  // get the body tag
56
+  try{
57
+    return html.match(/<body(?=[\s>])(.*?)>/gi)[0];
58
+  }catch(e){}
59
+}
60
+
61
+function findClassAttr(bodyTag) {
62
+  // get the body tag's class attribute
63
+  try{
64
+    return bodyTag.match(/ class=["|'](.*?)["|']/gi)[0];
65
+  }catch(e){}
66
+}
67
+
68
+if (rootdir) {
69
+
70
+  // go through each of the platform directories that have been prepared
71
+  var platforms = (process.env.CORDOVA_PLATFORMS ? process.env.CORDOVA_PLATFORMS.split(',') : []);
72
+
73
+  for(var x=0; x<platforms.length; x++) {
74
+    // open up the index.html file at the www root
75
+    try {
76
+      var platform = platforms[x].trim().toLowerCase();
77
+      var indexPath;
78
+
79
+      if(platform == 'android') {
80
+        indexPath = path.join('platforms', platform, 'assets', 'www', 'index.html');
81
+      } else {
82
+        indexPath = path.join('platforms', platform, 'www', 'index.html');
83
+      }
84
+
85
+      if(fs.existsSync(indexPath)) {
86
+        addPlatformBodyTag(indexPath, platform);
87
+      }
88
+
89
+    } catch(e) {
90
+      process.stdout.write(e);
91
+    }
92
+  }
93
+
94
+}

+ 4 - 0
ionic.project

@@ -0,0 +1,4 @@
1
+{
2
+  "name": "lively-app",
3
+  "app_id": ""
4
+}

File diff suppressed because it is too large
+ 3236 - 0
package-lock.json


+ 29 - 0
package.json

@@ -0,0 +1,29 @@
1
+{
2
+  "name": "lively-app",
3
+  "version": "1.1.1",
4
+  "description": "lively-app: An Ionic project",
5
+  "dependencies": {
6
+    "gulp": "^3.5.6",
7
+    "gulp-sass": "^2.0.4",
8
+    "gulp-concat": "^2.2.0",
9
+    "gulp-minify-css": "^0.3.0",
10
+    "gulp-rename": "^1.2.0"
11
+  },
12
+  "devDependencies": {
13
+    "bower": "^1.3.3",
14
+    "gulp-util": "^2.2.14",
15
+    "shelljs": "^0.3.0"
16
+  },
17
+  "cordovaPlugins": [
18
+    "cordova-plugin-device",
19
+    "cordova-plugin-console",
20
+    "cordova-plugin-whitelist",
21
+    "cordova-plugin-splashscreen",
22
+    "cordova-plugin-statusbar",
23
+    "ionic-plugin-keyboard"
24
+  ],
25
+  "cordovaPlatforms": [
26
+    "android",
27
+    "ios"
28
+  ]
29
+}

BIN
resources/android/icon/drawable-hdpi-icon.png


BIN
resources/android/icon/drawable-ldpi-icon.png


BIN
resources/android/icon/drawable-mdpi-icon.png


BIN
resources/android/icon/drawable-xhdpi-icon.png


BIN
resources/android/icon/drawable-xxhdpi-icon.png


BIN
resources/android/icon/drawable-xxxhdpi-icon.png


BIN
resources/android/splash/drawable-land-hdpi-screen.png


BIN
resources/android/splash/drawable-land-ldpi-screen.png


BIN
resources/android/splash/drawable-land-mdpi-screen.png


BIN
resources/android/splash/drawable-land-xhdpi-screen.png


BIN
resources/android/splash/drawable-land-xxhdpi-screen.png


BIN
resources/android/splash/drawable-land-xxxhdpi-screen.png


BIN
resources/android/splash/drawable-port-hdpi-screen.png


BIN
resources/android/splash/drawable-port-ldpi-screen.png


BIN
resources/android/splash/drawable-port-mdpi-screen.png


BIN
resources/android/splash/drawable-port-xhdpi-screen.png


BIN
resources/android/splash/drawable-port-xxhdpi-screen.png


BIN
resources/android/splash/drawable-port-xxxhdpi-screen.png


BIN
resources/icon.png


BIN
resources/ios/icon/iTunesArtwork.png


BIN
resources/ios/icon/iTunesArtwork@2x.png


BIN
resources/ios/icon/icon-40.png


BIN
resources/ios/icon/icon-40@2x.png


BIN
resources/ios/icon/icon-40@3x.png


BIN
resources/ios/icon/icon-50.png


BIN
resources/ios/icon/icon-50@2x.png


BIN
resources/ios/icon/icon-60.png


BIN
resources/ios/icon/icon-60@2x.png


BIN
resources/ios/icon/icon-60@3x.png


BIN
resources/ios/icon/icon-72.png


BIN
resources/ios/icon/icon-72@2x.png


BIN
resources/ios/icon/icon-76.png


BIN
resources/ios/icon/icon-76@2x.png


BIN
resources/ios/icon/icon-83.5@2x.png


BIN
resources/ios/icon/icon-Small.png


BIN
resources/ios/icon/icon-Small@2x.png


BIN
resources/ios/icon/icon-small-1.png


BIN
resources/ios/icon/icon-small@2x-1.png


BIN
resources/ios/icon/icon-small@3x.png


BIN
resources/ios/icon/icon.png


BIN
resources/ios/icon/icon@2x.png


BIN
resources/ios/splash/Default-568h@2x~iphone.png


BIN
resources/ios/splash/Default-667h.png


BIN
resources/ios/splash/Default-736h.png


BIN
resources/ios/splash/Default-Landscape-736h.png


BIN
resources/ios/splash/Default-Landscape@2x~ipad.png


BIN
resources/ios/splash/Default-Landscape~ipad.png


BIN
resources/ios/splash/Default-Portrait@2x~ipad.png


BIN
resources/ios/splash/Default-Portrait~ipad.png


BIN
resources/ios/splash/Default@2x~iphone.png


BIN
resources/ios/splash/Default~iphone.png


BIN
resources/splash.png


+ 22 - 0
scss/ionic.app.scss

@@ -0,0 +1,22 @@
1
+/*
2
+To customize the look and feel of Ionic, you can override the variables
3
+in ionic's _variables.scss file.
4
+
5
+For example, you might change some of the default colors:
6
+
7
+$light:                           #fff !default;
8
+$stable:                          #f8f8f8 !default;
9
+$positive:                        #387ef5 !default;
10
+$calm:                            #11c1f3 !default;
11
+$balanced:                        #33cd5f !default;
12
+$energized:                       #ffc900 !default;
13
+$assertive:                       #ef473a !default;
14
+$royal:                           #886aea !default;
15
+$dark:                            #444 !default;
16
+*/
17
+
18
+// The path for our ionicons font files, relative to the built CSS in www/css
19
+$ionicons-font-path: "../lib/ionic/fonts" !default;
20
+@import "www/lib/ionic/scss/ionic";
21
+@import "www/lib/font-awesome/scss/font-awesome";  
22
+

BIN
www.zip


File diff suppressed because it is too large
+ 12007 - 0
www/css/ionic.app.css


File diff suppressed because it is too large
+ 1 - 0
www/css/ionic.app.min.css


BIN
www/css/lively-bg.png


+ 138 - 0
www/css/style.css

@@ -0,0 +1,138 @@
1
+/* Empty. Add your own CSS if you like */
2
+.custom-icon .icon-badge {
3
+  position: absolute;
4
+  top: 3px;
5
+  right: 0px;
6
+  font-size: 8px;
7
+  padding: 2px 6px;
8
+}
9
+ion-content iframe {
10
+    width:100%;
11
+}
12
+.facebook {
13
+    background-color:#3b5998;
14
+    color:#fff;
15
+}
16
+.social-button:hover {
17
+    color:#fff;
18
+}
19
+.ig {
20
+    background-color:#517fa4;
21
+    color:#fff;
22
+}
23
+.center-block {
24
+    display:block;
25
+    margin:0 auto;
26
+
27
+    width:100%;
28
+}
29
+.center-block img {
30
+}
31
+.lively-bg {
32
+    background: url("./lively-bg.png") no-repeat top left;
33
+    background-size:cover;
34
+}
35
+.lively-bg .input-label {
36
+    color:#fff;
37
+    font-weight:bold;
38
+}
39
+.lively-bg .item-input {
40
+    background:none;
41
+    color:#fff;
42
+    border-top:none;
43
+}
44
+.img-padding {
45
+    padding:10px 20px;
46
+}
47
+.lively-bg .item-input input[type=text],
48
+.lively-bg .item-input input[type=password] 
49
+{
50
+    color:#fff;
51
+
52
+}
53
+.lively-bg .item {
54
+    background:none;
55
+}
56
+.label-content {
57
+    color:#fff;
58
+    font-size:larger;
59
+    font-weight:bold;
60
+}
61
+.borderless {
62
+    border:none;
63
+}
64
+.no-padding a.item-content {
65
+    padding: 0px;
66
+    border: none;
67
+}
68
+
69
+ion-item.no-padding.item {
70
+    border: none;
71
+}
72
+
73
+.list.card {
74
+    box-shadow: none;
75
+	margin-right:0px;
76
+	margin-left:0px;
77
+}
78
+.category {
79
+	background-color:#000;
80
+	color:#fff;
81
+	display:inline-block;
82
+	padding:5px 10px;
83
+}
84
+h2.title {
85
+    margin-top: 10px;
86
+    font-size: larger;
87
+}
88
+ion-content.lively.scroll-content.ionic-scroll.has-header {
89
+    top: 20px;
90
+}
91
+ion-list#lively-menu {}
92
+
93
+#lively-menu ion-item.item {
94
+    color: #fff;
95
+    background-color: #343434;
96
+    border-color: #565656;
97
+}
98
+
99
+#lively-menu a.item-content {
100
+    background-color: #343434;
101
+}
102
+i.icon-perspective {
103
+	background: url('../img/perspective.png') no-repeat center left;
104
+	background-size: contain;
105
+	width: 33px;	
106
+}
107
+hr.zig, hr.zag {
108
+  border: none;
109
+  height: 30px;
110
+  margin: 0 0px;
111
+}
112
+
113
+hr.zig{
114
+  background: linear-gradient(-135deg, #FFF 20px, rgba(0, 0, 0, 0) 0) 0 5px, linear-gradient(135deg, #FFF 20px, rgba(0, 0, 0, 0) 0) 0 5px;
115
+  background-color: rgba(0, 0, 0, 0);
116
+  background-position: center bottom;
117
+  background-repeat: repeat-x;
118
+  background-size: 20px 40px;
119
+  z-index: 100;
120
+  position: relative;
121
+}
122
+
123
+hr.zag {
124
+  background: linear-gradient(-135deg, #eee 20px, rgba(0, 0, 0, 0) 0) 0 5px, linear-gradient(135deg, #eee 20px, #FFF 0) 0 5px;
125
+  background-color: rgba(0, 0, 0, 0);
126
+  background-position: center bottom;
127
+  background-repeat: repeat-x;
128
+  background-size: 20px 40px;
129
+  z-index: 50;
130
+  margin-top: -28px;
131
+}
132
+.author-avatar {
133
+    width:60px;
134
+    height:60px;
135
+    border-radius: 50%;
136
+}
137
+.post-body { font-size:120%; }
138
+img { max-width:100%; }

BIN
www/img/Icon-Facebook.png


BIN
www/img/big-logo.png


BIN
www/img/default.png


BIN
www/img/ionic.png


BIN
www/img/lively-bg.png


BIN
www/img/logo.png


BIN
www/img/perspective.png


BIN
www/img/star.png


+ 38 - 0
www/index.html

@@ -0,0 +1,38 @@
1
+<!DOCTYPE html>
2
+<html>
3
+  <head>
4
+    <meta charset="utf-8">
5
+    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
6
+    <title></title>
7
+    <!--
8
+    <link href="lib/ionic/css/ionic.css" rel="stylesheet"> -->
9
+    <link href="css/ionic.app.css" rel="stylesheet">
10
+    <link href="css/style.css" rel="stylesheet">
11
+    <link href="lib/Ionicons/css/ionicons.css">
12
+    <!-- 
13
+    <link href="lib/font-awesome/css/font-awesome.css"> -->
14
+
15
+
16
+    <!-- ionic/angularjs js -->
17
+    <script src="lib/ionic/js/ionic.bundle.js"></script>
18
+    <!-- cordova script (this will be a 404 during development) -->
19
+    <script src="lib/ngCordova/dist/ng-cordova.js"></script>
20
+    
21
+    <script src="cordova.js"></script>
22
+    <script type="text/javascript" charset="utf-8">
23
+    var env = "prod";
24
+    </script>
25
+    <!-- your app's js -->
26
+    <script src="lib/humanize/humanize.js"></script>
27
+    <script src="lib/angularjs-humanize/src/angular-humanize.js"></script>
28
+    <script src="lib/jsSHA/src/sha1.js"></script>
29
+	<script src="lib/ng-cordova-oauth/dist/ng-cordova-oauth.js"></script>
30
+    <script src="http://cdn.mcot.net/publicscript/js/filters.js"></script>
31
+    <script src="js/app.js"></script>
32
+    <script src="js/controllers.js"></script>
33
+  </head>
34
+
35
+  <body ng-app="starter">
36
+    <ion-nav-view></ion-nav-view>
37
+  </body>
38
+</html>

+ 579 - 0
www/js/app.js

@@ -0,0 +1,579 @@
1
+// Ionic Starter App
2
+
3
+// angular.module is a global place for creating, registering and retrieving Angular modules
4
+// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
5
+// the 2nd parameter is an array of 'requires'
6
+// 'starter.controllers' is found in controllers.js
7
+var db;
8
+angular.module('starter', ['ionic', 'ngCordova', 'ngCordovaOauth',  'starter.controllers'])
9
+.run(function($ionicPlatform, $cordovaSQLite) {
10
+  $ionicPlatform.ready(function() {
11
+    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
12
+    // for form inputs)
13
+    if (window.cordova && window.cordova.plugins.Keyboard) {
14
+      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
15
+      cordova.plugins.Keyboard.disableScroll(true);
16
+
17
+    }
18
+    if (window.StatusBar) {
19
+      // org.apache.cordova.statusbar required
20
+      StatusBar.styleDefault();
21
+    }
22
+	if (window.cordova) {
23
+	    try {
24
+	        db = $cordovaSQLite.openDB({
25
+	            name: "tna.db",
26
+	            location: 'default'
27
+	        });
28
+	    } catch (error) {
29
+	        alert(error);
30
+	    }
31
+	    $cordovaSQLite.execute(db, 'CREATE TABLE IF NOT EXISTS Messages (id INTEGER PRIMARY KEY AUTOINCREMENT, message TEXT)');
32
+	    $cordovaSQLite.execute(db, 'CREATE TABLE IF NOT EXISTS Personal(key TEXT PRIMARY KEY, value TEXT)');
33
+	}
34
+ });
35
+})
36
+.filter('humanize', function(){
37
+    return function humanize(number) {
38
+        if(number < 1000) {
39
+            return number;
40
+        }
41
+        var si = ['K', 'M', 'G', 'T', 'P', 'H'];
42
+        var exp = Math.floor(Math.log(number) / Math.log(1000));
43
+        var result = number / Math.pow(1000, exp);
44
+        result = (result % 1 > (1 / Math.pow(1000, exp - 1))) ? result.toFixed(2) : result.toFixed(0);
45
+        return result + si[exp - 1];
46
+    };
47
+})
48
+.filter('get_url', ['IMG_URI', function(IMG_URI){
49
+    return function(url) {
50
+        return IMG_URI + url;
51
+    };
52
+}])
53
+.filter('map_link',[function(){
54
+    return function(geocode) {
55
+        var isIOS = ionic.Platform.isIOS();
56
+        var isAndroid = ionic.Platform.isAndroid();
57
+        if( isIOS ) {
58
+            return "maps://?q="+geocode;
59
+        }
60
+        if( isAnroid ) {
61
+            return "geo:"+geocode;
62
+        }
63
+    };
64
+}])
65
+.filter('titleCase', function() {
66
+    return function(input) {
67
+      input = input || '';
68
+      return input.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
69
+    };
70
+  })
71
+.filter('get_last_array', function(){
72
+    return function(arr) {
73
+        if( arr.length > 0 ){
74
+            var v  = arr[arr.length-1];
75
+            if( v == ""){
76
+                return "Untitled";
77
+            }else {
78
+                return v;
79
+            }
80
+        } else
81
+            return "Untitled";
82
+    };
83
+})
84
+.value('THROTTLE_MILLISECONDS', 4000)
85
+//.constant('API_URI', 'http://localhost:5050')
86
+.constant('API_URI', 'http://mcotn-api.simplico.net')
87
+//.constant('IMG_URI', 'http://simplico.net:5060')
88
+.constant('IMG_URI', 'http://mcotn-backend.simplico.net')
89
+//.constant('GEN_USER_API_URI', 'http://localhost:5052')
90
+.constant('GEN_USER_API_URI', 'http://mcotn-simplitic.simplico.net')
91
+.constant('SEARCH_API', '/api/v1.0/search?collection=posts')
92
+.constant('SHARE_API', '/api/v1.0/share')
93
+.constant('USER_API', '/api/v1.0/users_social')
94
+.constant('CMS_USER_API', '/api/v1.0/users')
95
+.constant('SETTING_API', '/api/v1.0/setting')
96
+.constant('FAV_API', '/api/v1.0/fav')
97
+.constant('NOTI_API', '/api/v1.0/noti')
98
+.constant('AUTH_API', '/auth')
99
+.constant('PAGE_LIMIT', 10)
100
+.constant('DB', 'lively.db')
101
+.constant('PUBLIC_TOKEN', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoicHVibGljIiwicm9sZXMiOlsicHVibGljIl0sImV4cCI6MTczNDE2NTcxMCwiaWF0IjoxNDc0OTY1NzEwLCJuYmYiOjE0NzQ5NjU3MTAsImlkZW50aXR5IjoiNTdlOWVmMjZjMjU0ZmQ4N2Q3ZmQ4MzM2In0.-9fcm5s8qYbdqBDUX7cZJ5J3AX91fe6VrCLs_S-_eBU')
102
+.config(function($stateProvider, $urlRouterProvider, $cordovaInAppBrowserProvider, $ionicConfigProvider) {
103
+  var defaultOptions = {
104
+      location: 'no',
105
+      clearcache: 'no',
106
+      toolbar: 'yes'
107
+  };
108
+$ionicConfigProvider.backButton.previousTitleText(false).text('');
109
+  $cordovaInAppBrowserProvider.setDefaultOptions(defaultOptions);
110
+ $stateProvider
111
+
112
+    .state('app', {
113
+    url: '/app',
114
+    abstract: true,
115
+    templateUrl: 'templates/menu.html',
116
+    controller: 'AppCtrl'
117
+  })
118
+
119
+  .state('app.search', {
120
+    url: '/search',
121
+    views: {
122
+      'menuContent': {
123
+        templateUrl: 'templates/search.html',
124
+        controller: 'SearchCtrl'
125
+      }
126
+    }
127
+  })
128
+
129
+  .state('app.login', {
130
+    url: '/login',
131
+    views: {
132
+        'menuContent': {
133
+            templateUrl: 'templates/login.html',
134
+            controller: 'LoginCtrl'
135
+        }
136
+    }
137
+  })
138
+  .state('app.view', {
139
+    url: '/view/:id',
140
+    views: {
141
+      'menuContent': {
142
+        templateUrl: 'templates/view.html',
143
+        controller: 'ViewCtrl'
144
+      }
145
+    }
146
+  })
147
+
148
+  .state('app.browse', {
149
+      url: '/browse',
150
+      views: {
151
+        'menuContent': {
152
+          templateUrl: 'templates/browse.html'
153
+        }
154
+      }
155
+    })
156
+    .state('app.playlists', {
157
+      url: '/playlists',
158
+      views: {
159
+        'menuContent': {
160
+          templateUrl: 'templates/playlists.html',
161
+          controller: 'PlaylistsCtrl'
162
+        }
163
+      }
164
+    })
165
+    .state('app.settings', {
166
+      url: '/settings',
167
+      cache: false,
168
+      views: {
169
+        'menuContent': {
170
+          templateUrl: 'templates/settings.html',
171
+          controller: 'SettingCtrl'
172
+        }
173
+      }
174
+    })
175
+
176
+    .state('app.index', {
177
+      url: '/index',
178
+      views: {
179
+        'menuContent': {
180
+          templateUrl: 'templates/index_page.html',
181
+          controller: 'IndexPageCtrl'
182
+        }
183
+      }
184
+    })
185
+    .state('app.fav', {
186
+      url: '/fav',
187
+      cache: false,
188
+      views: {
189
+        'menuContent': {
190
+          templateUrl: 'templates/index_page.html',
191
+          controller: 'FavCtrl'
192
+        }
193
+      }
194
+    })
195
+    .state('app.byauthor', {
196
+        url: '/byauthor/:uid',
197
+      views: {
198
+        'menuContent': {
199
+          templateUrl: 'templates/byauthor.html',
200
+          controller: 'ByAuthorCtrl'
201
+        }
202
+      }
203
+    })
204
+    .state('app.cat', {
205
+        url: '/cat/:catname',
206
+      views: {
207
+        'menuContent': {
208
+          templateUrl: 'templates/index_page.html',
209
+          controller: 'ListPageCtrl'
210
+        }
211
+      }
212
+    })
213
+    .state('app.nearby', {
214
+        url: '/nearby',
215
+        cache: false,
216
+      views: {
217
+        'menuContent': {
218
+          templateUrl: 'templates/nearby.html',
219
+          controller: 'NearbyCtrl'
220
+        }
221
+      }
222
+    })
223
+
224
+  .state('app.single', {
225
+    url: '/playlists/:playlistId',
226
+    views: {
227
+      'menuContent': {
228
+        templateUrl: 'templates/playlist.html',
229
+        controller: 'PlaylistCtrl'
230
+      }
231
+    }
232
+  });
233
+  // if none of the above states are matched, use this as the fallback
234
+  $urlRouterProvider.otherwise('/app/index');
235
+})
236
+.service('mcotcms', function($http, $httpParamSerializer, API_URI, SEARCH_API, PAGE_LIMIT, SHARE_API, USER_API,GEN_USER_API_URI, AUTH_API, $cordovaSQLite, SETTING_API, FAV_API, NOTI_API, CMS_USER_API) {
237
+
238
+    this.all_posts = function(){
239
+        console.log("all posts");
240
+		return $http({
241
+            method: 'GET',
242
+            url: API_URI + SEARCH_API
243
+		});
244
+    }
245
+    this.get_share = function(id){
246
+        var params = {
247
+            'id': id,
248
+            'action': 'FETCH'
249
+        };
250
+
251
+        var qs = $httpParamSerializer(params);
252
+		return $http({
253
+            method: 'POST',
254
+            url: GEN_USER_API_URI + SHARE_API,
255
+            data: params
256
+		});
257
+    }
258
+    this.update_share = function(id){
259
+        var params = {
260
+            'id': id,
261
+            'action': 'update'
262
+        };
263
+
264
+        var qs = $httpParamSerializer(params);
265
+		return $http({
266
+            method: 'POST',
267
+            url: GEN_USER_API_URI + SHARE_API,
268
+            data: params
269
+		});
270
+    }
271
+    this.clear_data = function(){
272
+        console.log("clear data");
273
+        window.localStorage.clear();
274
+
275
+    }
276
+    this.load_data = function(){
277
+	    return $cordovaSQLite.execute(db, 'SELECT * FROM Messages ORDER BY id DESC');
278
+    }
279
+    this.load_personal_data = function(key){
280
+	    return $cordovaSQLite.execute(db, 'SELECT *  FROM Personal where key = ?', [key]);
281
+    }
282
+    this.get_settings = function(token){
283
+        var params = {
284
+            action: 'GET'
285
+        };
286
+        var qs = $httpParamSerializer(params);
287
+        return $http({
288
+            method: 'POST',
289
+            url: GEN_USER_API_URI + SETTING_API,
290
+            headers: {
291
+                Authorization: 'JWT '+token,
292
+            },
293
+            data: params
294
+        });
295
+    }
296
+    this.post_api = function(token, action, model){
297
+        var params = {
298
+            'model': model,
299
+            'action': action
300
+        };
301
+        return $http({
302
+            method: 'POST',
303
+            url: API_URI + "/api/v2.0/posts",
304
+            headers: {
305
+                Authorization: 'JWT '+token,
306
+            },
307
+            data: params
308
+        });
309
+    }
310
+    this.by_author = function(author_id){
311
+        var params = {
312
+            'id': author_id,
313
+        };
314
+        var qs = $httpParamSerializer(params);
315
+		return $http({
316
+            method: 'GET',
317
+            url: API_URI + CMS_USER_API + "?id=" + author_id,
318
+            data: params
319
+		});
320
+    }
321
+    this.more_on = function(oid, options){
322
+        var params = {
323
+            'method': 'moreon',
324
+            'id': oid,
325
+            /*
326
+            'query': {
327
+                'meta.location': {
328
+                    '$near': {
329
+                        '$geometry': {'type': "Point", 'coordinates':  [position.longitude, position.latitude]},
330
+                        '$maxDistance': 5000
331
+                    }
332
+                }
333
+            },*/
334
+            'type': options.type,
335
+            'version': 'short'
336
+
337
+        };
338
+        var qs = $httpParamSerializer(params);
339
+		return $http({
340
+            method: 'POST',
341
+            url: API_URI + SEARCH_API,
342
+            data: params
343
+		});
344
+    }
345
+    this.nearby = function(position, options){
346
+        var page = options.page;
347
+        var skip = page * PAGE_LIMIT;
348
+        var limit = PAGE_LIMIT;
349
+        var params = {
350
+            'query': {
351
+                'location': {
352
+                    '$near': {
353
+                        '$geometry': {'type': "Point", 'coordinates':  [position.longitude, position.latitude]},
354
+                        '$maxDistance': 5000
355
+                    }
356
+                }
357
+            },
358
+            'type': options.type,
359
+            'skip': skip,
360
+            'page': page,
361
+            'limit': limit,
362
+            'version': 'short'
363
+
364
+        };
365
+        var qs = $httpParamSerializer(params);
366
+		return $http({
367
+            method: 'POST',
368
+            url: API_URI + SEARCH_API,
369
+            data: params
370
+		});
371
+    }
372
+    this.save_settings = function(token, cats){
373
+        var params = {
374
+            'cats': cats,
375
+            'action': 'UPDATE'
376
+        };
377
+        var qs = $httpParamSerializer(params);
378
+        return $http({
379
+            method: 'POST',
380
+            url: GEN_USER_API_URI + SETTING_API,
381
+            headers: {
382
+                Authorization: 'JWT '+token,
383
+            },
384
+            data: params
385
+        });
386
+    }
387
+    this.get_fav  = function(token, options){
388
+
389
+        var page = options.page;
390
+        var skip = page * PAGE_LIMIT;
391
+        var limit = PAGE_LIMIT;
392
+        var params = {
393
+            'action': 'GET',
394
+            'type': options.type,
395
+            'skip': skip,
396
+            'page': page,
397
+            'limit': limit
398
+        };
399
+        var qs = $httpParamSerializer(params);
400
+        return $http({
401
+            method: 'POST',
402
+            url: GEN_USER_API_URI + FAV_API,
403
+            headers: {
404
+                Authorization: 'JWT '+token,
405
+            },
406
+            data: params
407
+        });
408
+    }
409
+    this.fetch_noti = function(token, options) {
410
+        var params = {
411
+            'action': 'FETCH',
412
+            'type': options.type,
413
+        };
414
+        var qs = $httpParamSerializer(params);
415
+        return $http({
416
+            method: 'POST',
417
+            url: GEN_USER_API_URI + NOTI_API,
418
+            headers: {
419
+                Authorization: 'JWT '+token,
420
+            },
421
+            data: params
422
+        });
423
+    }
424
+    this.add_fav = function(token, post_id, type){
425
+        var params = {
426
+            'post_id': post_id,
427
+            'action': 'ADD',
428
+            'type': type
429
+        };
430
+        var qs = $httpParamSerializer(params);
431
+        return $http({
432
+            method: 'POST',
433
+            url: GEN_USER_API_URI + FAV_API,
434
+            headers: {
435
+                Authorization: 'JWT '+token,
436
+            },
437
+            data: params
438
+        });
439
+    }
440
+    this.store_token = function(token){
441
+        var storage = window.localStorage;
442
+        console.log("token is ", token);
443
+        storage.setItem("token", token);
444
+        return $cordovaSQLite.execute(db, 'INSERT or REPLACE INTO Personal(key, value) VALUES (?, ?)', ['token', token]);
445
+    }
446
+
447
+    this.get_token  = function(){
448
+        return window.localStorage.getItem("token");
449
+    }
450
+    this.get_post_by_id = function(id){
451
+
452
+        var params = {
453
+            'id': id,
454
+            'version': 'full'
455
+        };
456
+        var qs = $httpParamSerializer(params);
457
+		return $http({
458
+            method: 'POST',
459
+            url: API_URI + SEARCH_API,
460
+            data: params
461
+		});
462
+    }
463
+
464
+    this.login = function(username, pass) {
465
+
466
+        var params = {
467
+            'username': username,
468
+            'password': pass
469
+        };
470
+        var qs = $httpParamSerializer(params);
471
+        return $http({
472
+            method: 'POST',
473
+            url: GEN_USER_API_URI + AUTH_API,
474
+            data: params
475
+        });
476
+    }
477
+    this.add_user = function(user_obj){
478
+        var params = {
479
+            'obj': user_obj,
480
+            'action': 'add'
481
+        };
482
+        var qs = $httpParamSerializer(params);
483
+        return $http({
484
+            method: 'POST',
485
+            url: GEN_USER_API_URI + USER_API,
486
+            data: params
487
+        });
488
+    }
489
+    this.add_user_by_form = function(user_obj){
490
+        var params = {
491
+            'obj': user_obj,
492
+            'action': 'register_by_form'
493
+        };
494
+        var qs = $httpParamSerializer(params);
495
+        return $http({
496
+            method: 'POST',
497
+            url: GEN_USER_API_URI + USER_API,
498
+            data: params
499
+        });
500
+    }
501
+    this.get_posts = function(option) {
502
+        var query = option.query;
503
+        var page = option.page;
504
+        var skip = page * PAGE_LIMIT;
505
+        var limit = PAGE_LIMIT;
506
+        var version = option.version;
507
+        var params = {
508
+            'query': query,
509
+            'page': page,
510
+            'skip': skip,
511
+            'limit': limit,
512
+            'version': version,
513
+			'sort': 'desc'
514
+        };
515
+        var qs = $httpParamSerializer(params);
516
+        console.log(qs);
517
+		return $http({
518
+            method: 'POST',
519
+            url: API_URI + SEARCH_API,
520
+            data: params
521
+		});
522
+    }
523
+
524
+})
525
+.directive('searchBar', [function () {
526
+	return {
527
+		scope: {
528
+			ngModel: '='
529
+		},
530
+		require: ['^ionNavBar', '?ngModel'],
531
+		restrict: 'E',
532
+		replace: true,
533
+		template: '<ion-nav-buttons side="right">'+
534
+						'<div class="searchBar">'+
535
+							'<div class="searchTxt" ng-show="ngModel.show">'+
536
+						  		'<div class="bgdiv"></div>'+
537
+						  		'<div class="bgtxt">'+
538
+						  			'<input type="text" placeholder="Procurar..." ng-model="ngModel.txt">'+
539
+						  		'</div>'+
540
+					  		'</div>'+
541
+						  	'<i class="icon placeholder-icon" ng-click="ngModel.txt=\'\';ngModel.show=!ngModel.show"></i>'+
542
+						'</div>'+
543
+					'</ion-nav-buttons>',
544
+
545
+		compile: function (element, attrs) {
546
+			var icon=attrs.icon
547
+					|| (ionic.Platform.isAndroid() && 'ion-android-search')
548
+					|| (ionic.Platform.isIOS()     && 'ion-ios7-search')
549
+					|| 'ion-search';
550
+			angular.element(element[0].querySelector('.icon')).addClass(icon);
551
+
552
+			return function($scope, $element, $attrs, ctrls) {
553
+				var navBarCtrl = ctrls[0];
554
+				$scope.navElement = $attrs.side === 'right' ? navBarCtrl.rightButtonsElement : navBarCtrl.leftButtonsElement;
555
+
556
+			};
557
+		},
558
+		controller: ['$scope','$ionicNavBarDelegate', function($scope,$ionicNavBarDelegate){
559
+			var title, definedClass;
560
+			$scope.$watch('ngModel.show', function(showing, oldVal, scope) {
561
+				if(showing!==oldVal) {
562
+					if(showing) {
563
+						if(!definedClass) {
564
+							var numicons=$scope.navElement.children().length;
565
+							angular.element($scope.navElement[0].querySelector('.searchBar')).addClass('numicons'+numicons);
566
+						}
567
+
568
+						title = $ionicNavBarDelegate.getTitle();
569
+						$ionicNavBarDelegate.setTitle('');
570
+					} else {
571
+						$ionicNavBarDelegate.setTitle(title);
572
+					}
573
+				} else if (!title) {
574
+					title = $ionicNavBarDelegate.getTitle();
575
+				}
576
+			});
577
+		}]
578
+	};
579
+}]);

+ 873 - 0
www/js/controllers.js

@@ -0,0 +1,873 @@
1
+angular.module('starter.controllers', ["angular-humanize", 'mcot.filters'])
2
+
3
+.controller('AppCtrl', function($scope, $ionicModal, $timeout, $cordovaFacebook, mcotcms, $cordovaSQLite, $location, $ionicHistory, $ionicPopup, $state, $ionicLoading, $cordovaLocalNotification, $rootScope, $interval, $cordovaOauth, $cordovaInAppBrowser, $http) {
4
+        $scope.data = {
5
+            bcount: 0
6
+        };
7
+        $scope.igLogin = function(){
8
+            $cordovaOauth.instagram("aede22fcf7a145779da9a3cd094069f5", ["basic"])
9
+                .then(function(success){
10
+                    console.log(success);
11
+                    var token = success.access_token;
12
+                    $http.get("https://api.instagram.com/v1/users/self/?access_token="+token)
13
+                        .then(function(success){
14
+                            console.log(success);
15
+                        }, function(error){
16
+                            console.log(error);
17
+                        });
18
+                },function(error){
19
+                    console.log(error);
20
+                });
21
+        };
22
+		$scope.twitterlogin = function(){
23
+			console.log("twitter login");
24
+			var api_key = "VNA6F7wAAwn10KJ7vqhdUPVzR"; //Enter your Consumer Key (API Key)
25
+			var api_secret = "bJDNQhpZPlScH45WuARaJtvv0fMTR68AVTI3VbsfImeht8tt3S"; // Enter your Consumer Secret (API Secret)
26
+			console.log("twitterlogin function got called");
27
+            /*
28
+			 var options = {
29
+			     location: 'yes',
30
+			     clearcache: 'yes',
31
+			     toolbar: 'no'
32
+			 };
33
+	$cordovaInAppBrowser.open('http://ngcordova.com', '_blank')
34
+	    .then(function(event) {
35
+	        // success
36
+	    })
37
+	    .catch(function(event) {
38
+	        // error
39
+	    });*/
40
+           /*
41
+			$cordovaOauth.twitter(api_key, api_secret).then(function(result) {
42
+			console.log(result);
43
+			}, function(error){
44
+				console.log(error);
45
+			});*/
46
+            $cordovaOauth.google("314112088577-mqifo59b09psg38fg5l3s27e8h2ihj2c.apps.googleusercontent.com", ["email"]).then(function(result) {
47
+                    console.log("Response Object -> " + JSON.stringify(result));
48
+            }, function(error) {
49
+                    console.log("Error -> " + error);
50
+            });
51
+		};
52
+ $rootScope.$on('$cordovaInAppBrowser:loaderror', function(e, event) {
53
+     console.log("load error");
54
+     console.log(e);
55
+     console.log(event);
56
+ });
57
+
58
+		$scope.authenticate = function(provider) {
59
+		    $auth.authenticate(provider);
60
+		};
61
+      /*
62
+        $interval(function() {
63
+            //$scope.fetchNotification();
64
+            $cordovaLocalNotification.schedule({
65
+                id: Math.floor(Date.now() / 1000),
66
+                title: 'test noti',
67
+                text: 'description',
68
+            }).then(function(result) {
69
+                console.log(result);
70
+            });
71
+            console.log("noti");
72
+        }, 5000);*/
73
+	    $scope.fetchNotification =  function(){
74
+            var token = mcotcms.get_token();
75
+            if( token !== null ){
76
+                mcotcms.fetch_noti(token, {type: 'post'})
77
+                    .then(function(success){
78
+                        console.log("fetch_noti");
79
+                        $scope.data.bcount = success.data.output.length;
80
+                        for(var i = 0; i < success.data.output.length; i++) {
81
+                            post = success.data.output[i];
82
+                            console.log("the post");
83
+                            console.log(post);
84
+                            $cordovaLocalNotification.schedule({
85
+                                id: post._id.$oid,
86
+                                title: post.title,
87
+                                text: post.description,
88
+                                data: {
89
+                                    oid: success.data.output[i]._id.$oid
90
+                                }
91
+                            }).then(function(result) {
92
+                                console.log(result);
93
+                            });
94
+                        }
95
+                    }, function(error){
96
+
97
+                    });
98
+            }
99
+        };
100
+        $scope.scheduleSingleNotification = function() {
101
+            var now = new Date().getTime();
102
+            var _10SecondsFromNow = new Date(now + 10 * 1000);
103
+            console.log("run notification");
104
+            /*
105
+			$cordovaLocalNotification.schedule({
106
+				id: 1,
107
+				title: 'Title here',
108
+				text: 'Text here',
109
+				at: _10SecondsFromNow
110
+			}).then(function(result) {
111
+				console.log(result);
112
+			});*/
113
+        };
114
+        $rootScope.$on('$cordovaLocalNotification:trigger',
115
+            function(event, notification, state) {
116
+                console.log("trigger");
117
+                console.log(event);
118
+                console.log(notification);
119
+                console.log(state);
120
+            });
121
+        $rootScope.$on('$cordovaLocalNotification:update',
122
+            function(event, notification, state) {
123
+                console.log("update");
124
+                console.log(event);
125
+                console.log(notification);
126
+                console.log(state);
127
+            });
128
+        $rootScope.$on('$cordovaLocalNotification:click',
129
+            function(event, notification, state) {
130
+                console.log("click");
131
+                console.log(event);
132
+                console.log(notification);
133
+                console.log(state);
134
+                var d = angular.fromJson(notification.data);
135
+                console.log(d);
136
+                $state.go("app.view", { id: d.oid});
137
+            });
138
+        $scope.loadData = function() {
139
+            //console.log(db);
140
+            console.log(window.localStorage);
141
+            console.log("load call");
142
+            mcotcms.load_personal_data("token")
143
+                .then(
144
+                    function(res) {
145
+
146
+                        if (res.rows.length > 0) {
147
+
148
+                            $scope.newMessage = res.rows.item(0);
149
+                            $scope.statusMessage = "Message loaded successful, cheers!";
150
+                        }
151
+                    },
152
+                    function(error) {
153
+                        $scope.statusMessage = "Error on loading: " + error.message;
154
+                    }
155
+                );
156
+        };
157
+        // With the new view caching in Ionic, Controllers are only called
158
+        // when they are recreated or on app start, instead of every page change.
159
+        // To listen for when this page is active (for example, to refresh data),
160
+        // listen for the $ionicView.enter event:
161
+        //$scope.$on('$ionicView.enter', function(e) {
162
+        //});
163
+
164
+        // Form data for the login modal
165
+        $scope.loginData = {};
166
+        $scope.signupData = {};
167
+        $scope.showAlert = function(title, text) {
168
+            var alertPopup = $ionicPopup.alert({
169
+                title: title,
170
+                template: text
171
+            });
172
+
173
+            alertPopup.then(function(res) {
174
+                console.log('Thank you for not eating my delicious ice cream cone');
175
+            });
176
+        };
177
+        $scope.logout = function() {
178
+            if (angular.isDefined($scope.modal))
179
+                $scope.modal.hide();
180
+            if (angular.isDefined($scope.signup_modal))
181
+                $scope.signup_modal.hide();
182
+
183
+            mcotcms.clear_data();
184
+            $ionicHistory.nextViewOptions({
185
+                disableBack: true
186
+            });
187
+            $state.go('app.index');
188
+
189
+            $cordovaFacebook.logout()
190
+                .then(function(success) {
191
+                    // success
192
+                    console.log("logout");
193
+                    console.log(success);
194
+                }, function(error) {
195
+                    // error
196
+                });
197
+        };
198
+        $scope.doSignUp = function() {
199
+
200
+            if ($scope.signupData.password == $scope.signupData.confirm_password) {
201
+                $scope.signupData.message = "Success";
202
+                mcotcms.add_user_by_form({
203
+                        email: $scope.signupData.email,
204
+                        password: $scope.signupData.password
205
+                    })
206
+                    .then(function(success) {
207
+                        console.log("add user ");
208
+                        //mcotcms.execute();
209
+                        //mcotcms.select();
210
+                        console.log(success);
211
+                        $scope.signup_modal.hide();
212
+                        $scope.$emit('requireLogin', {})
213
+                            /*
214
+						  $cordovaSQLite.execute(db, 'INSERT INTO Messages (message) VALUES (?)', ["tum"])
215
+						      .then(function(result) {
216
+						          $scope.statusMessage = "Message saved successful, cheers!";
217
+						      }, function(error) {
218
+						          $scope.statusMessage = "Error on saving: " + error.message;
219
+						      })*/
220
+                    }, function(error) {
221
+                        console.log("add erro user ");
222
+                        console.log(error);
223
+                        $scope.$emit("signupError", {
224
+                                msg: error.data.error
225
+                            })
226
+                            //console.log(error);
227
+                            //$scope.statusMessage = err.data.error;
228
+                            //console.log(err);
229
+                    });
230
+            } else {
231
+                $scope.signupData.message = "Not Matched";
232
+            }
233
+        };
234
+        $scope.getStatus = function() {
235
+            $cordovaFacebook.getLoginStatus()
236
+                .then(function(success) {
237
+                    console.log("get status");
238
+                    console.log(success);
239
+                    /*
240
+                    { authResponse: {
241
+                        userID: "12345678912345",
242
+                        accessToken: "kgkh3g42kh4g23kh4g2kh34g2kg4k2h4gkh3g4k2h4gk23h4gk2h34gk234gk2h34AndSoOn",
243
+                        session_Key: true,
244
+                        expiresIn: "5183738",
245
+                        sig: "..."
246
+                        },
247
+                        status: "connected"
248
+                    }
249
+                    */
250
+                }, function(error) {
251
+                    // error
252
+                });
253
+        };
254
+        $scope.fblogin = function() {
255
+            $cordovaFacebook.login(["public_profile", "email", "user_friends"])
256
+                .then(function(login_obj) {
257
+                    console.log("fb login");
258
+                    console.log(login_obj);
259
+                    $cordovaFacebook.api("me", ["public_profile"])
260
+                        .then(function(me_obj) {
261
+                            console.log("me");
262
+                            //console.log(me_obj);
263
+                            mcotcms.add_user({
264
+                                    'login': login_obj,
265
+                                    'me': me_obj
266
+                                })
267
+                                .then(function(result) {
268
+                                    console.log(" success add_user ");
269
+                                    console.log(result.data.token);
270
+                                    mcotcms.store_token(result.data.token)
271
+                                        .then(function(result) {
272
+                                            $scope.$emit('signinSuccess', {});
273
+                                        }, function(error) {
274
+                                            console.log(error);
275
+                                        });
276
+                                }, function(err) {
277
+                                    console.log(err);
278
+                                });
279
+
280
+                        }, function(error) {
281
+                            console.log("error fb");
282
+                        });
283
+
284
+                    // { id: "634565435",
285
+                    //   lastName: "bob"
286
+                    //   ...
287
+                    // }
288
+                }, function(error) {
289
+                    console.log("error fb");
290
+                });
291
+        };
292
+        // Create the login modal that we will use later
293
+        $ionicModal.fromTemplateUrl('templates/login.html', {
294
+            scope: $scope
295
+        }).then(function(modal) {
296
+            $scope.modal = modal;
297
+        });
298
+        $scope.openLoginDialog = function() {
299
+            console.log("modal");
300
+            console.log($scope.modal);
301
+        };
302
+        // Create the login modal that we will use later
303
+        $scope.$on('requireLogin', function(event, args) {
304
+            if( angular.isDefined($scope.modal)) {
305
+                $scope.modal.remove();
306
+            }
307
+            $ionicModal.fromTemplateUrl('templates/login.html', {
308
+                scope: $scope
309
+            }).then(function(modal) {
310
+
311
+                $scope.modal = modal;
312
+                $scope.modal.show();
313
+            });
314
+            //$scope.openLoginDialog();
315
+        });
316
+        $scope.$on('logoutEvent', function(event, args) {
317
+            $scope.logout();
318
+        });
319
+        $scope.$on('signinError', function(event, args) {
320
+            console.log("Sign In Error");
321
+            $scope.showAlert("Sign In Error", args.msg);
322
+        });
323
+        $scope.$on('showLoading', function(event, args) {
324
+            console.log("show loading");
325
+            $ionicLoading.show({
326
+                template: args.text
327
+            }).then(function() {
328
+                console.log("The loading indicator is now displayed");
329
+            });
330
+        });
331
+
332
+        $scope.$on('hideLoading', function(event, args) {
333
+            $ionicLoading.hide().then(function() {
334
+                console.log("The loading indicator is now hidden");
335
+            });
336
+        });
337
+        $scope.$on('signupError', function(event, args) {
338
+            console.log("Sign Up Error");
339
+            $scope.showAlert("Sign Up Error", args.msg);
340
+        });
341
+        $scope.$on('signinSuccess', function(event, args) {
342
+            console.log("Sign in success");
343
+            $state.go("app.index");
344
+            $scope.closeLogin();
345
+            $scope.closeSignUp();
346
+        });
347
+
348
+        $ionicModal.fromTemplateUrl('templates/signup.html', {
349
+            scope: $scope
350
+        }).then(function(su_modal) {
351
+            $scope.signup_modal = su_modal;
352
+        });
353
+
354
+        // Triggered in the login modal to close it
355
+        $scope.closeLogin = function() {
356
+            console.log("close sigin");
357
+            if(angular.isDefined($scope.modal))
358
+                $scope.modal.hide();
359
+
360
+            var token = mcotcms.get_token();
361
+
362
+            console.log(token);
363
+            if (token == 'null') {
364
+                console.log('null ?');
365
+                $ionicHistory.nextViewOptions({
366
+                    disableBack: true
367
+                });
368
+                $state.go('app.index');
369
+            }
370
+        };
371
+        $scope.closeSignUp = function() {
372
+            if( angular.isDefined($scope.signup_modal))
373
+                $scope.signup_modal.hide();
374
+            var token = mcotcms.get_token();
375
+            if (token == null) {
376
+                $ionicHistory.nextViewOptions({
377
+                    disableBack: true
378
+                });
379
+                $state.go('app.index');
380
+            }
381
+        };
382
+
383
+        // Open the login modal
384
+        $scope.login = function() {
385
+            $scope.modal.show();
386
+        };
387
+
388
+        // Perform the login action when the user submits the login form
389
+        $scope.doLogin = function() {
390
+            console.log('Doing login', $scope.loginData);
391
+
392
+            // Simulate a login delay. Remove this and replace with your login
393
+            // code if using a login system
394
+            mcotcms.login($scope.loginData.username, $scope.loginData.password)
395
+                .then(function(success) {
396
+                    console.log(success);
397
+                    mcotcms.store_token(success.data.access_token)
398
+                        .then(function(result) {
399
+                            console.log(result);
400
+                        }, function(error) {
401
+                            console.log(error);
402
+                        });
403
+                }, function(error) {
404
+                    $scope.$emit("signinError", {
405
+                        msg: error.data.description
406
+                    })
407
+                    console.log(error);
408
+                });
409
+            $timeout(function() {
410
+                $scope.closeLogin();
411
+            }, 1000);
412
+        };
413
+    })
414
+    .controller('RequireLoginCtrl', function($scope, mcotcms, $location) {
415
+        console.log("check login ...");
416
+        var token = mcotcms.get_token();
417
+        if (token == 'null' || token == null)  {
418
+            console.log("token null");
419
+            mcotcms.store_token(null);
420
+            $scope.$emit('requireLogin', {});
421
+        }
422
+
423
+    })
424
+    .controller('UtilCtrl', function($scope, mcotcms, $location) {
425
+
426
+        $scope.toDate = function(mongoDate) {
427
+          console.log(mongoDate)
428
+			if( angular.isDefined(mongoDate) ) {
429
+            	return new Date(mongoDate.$date);
430
+			}else {
431
+            	return new Date();
432
+			}
433
+        };
434
+		$scope.go = function ( path ) {
435
+			$location.path( path );
436
+		};
437
+        $scope.fav = function(post_id, type) {
438
+            var token = mcotcms.get_token();
439
+            mcotcms.add_fav(token, post_id, type)
440
+                .then(function(success) {
441
+                    console.log(success);
442
+                }, function(error) {
443
+                    console.log(error);
444
+                });
445
+        };
446
+    })
447
+    .controller('LoadMoreCtrl', function($scope, mcotcms, PAGE_LIMIT) {
448
+        $scope.current_page = 0;
449
+        $scope.is_empty = true;
450
+        $scope.moreDataCanBeLoaded = function() {
451
+            console.log("more data canbe loaded");
452
+            return $scope.is_empty == false;
453
+        };
454
+        $scope.firstLoad = function() {
455
+            console.log("load 1");
456
+            $scope.is_empty = true;
457
+            if (angular.isUndefined($scope.results)) {
458
+                $scope.results = [];
459
+            }
460
+            if (angular.isDefined($scope.special_action)) {
461
+                console.log("wait im");
462
+                if ($scope.special_action == "get_fav") {
463
+                    var the_query = mcotcms.get_fav($scope.token, {
464
+                        'page': $scope.current_page++,
465
+                        'type': 'post'
466
+                    });
467
+                } else if ($scope.special_action == "get_nearby") {
468
+                    console.log("get nearby");
469
+                    var the_query = mcotcms.nearby($scope.position, {
470
+                        'page': $scope.current_page++,
471
+                        'type': 'post'
472
+                    });
473
+                }
474
+            } else {
475
+                var the_query = mcotcms.get_posts({
476
+                    'query': $scope.query,
477
+                    'page': $scope.current_page++,
478
+                    'version': $scope.post_version
479
+                });
480
+            }
481
+            the_query.then(function successCallback(response) {
482
+                // this callback will be called asynchronously
483
+                // when the response is available
484
+                console.log("first load");
485
+                console.log(response.data)
486
+                if (response.data.output.length == 0) {
487
+                    $scope.is_empty = true;
488
+                    return;
489
+                }
490
+                var output = [];
491
+                // for(var i = 0; i < 10; i++ ){
492
+                output = output.concat(response.data.output);
493
+                //}
494
+                console.log("update results");
495
+                $scope.results = $scope.results.concat(output);
496
+                console.log($scope.results);
497
+                $scope.is_empty = false;
498
+                //$scope.$broadcast('scroll.infiniteScrollComplete');
499
+                //$scope.results = response.data.output;
500
+            }, function errorCallback(response) {
501
+                // called asynchronously if an error occurs
502
+                // or server returns response with an error status.
503
+            });
504
+        };
505
+        $scope.firstLoad();
506
+        $scope.loadMoreData = function() {
507
+            console.log("load more 2");
508
+            if ($scope.is_empty == true) {
509
+                return;
510
+            }
511
+            if (angular.isUndefined($scope.results)) {
512
+                console.log("recreate ");
513
+                $scope.results = [];
514
+            }
515
+            if (angular.isDefined($scope.special_action)) {
516
+                console.log("wait im");
517
+                if ($scope.special_action == "get_fav") {
518
+                    var the_query = mcotcms.get_fav($scope.token, {
519
+                        'page': $scope.current_page++,
520
+                        'type': 'post'
521
+                    });
522
+                } else if ($scope.special_action == "get_nearby") {
523
+                    var the_query = mcotcms.nearby($scope.position, {
524
+                        'page': $scope.current_page++,
525
+                        'type': 'post'
526
+                    });
527
+                }
528
+            } else {
529
+                var the_query = mcotcms.get_posts({
530
+                    'query': $scope.query,
531
+                    'page': $scope.current_page++,
532
+                    'version': $scope.post_version
533
+                })
534
+            }
535
+            the_query.then(function successCallback(response) {
536
+                // this callback will be called asynchronously
537
+                // when the response is available
538
+                console.log("... =>");
539
+                console.log(response.data.output.length);
540
+                if (response.data.output.length == 0) {
541
+                    $scope.is_empty = true;
542
+                    return;
543
+                }
544
+                console.log("not empty ");
545
+                var output = [];
546
+                /*
547
+                for(var i = 0; i < 10; i++ ){*/
548
+                output = output.concat(response.data.output);
549
+                /*}*/
550
+                console.log("update results");
551
+                $scope.results = $scope.results.concat(output);
552
+                $scope.is_empty = false;
553
+                $scope.$broadcast('scroll.infiniteScrollComplete');
554
+                //$scope.results = response.data.output;
555
+            }, function errorCallback(response) {
556
+                // called asynchronously if an error occurs
557
+                // or server returns response with an error status.
558
+            });
559
+        };
560
+        $scope.$on('$stateChangeSuccess', function() {
561
+            $scope.loadMoreData();
562
+        });
563
+    })
564
+    .controller('FavCtrl', function($scope, mcotcms, $controller) {
565
+        $scope.query = {};
566
+        $scope.post_version = "short";
567
+        $scope.special_action = "get_fav";
568
+        $controller('RequireLoginCtrl', {
569
+            $scope: $scope
570
+        });
571
+        $scope.token = mcotcms.get_token();
572
+        if ($scope.token != 'null') {
573
+            $controller('LoadMoreCtrl', {
574
+                $scope: $scope
575
+            });
576
+        }
577
+    })
578
+    .controller('ByAuthorCtrl', function($scope, $stateParams, $controller, mcotcms) {
579
+        console.log($stateParams);
580
+        $scope.catname = $stateParams.uid;
581
+        $scope.uid = $stateParams.uid;
582
+        $scope.query = { 'author': $stateParams.uid };
583
+        $scope.post_version = "short";
584
+		mcotcms.by_author($scope.uid)
585
+			.then(function(success){
586
+				console.log("author");
587
+				console.log(success);
588
+				$scope.by_author = success.data.output;
589
+			},function(error){
590
+			});
591
+        $controller('LoadMoreCtrl', {
592
+            $scope: $scope
593
+        });
594
+    })
595
+    .controller('NearbyCtrl', function($scope, mcotcms, $controller, $cordovaGeolocation, $ionicLoading) {
596
+        $scope.query = {};
597
+        var posOptions = {
598
+            timeout: 10000,
599
+            enableHighAccuracy: false
600
+        };
601
+        $scope.$emit("showLoading", {
602
+            text: "Getting Current Position"
603
+        });
604
+        $cordovaGeolocation
605
+            .getCurrentPosition(posOptions)
606
+            .then(function(position) {
607
+                var lat = position.coords.latitude
608
+                var long = position.coords.longitude
609
+                $scope.position = position.coords;
610
+                $scope.post_version = "short";
611
+                $scope.special_action = "get_nearby";
612
+                console.log(position.coords);
613
+                console.log("get nearby call");
614
+                $scope.$emit("hideLoading", {});
615
+                $controller('LoadMoreCtrl', {
616
+                    $scope: $scope
617
+                });
618
+            }, function(err) {
619
+                // error
620
+            });
621
+    })
622
+    .controller('PlaylistsCtrl', function($scope) {
623
+        $scope.playlists = [{
624
+            title: 'Reggae',
625
+            id: 1
626
+        }, {
627
+            title: 'Chill',
628
+            id: 2
629
+        }, {
630
+            title: 'Dubstep',
631
+            id: 3
632
+        }, {
633
+            title: 'Indie',
634
+            id: 4
635
+        }, {
636
+            title: 'Rap',
637
+            id: 5
638
+        }, {
639
+            title: 'Cowbell',
640
+            id: 6
641
+        }];
642
+        $scope.settingsList = [{
643
+            text: "Wireless",
644
+            checked: true
645
+        }, {
646
+            text: "GPS",
647
+            checked: false
648
+        }, {
649
+            text: "Bluetooth",
650
+            checked: false
651
+        }];
652
+    })
653
+    .controller('SettingCtrl', function($scope, mcotcms, $controller) {
654
+        $controller('RequireLoginCtrl', {
655
+            $scope: $scope
656
+        });
657
+        var token = mcotcms.get_token();
658
+        $scope.token = token;
659
+        $scope.openUserLink = function() {
660
+            window.open('http://mcot-simplitic.simplico.net/user_panel/'+$scope.token, '_system');
661
+        };
662
+        console.log("token ", token);
663
+        $scope.categories = [{
664
+            title: "nearme",
665
+            value: true
666
+        }, {
667
+            title: "Breaking News",
668
+            value: true
669
+        }, {
670
+            title: "ข่าวพาดหัว",
671
+            value: true
672
+        }, {
673
+            title: "health",
674
+            value: true
675
+        }, {
676
+            title: "fashion",
677
+            value: true
678
+        }, {
679
+            title: "perspective",
680
+            value: true
681
+        }, {
682
+            title: "idea",
683
+            value: true
684
+        }, {
685
+            title: "outlook",
686
+            value: true
687
+        }, {
688
+            title: "phototalk",
689
+            value: true
690
+        }, ];
691
+        $scope.$on('$destroy', function() {
692
+            console.log("leaving from setting");
693
+            console.log($scope.categories);
694
+            mcotcms.save_settings(token, $scope.categories);
695
+        });
696
+        mcotcms.get_settings(token)
697
+            .then(function(success) {
698
+                var cat_serv = success.data.output;
699
+                console.log(cat_serv);
700
+                for (var i in $scope.categories) {
701
+                    c = $scope.categories[i];
702
+                    console.log(cat_serv[c.title]);
703
+                    if (cat_serv[c.title] != true) {
704
+                        c.value = false;
705
+                    }
706
+                }
707
+            }, function(error) {
708
+                console.log("errror ..", error);
709
+                mcotcms.store_token(null);
710
+                //$scope.$emit("requireLogin", {});
711
+            });
712
+        $scope.logout = function() {
713
+            console.log("logout ...");
714
+            $scope.$emit('logoutEvent', {});
715
+        };
716
+    })
717
+    .controller('IndexPageCtrl', function($scope, mcotcms, $controller) {
718
+        $scope.query = { 'categories.text': 'TNA'};
719
+        $scope.post_version = "short";
720
+
721
+        $controller('UtilCtrl', {
722
+            $scope: $scope
723
+        });
724
+        $controller('LoadMoreCtrl', {
725
+            $scope: $scope
726
+        });
727
+        /*
728
+        mcotcms.all_posts()
729
+        .then(function successCallback(response) {
730
+            // this callback will be called asynchronously
731
+            // when the response is available
732
+            var output = [];
733
+            for(var i = 0; i < 10; i++ ){
734
+                output = output.concat(response.data.output);
735
+            }
736
+            $scope.results = output ;
737
+            //$scope.results = response.data.output;
738
+        }, function errorCallback(response) {
739
+            // called asynchronously if an error occurs
740
+            // or server returns response with an error status.
741
+        });*/
742
+
743
+    })
744
+
745
+.controller('PlaylistCtrl', function($scope, $stateParams) {})
746
+    .controller('LoginCtrl', function($scope, $stateParams, $cordovaFacebook) {
747
+        $scope.login = function() {
748
+            $cordovaFacebook.login(["public_profile", "email", "user_friends"])
749
+                .then(function(success) {
750
+                    console.log(success);
751
+                    // { id: "634565435",
752
+                    //   lastName: "bob"
753
+                    //   ...
754
+                    // }
755
+                }, function(error) {
756
+                    // error
757
+                });
758
+        };
759
+    })
760
+    .controller('ViewCtrl', function($scope, $stateParams, mcotcms, $cordovaDatePicker, $cordovaSocialSharing, $filter, $controller, $sce, PUBLIC_TOKEN) {
761
+        //var deviceType = (navigator.userAgent.match(/iPad/i))  == "iPad" ? "iPad" : (navigator.userAgent.match(/iPhone/i))  == "iPhone" ? "iPhone" : (navigator.userAgent.match(/Android/i)) == "Android" ? "Android" : (navigator.userAgent.match(/BlackBerry/i)) == "BlackBerry" ? "BlackBerry" : "null";
762
+        var isIOS = ionic.Platform.isIOS();
763
+        var isAndroid = ionic.Platform.isAndroid();
764
+        //console.log(deviceType);
765
+        console.log("is IOS ", isIOS);
766
+        console.log("is ANd ", isAndroid);
767
+
768
+        var id = $stateParams.id;
769
+
770
+        $controller('UtilCtrl', {
771
+            $scope: $scope
772
+        });
773
+        $scope.openMap = function(loc) {
774
+            window.open("https://www.google.com/maps?q="+loc[1]+","+loc[0],'_system');
775
+        };
776
+        mcotcms.get_share(id)
777
+            .then(function(success){
778
+                console.log(success);
779
+                $scope.shareCounts = success.data.output;
780
+            },function(error){
781
+            });
782
+        mcotcms.get_post_by_id(id)
783
+            .then(function successCallback(response) {
784
+                $scope.post = response.data.output[0];
785
+				$scope.post_body = $sce.trustAsHtml($scope.post.body);
786
+                mcotcms.by_author($scope.post['author'])
787
+                    .then(function(success){
788
+                        console.log("author");
789
+                        console.log(success);
790
+                        $scope.by_author = success.data.output;
791
+                    },function(error){
792
+                    });
793
+                mcotcms.post_api(PUBLIC_TOKEN, "LIST",  { query: {'categories.text': $scope.post.categories[$scope.post.categories.length - 1].text} })
794
+                    .then(function(success){
795
+                        $scope.more_ons = success.data.output;
796
+                    }, function(error){
797
+
798
+                    });
799
+            }, function errorCallback(response) {});
800
+
801
+        $scope.doSomething = function() {
802
+            var options = {
803
+                date: new Date(),
804
+                mode: 'date', // or 'time'
805
+                minDate: new Date() - 10000,
806
+                allowOldDates: true,
807
+                allowFutureDates: false,
808
+                doneButtonLabel: 'DONE',
809
+                doneButtonColor: '#F2F3F4',
810
+                cancelButtonLabel: 'CANCEL',
811
+                cancelButtonColor: '#000000'
812
+            };
813
+            $cordovaDatePicker.show(options).then(function(date) {
814
+                alert(date);
815
+            });
816
+        };
817
+
818
+        $scope.share = function() {
819
+            var link_url = "http://www.tnamcot.com/view/" + $scope.post._id.$oid;
820
+
821
+            $cordovaSocialSharing
822
+                //.share($scope.post.title + " | " + $scope.post.description + ' ' + link_url, null, null, link_url) // Share via native share sheet
823
+                .share(null, null, null, link_url) // Share via native share sheet
824
+                .then(function(result) {
825
+                    // Success!
826
+                    console.log("share result ", result);
827
+                    if (result == true) {
828
+                        console.log("share true !!");
829
+                        mcotcms.update_share($scope.post._id.$oid)
830
+                            .then(function successCallBack(response) {
831
+                                console.log("share ok");
832
+                            }, function errorCallback(response) {
833
+                                console.log("fail");
834
+                            });
835
+                    }
836
+                    console.log("Share completed? ", result.completed); // On Android apps mostly return false even while it's true
837
+                    console.log("Shared to app: ", result.app); // On Android result.app is currently empty. On iOS it's empty when sharing is cancelled (result.completed=false)
838
+                }, function(err) {
839
+                    // An error occured. Show a message to the user
840
+                    console.log("share error ", err);
841
+                });
842
+        };
843
+
844
+
845
+    })
846
+    .controller('ListPageCtrl', function($scope, $stateParams, $controller, mcotcms) {
847
+        console.log($stateParams);
848
+        $scope.catname = $stateParams.catname;
849
+        $scope.query = {'categories.text': $stateParams.catname};
850
+        $controller('LoadMoreCtrl', {
851
+            $scope: $scope
852
+        });
853
+    })
854
+    .controller('SearchCtrl', function($scope, $stateParams, $http, $ionicHistory, mcotcms, $controller) {
855
+        console.log("search ctrl");
856
+        $scope.search = "";
857
+        $scope.search2 = "";
858
+        $scope.query = {'categories.text': 'TNA'};
859
+        $scope.post_version = "short";
860
+        $controller('UtilCtrl', {
861
+            $scope: $scope
862
+        });
863
+        $controller('LoadMoreCtrl', {
864
+            $scope: $scope
865
+        });
866
+        $scope.change = function(v) {
867
+            $scope.query = {'title': {'$regex': v} };
868
+            $scope.results = [];
869
+            $scope.current_page = 0;
870
+            $scope.firstLoad();
871
+
872
+        };
873
+    });

+ 41 - 0
www/lib/Ionicons/.bower.json

@@ -0,0 +1,41 @@
1
+{
2
+  "ignore": [
3
+    "**/.*",
4
+    "builder",
5
+    "node_modules",
6
+    "bower_components",
7
+    "test",
8
+    "tests"
9
+  ],
10
+  "version": "2.0.1",
11
+  "name": "Ionicons",
12
+  "license": "MIT",
13
+  "authors": [
14
+    "Ben Sperry <ben@drifty.com>",
15
+    "Adam Bradley <adam@drifty.com>",
16
+    "Max Lynch <max@drifty.com>"
17
+  ],
18
+  "keywords": [
19
+    "fonts",
20
+    "icon font",
21
+    "icons",
22
+    "ionic",
23
+    "web font"
24
+  ],
25
+  "main": [
26
+    "css/ionicons.css",
27
+    "fonts/*"
28
+  ],
29
+  "homepage": "https://github.com/driftyco/ionicons",
30
+  "description": "Ionicons - free and beautiful icons from the creators of Ionic Framework",
31
+  "_release": "2.0.1",
32
+  "_resolution": {
33
+    "type": "version",
34
+    "tag": "v2.0.1",
35
+    "commit": "ecb4b806831005c25b97ed9089fbb1d7dcc0879c"
36
+  },
37
+  "_source": "https://github.com/driftyco/ionicons.git",
38
+  "_target": "^2.0.1",
39
+  "_originalSource": "ionicons",
40
+  "_direct": true
41
+}

+ 21 - 0
www/lib/Ionicons/LICENSE

@@ -0,0 +1,21 @@
1
+The MIT License (MIT)
2
+
3
+Copyright (c) 2014 Drifty (http://drifty.com/)
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in
13
+all copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+THE SOFTWARE.

+ 31 - 0
www/lib/Ionicons/bower.json

@@ -0,0 +1,31 @@
1
+{
2
+    "ignore": [
3
+        "**/.*",
4
+        "builder",
5
+        "node_modules",
6
+        "bower_components",
7
+        "test",
8
+        "tests"
9
+    ],
10
+    "version": "2.0.0",
11
+    "name": "Ionicons",
12
+    "license": "MIT",
13
+    "authors": [
14
+        "Ben Sperry <ben@drifty.com>",
15
+        "Adam Bradley <adam@drifty.com>",
16
+        "Max Lynch <max@drifty.com>"
17
+    ],
18
+    "keywords": [
19
+        "fonts",
20
+        "icon font",
21
+        "icons",
22
+        "ionic",
23
+        "web font"
24
+    ],
25
+    "main": [
26
+        "css/ionicons.css",
27
+        "fonts/*"
28
+    ],
29
+    "homepage": "https://github.com/driftyco/ionicons",
30
+    "description": "Ionicons - free and beautiful icons from the creators of Ionic Framework"
31
+}

File diff suppressed because it is too large
+ 28009 - 0
www/lib/Ionicons/cheatsheet.html


+ 19 - 0
www/lib/Ionicons/component.json

@@ -0,0 +1,19 @@
1
+{
2
+    "repo": "driftyco/ionicons",
3
+    "development": {},
4
+    "version": "2.0.0",
5
+    "styles": [
6
+        "css/ionicons.css"
7
+    ],
8
+    "name": "Ionicons",
9
+    "dependencies": {},
10
+    "keywords": [],
11
+    "license": "MIT",
12
+    "fonts": [
13
+        "fonts/ionicons.eot",
14
+        "fonts/ionicons.svg",
15
+        "fonts/ionicons.ttf",
16
+        "fonts/ionicons.woff"
17
+    ],
18
+    "description": "The premium icon font for Ionic Framework."
19
+}

+ 36 - 0
www/lib/Ionicons/composer.json

@@ -0,0 +1,36 @@
1
+{
2
+    "name": "driftyco/ionicons",
3
+    "license": [
4
+        "MIT"
5
+    ],
6
+    "extra": {},
7
+    "authors": [
8
+        {
9
+            "homepage": "https://twitter.com/benjsperry",
10
+            "role": "Designer",
11
+            "name": "Ben Sperry",
12
+            "email": "ben@drifty.com"
13
+        },
14
+        {
15
+            "homepage": "https://twitter.com/adamdbradley",
16
+            "role": "Developer",
17
+            "name": "Adam Bradley",
18
+            "email": "adam@drifty.com"
19
+        },
20
+        {
21
+            "homepage": "https://twitter.com/maxlynch",
22
+            "role": "Developer",
23
+            "name": "Max Lynch",
24
+            "email": "max@drifty.com"
25
+        }
26
+    ],
27
+    "keywords": [
28
+        "fonts",
29
+        "icon font",
30
+        "icons",
31
+        "ionic",
32
+        "web font"
33
+    ],
34
+    "homepage": "http://ionicons.com/",
35
+    "description": "The premium icon font for Ionic Framework."
36
+}

File diff suppressed because it is too large
+ 1480 - 0
www/lib/Ionicons/css/ionicons.css


File diff suppressed because it is too large
+ 11 - 0
www/lib/Ionicons/css/ionicons.min.css


BIN
www/lib/Ionicons/fonts/ionicons.eot


File diff suppressed because it is too large
+ 2230 - 0
www/lib/Ionicons/fonts/ionicons.svg


BIN
www/lib/Ionicons/fonts/ionicons.ttf


BIN
www/lib/Ionicons/fonts/ionicons.woff


+ 27 - 0
www/lib/Ionicons/less/_ionicons-font.less

@@ -0,0 +1,27 @@
1
+// Ionicons Font Path
2
+// --------------------------
3
+
4
+@font-face {
5
+ font-family: @ionicons-font-family;
6
+ src:url("@{ionicons-font-path}/ionicons.eot?v=@{ionicons-version}");
7
+ src:url("@{ionicons-font-path}/ionicons.eot?v=@{ionicons-version}#iefix") format("embedded-opentype"),
8
+  url("@{ionicons-font-path}/ionicons.ttf?v=@{ionicons-version}") format("truetype"),
9
+  url("@{ionicons-font-path}/ionicons.woff?v=@{ionicons-version}") format("woff"),
10
+  url("@{ionicons-font-path}/ionicons.svg?v=@{ionicons-version}#Ionicons") format("svg");
11
+ font-weight: normal;
12
+ font-style: normal;
13
+}
14
+
15
+.ion {
16
+  display: inline-block;
17
+  font-family: @ionicons-font-family;
18
+  speak: none;
19
+  font-style: normal;
20
+  font-weight: normal;
21
+  font-variant: normal;
22
+  text-transform: none;
23
+  text-rendering: auto;
24
+  line-height: 1;
25
+  -webkit-font-smoothing: antialiased;
26
+  -moz-osx-font-smoothing: grayscale;
27
+}

File diff suppressed because it is too large
+ 1473 - 0
www/lib/Ionicons/less/_ionicons-icons.less


+ 747 - 0
www/lib/Ionicons/less/_ionicons-variables.less

@@ -0,0 +1,747 @@
1
+/*!
2
+Ionicons, v2.0.0
3
+Created by Ben Sperry for the Ionic Framework, http://ionicons.com/
4
+https://twitter.com/benjsperry  https://twitter.com/ionicframework
5
+MIT License: https://github.com/driftyco/ionicons
6
+*/
7
+// Ionicons Variables
8
+// --------------------------
9
+
10
+@ionicons-font-path: "../fonts";
11
+@ionicons-font-family: "Ionicons";
12
+@ionicons-version: "2.0.0";
13
+@ionicons-prefix: ion-;
14
+
15
+@ionicon-var-alert: "\f101";
16
+@ionicon-var-alert-circled: "\f100";
17
+@ionicon-var-android-add: "\f2c7";
18
+@ionicon-var-android-add-circle: "\f359";
19
+@ionicon-var-android-alarm-clock: "\f35a";
20
+@ionicon-var-android-alert: "\f35b";
21
+@ionicon-var-android-apps: "\f35c";
22
+@ionicon-var-android-archive: "\f2c9";
23
+@ionicon-var-android-arrow-back: "\f2ca";
24
+@ionicon-var-android-arrow-down: "\f35d";
25
+@ionicon-var-android-arrow-dropdown: "\f35f";
26
+@ionicon-var-android-arrow-dropdown-circle: "\f35e";
27
+@ionicon-var-android-arrow-dropleft: "\f361";
28
+@ionicon-var-android-arrow-dropleft-circle: "\f360";
29
+@ionicon-var-android-arrow-dropright: "\f363";
30
+@ionicon-var-android-arrow-dropright-circle: "\f362";
31
+@ionicon-var-android-arrow-dropup: "\f365";
32
+@ionicon-var-android-arrow-dropup-circle: "\f364";
33
+@ionicon-var-android-arrow-forward: "\f30f";
34
+@ionicon-var-android-arrow-up: "\f366";
35
+@ionicon-var-android-attach: "\f367";
36
+@ionicon-var-android-bar: "\f368";
37
+@ionicon-var-android-bicycle: "\f369";
38
+@ionicon-var-android-boat: "\f36a";
39
+@ionicon-var-android-bookmark: "\f36b";
40
+@ionicon-var-android-bulb: "\f36c";
41
+@ionicon-var-android-bus: "\f36d";
42
+@ionicon-var-android-calendar: "\f2d1";
43
+@ionicon-var-android-call: "\f2d2";
44
+@ionicon-var-android-camera: "\f2d3";
45
+@ionicon-var-android-cancel: "\f36e";
46
+@ionicon-var-android-car: "\f36f";
47
+@ionicon-var-android-cart: "\f370";
48
+@ionicon-var-android-chat: "\f2d4";
49
+@ionicon-var-android-checkbox: "\f374";
50
+@ionicon-var-android-checkbox-blank: "\f371";
51
+@ionicon-var-android-checkbox-outline: "\f373";
52
+@ionicon-var-android-checkbox-outline-blank: "\f372";
53
+@ionicon-var-android-checkmark-circle: "\f375";
54
+@ionicon-var-android-clipboard: "\f376";
55
+@ionicon-var-android-close: "\f2d7";
56
+@ionicon-var-android-cloud: "\f37a";
57
+@ionicon-var-android-cloud-circle: "\f377";
58
+@ionicon-var-android-cloud-done: "\f378";
59
+@ionicon-var-android-cloud-outline: "\f379";
60
+@ionicon-var-android-color-palette: "\f37b";
61
+@ionicon-var-android-compass: "\f37c";
62
+@ionicon-var-android-contact: "\f2d8";
63
+@ionicon-var-android-contacts: "\f2d9";
64
+@ionicon-var-android-contract: "\f37d";
65
+@ionicon-var-android-create: "\f37e";
66
+@ionicon-var-android-delete: "\f37f";
67
+@ionicon-var-android-desktop: "\f380";
68
+@ionicon-var-android-document: "\f381";
69
+@ionicon-var-android-done: "\f383";
70
+@ionicon-var-android-done-all: "\f382";
71
+@ionicon-var-android-download: "\f2dd";
72
+@ionicon-var-android-drafts: "\f384";
73
+@ionicon-var-android-exit: "\f385";
74
+@ionicon-var-android-expand: "\f386";
75
+@ionicon-var-android-favorite: "\f388";
76
+@ionicon-var-android-favorite-outline: "\f387";
77
+@ionicon-var-android-film: "\f389";
78
+@ionicon-var-android-folder: "\f2e0";
79
+@ionicon-var-android-folder-open: "\f38a";
80
+@ionicon-var-android-funnel: "\f38b";
81
+@ionicon-var-android-globe: "\f38c";
82
+@ionicon-var-android-hand: "\f2e3";
83
+@ionicon-var-android-hangout: "\f38d";
84
+@ionicon-var-android-happy: "\f38e";
85
+@ionicon-var-android-home: "\f38f";
86
+@ionicon-var-android-image: "\f2e4";
87
+@ionicon-var-android-laptop: "\f390";
88
+@ionicon-var-android-list: "\f391";
89
+@ionicon-var-android-locate: "\f2e9";
90
+@ionicon-var-android-lock: "\f392";
91
+@ionicon-var-android-mail: "\f2eb";
92
+@ionicon-var-android-map: "\f393";
93
+@ionicon-var-android-menu: "\f394";
94
+@ionicon-var-android-microphone: "\f2ec";
95
+@ionicon-var-android-microphone-off: "\f395";
96
+@ionicon-var-android-more-horizontal: "\f396";
97
+@ionicon-var-android-more-vertical: "\f397";
98
+@ionicon-var-android-navigate: "\f398";
99
+@ionicon-var-android-notifications: "\f39b";
100
+@ionicon-var-android-notifications-none: "\f399";
101
+@ionicon-var-android-notifications-off: "\f39a";
102
+@ionicon-var-android-open: "\f39c";
103
+@ionicon-var-android-options: "\f39d";
104
+@ionicon-var-android-people: "\f39e";
105
+@ionicon-var-android-person: "\f3a0";
106
+@ionicon-var-android-person-add: "\f39f";
107
+@ionicon-var-android-phone-landscape: "\f3a1";
108
+@ionicon-var-android-phone-portrait: "\f3a2";
109
+@ionicon-var-android-pin: "\f3a3";
110
+@ionicon-var-android-plane: "\f3a4";
111
+@ionicon-var-android-playstore: "\f2f0";
112
+@ionicon-var-android-print: "\f3a5";
113
+@ionicon-var-android-radio-button-off: "\f3a6";
114
+@ionicon-var-android-radio-button-on: "\f3a7";
115
+@ionicon-var-android-refresh: "\f3a8";
116
+@ionicon-var-android-remove: "\f2f4";
117
+@ionicon-var-android-remove-circle: "\f3a9";
118
+@ionicon-var-android-restaurant: "\f3aa";
119
+@ionicon-var-android-sad: "\f3ab";
120
+@ionicon-var-android-search: "\f2f5";
121
+@ionicon-var-android-send: "\f2f6";
122
+@ionicon-var-android-settings: "\f2f7";
123
+@ionicon-var-android-share: "\f2f8";
124
+@ionicon-var-android-share-alt: "\f3ac";
125
+@ionicon-var-android-star: "\f2fc";
126
+@ionicon-var-android-star-half: "\f3ad";
127
+@ionicon-var-android-star-outline: "\f3ae";
128
+@ionicon-var-android-stopwatch: "\f2fd";
129
+@ionicon-var-android-subway: "\f3af";
130
+@ionicon-var-android-sunny: "\f3b0";
131
+@ionicon-var-android-sync: "\f3b1";
132
+@ionicon-var-android-textsms: "\f3b2";
133
+@ionicon-var-android-time: "\f3b3";
134
+@ionicon-var-android-train: "\f3b4";
135
+@ionicon-var-android-unlock: "\f3b5";
136
+@ionicon-var-android-upload: "\f3b6";
137
+@ionicon-var-android-volume-down: "\f3b7";
138
+@ionicon-var-android-volume-mute: "\f3b8";
139
+@ionicon-var-android-volume-off: "\f3b9";
140
+@ionicon-var-android-volume-up: "\f3ba";
141
+@ionicon-var-android-walk: "\f3bb";
142
+@ionicon-var-android-warning: "\f3bc";
143
+@ionicon-var-android-watch: "\f3bd";
144
+@ionicon-var-android-wifi: "\f305";
145
+@ionicon-var-aperture: "\f313";
146
+@ionicon-var-archive: "\f102";
147
+@ionicon-var-arrow-down-a: "\f103";
148
+@ionicon-var-arrow-down-b: "\f104";
149
+@ionicon-var-arrow-down-c: "\f105";
150
+@ionicon-var-arrow-expand: "\f25e";
151
+@ionicon-var-arrow-graph-down-left: "\f25f";
152
+@ionicon-var-arrow-graph-down-right: "\f260";
153
+@ionicon-var-arrow-graph-up-left: "\f261";
154
+@ionicon-var-arrow-graph-up-right: "\f262";
155
+@ionicon-var-arrow-left-a: "\f106";
156
+@ionicon-var-arrow-left-b: "\f107";
157
+@ionicon-var-arrow-left-c: "\f108";
158
+@ionicon-var-arrow-move: "\f263";
159
+@ionicon-var-arrow-resize: "\f264";
160
+@ionicon-var-arrow-return-left: "\f265";
161
+@ionicon-var-arrow-return-right: "\f266";
162
+@ionicon-var-arrow-right-a: "\f109";
163
+@ionicon-var-arrow-right-b: "\f10a";
164
+@ionicon-var-arrow-right-c: "\f10b";
165
+@ionicon-var-arrow-shrink: "\f267";
166
+@ionicon-var-arrow-swap: "\f268";
167
+@ionicon-var-arrow-up-a: "\f10c";
168
+@ionicon-var-arrow-up-b: "\f10d";
169
+@ionicon-var-arrow-up-c: "\f10e";
170
+@ionicon-var-asterisk: "\f314";
171
+@ionicon-var-at: "\f10f";
172
+@ionicon-var-backspace: "\f3bf";
173
+@ionicon-var-backspace-outline: "\f3be";
174
+@ionicon-var-bag: "\f110";
175
+@ionicon-var-battery-charging: "\f111";
176
+@ionicon-var-battery-empty: "\f112";
177
+@ionicon-var-battery-full: "\f113";
178
+@ionicon-var-battery-half: "\f114";
179
+@ionicon-var-battery-low: "\f115";
180
+@ionicon-var-beaker: "\f269";
181
+@ionicon-var-beer: "\f26a";
182
+@ionicon-var-bluetooth: "\f116";
183
+@ionicon-var-bonfire: "\f315";
184
+@ionicon-var-bookmark: "\f26b";
185
+@ionicon-var-bowtie: "\f3c0";
186
+@ionicon-var-briefcase: "\f26c";
187
+@ionicon-var-bug: "\f2be";
188
+@ionicon-var-calculator: "\f26d";
189
+@ionicon-var-calendar: "\f117";
190
+@ionicon-var-camera: "\f118";
191
+@ionicon-var-card: "\f119";
192
+@ionicon-var-cash: "\f316";
193
+@ionicon-var-chatbox: "\f11b";
194
+@ionicon-var-chatbox-working: "\f11a";
195
+@ionicon-var-chatboxes: "\f11c";
196
+@ionicon-var-chatbubble: "\f11e";
197
+@ionicon-var-chatbubble-working: "\f11d";
198
+@ionicon-var-chatbubbles: "\f11f";
199
+@ionicon-var-checkmark: "\f122";
200
+@ionicon-var-checkmark-circled: "\f120";
201
+@ionicon-var-checkmark-round: "\f121";
202
+@ionicon-var-chevron-down: "\f123";
203
+@ionicon-var-chevron-left: "\f124";
204
+@ionicon-var-chevron-right: "\f125";
205
+@ionicon-var-chevron-up: "\f126";
206
+@ionicon-var-clipboard: "\f127";
207
+@ionicon-var-clock: "\f26e";
208
+@ionicon-var-close: "\f12a";
209
+@ionicon-var-close-circled: "\f128";
210
+@ionicon-var-close-round: "\f129";
211
+@ionicon-var-closed-captioning: "\f317";
212
+@ionicon-var-cloud: "\f12b";
213
+@ionicon-var-code: "\f271";
214
+@ionicon-var-code-download: "\f26f";
215
+@ionicon-var-code-working: "\f270";
216
+@ionicon-var-coffee: "\f272";
217
+@ionicon-var-compass: "\f273";
218
+@ionicon-var-compose: "\f12c";
219
+@ionicon-var-connection-bars: "\f274";
220
+@ionicon-var-contrast: "\f275";
221
+@ionicon-var-crop: "\f3c1";
222
+@ionicon-var-cube: "\f318";
223
+@ionicon-var-disc: "\f12d";
224
+@ionicon-var-document: "\f12f";
225
+@ionicon-var-document-text: "\f12e";
226
+@ionicon-var-drag: "\f130";
227
+@ionicon-var-earth: "\f276";
228
+@ionicon-var-easel: "\f3c2";
229
+@ionicon-var-edit: "\f2bf";
230
+@ionicon-var-egg: "\f277";
231
+@ionicon-var-eject: "\f131";
232
+@ionicon-var-email: "\f132";
233
+@ionicon-var-email-unread: "\f3c3";
234
+@ionicon-var-erlenmeyer-flask: "\f3c5";
235
+@ionicon-var-erlenmeyer-flask-bubbles: "\f3c4";
236
+@ionicon-var-eye: "\f133";
237
+@ionicon-var-eye-disabled: "\f306";
238
+@ionicon-var-female: "\f278";
239
+@ionicon-var-filing: "\f134";
240
+@ionicon-var-film-marker: "\f135";
241
+@ionicon-var-fireball: "\f319";
242
+@ionicon-var-flag: "\f279";
243
+@ionicon-var-flame: "\f31a";
244
+@ionicon-var-flash: "\f137";
245
+@ionicon-var-flash-off: "\f136";
246
+@ionicon-var-folder: "\f139";
247
+@ionicon-var-fork: "\f27a";
248
+@ionicon-var-fork-repo: "\f2c0";
249
+@ionicon-var-forward: "\f13a";
250
+@ionicon-var-funnel: "\f31b";
251
+@ionicon-var-gear-a: "\f13d";
252
+@ionicon-var-gear-b: "\f13e";
253
+@ionicon-var-grid: "\f13f";
254
+@ionicon-var-hammer: "\f27b";
255
+@ionicon-var-happy: "\f31c";
256
+@ionicon-var-happy-outline: "\f3c6";
257
+@ionicon-var-headphone: "\f140";
258
+@ionicon-var-heart: "\f141";
259
+@ionicon-var-heart-broken: "\f31d";
260
+@ionicon-var-help: "\f143";
261
+@ionicon-var-help-buoy: "\f27c";
262
+@ionicon-var-help-circled: "\f142";
263
+@ionicon-var-home: "\f144";
264
+@ionicon-var-icecream: "\f27d";
265
+@ionicon-var-image: "\f147";
266
+@ionicon-var-images: "\f148";
267
+@ionicon-var-information: "\f14a";
268
+@ionicon-var-information-circled: "\f149";
269
+@ionicon-var-ionic: "\f14b";
270
+@ionicon-var-ios-alarm: "\f3c8";
271
+@ionicon-var-ios-alarm-outline: "\f3c7";
272
+@ionicon-var-ios-albums: "\f3ca";
273
+@ionicon-var-ios-albums-outline: "\f3c9";
274
+@ionicon-var-ios-americanfootball: "\f3cc";
275
+@ionicon-var-ios-americanfootball-outline: "\f3cb";
276
+@ionicon-var-ios-analytics: "\f3ce";
277
+@ionicon-var-ios-analytics-outline: "\f3cd";
278
+@ionicon-var-ios-arrow-back: "\f3cf";
279
+@ionicon-var-ios-arrow-down: "\f3d0";
280
+@ionicon-var-ios-arrow-forward: "\f3d1";
281
+@ionicon-var-ios-arrow-left: "\f3d2";
282
+@ionicon-var-ios-arrow-right: "\f3d3";
283
+@ionicon-var-ios-arrow-thin-down: "\f3d4";
284
+@ionicon-var-ios-arrow-thin-left: "\f3d5";
285
+@ionicon-var-ios-arrow-thin-right: "\f3d6";
286
+@ionicon-var-ios-arrow-thin-up: "\f3d7";
287
+@ionicon-var-ios-arrow-up: "\f3d8";
288
+@ionicon-var-ios-at: "\f3da";
289
+@ionicon-var-ios-at-outline: "\f3d9";
290
+@ionicon-var-ios-barcode: "\f3dc";
291
+@ionicon-var-ios-barcode-outline: "\f3db";
292
+@ionicon-var-ios-baseball: "\f3de";
293
+@ionicon-var-ios-baseball-outline: "\f3dd";
294
+@ionicon-var-ios-basketball: "\f3e0";
295
+@ionicon-var-ios-basketball-outline: "\f3df";
296
+@ionicon-var-ios-bell: "\f3e2";
297
+@ionicon-var-ios-bell-outline: "\f3e1";
298
+@ionicon-var-ios-body: "\f3e4";
299
+@ionicon-var-ios-body-outline: "\f3e3";
300
+@ionicon-var-ios-bolt: "\f3e6";
301
+@ionicon-var-ios-bolt-outline: "\f3e5";
302
+@ionicon-var-ios-book: "\f3e8";
303
+@ionicon-var-ios-book-outline: "\f3e7";
304
+@ionicon-var-ios-bookmarks: "\f3ea";
305
+@ionicon-var-ios-bookmarks-outline: "\f3e9";
306
+@ionicon-var-ios-box: "\f3ec";
307
+@ionicon-var-ios-box-outline: "\f3eb";
308
+@ionicon-var-ios-briefcase: "\f3ee";
309
+@ionicon-var-ios-briefcase-outline: "\f3ed";
310
+@ionicon-var-ios-browsers: "\f3f0";
311
+@ionicon-var-ios-browsers-outline: "\f3ef";
312
+@ionicon-var-ios-calculator: "\f3f2";
313
+@ionicon-var-ios-calculator-outline: "\f3f1";
314
+@ionicon-var-ios-calendar: "\f3f4";
315
+@ionicon-var-ios-calendar-outline: "\f3f3";
316
+@ionicon-var-ios-camera: "\f3f6";
317
+@ionicon-var-ios-camera-outline: "\f3f5";
318
+@ionicon-var-ios-cart: "\f3f8";
319
+@ionicon-var-ios-cart-outline: "\f3f7";
320
+@ionicon-var-ios-chatboxes: "\f3fa";
321
+@ionicon-var-ios-chatboxes-outline: "\f3f9";
322
+@ionicon-var-ios-chatbubble: "\f3fc";
323
+@ionicon-var-ios-chatbubble-outline: "\f3fb";
324
+@ionicon-var-ios-checkmark: "\f3ff";
325
+@ionicon-var-ios-checkmark-empty: "\f3fd";
326
+@ionicon-var-ios-checkmark-outline: "\f3fe";
327
+@ionicon-var-ios-circle-filled: "\f400";
328
+@ionicon-var-ios-circle-outline: "\f401";
329
+@ionicon-var-ios-clock: "\f403";
330
+@ionicon-var-ios-clock-outline: "\f402";
331
+@ionicon-var-ios-close: "\f406";
332
+@ionicon-var-ios-close-empty: "\f404";
333
+@ionicon-var-ios-close-outline: "\f405";
334
+@ionicon-var-ios-cloud: "\f40c";
335
+@ionicon-var-ios-cloud-download: "\f408";
336
+@ionicon-var-ios-cloud-download-outline: "\f407";
337
+@ionicon-var-ios-cloud-outline: "\f409";
338
+@ionicon-var-ios-cloud-upload: "\f40b";
339
+@ionicon-var-ios-cloud-upload-outline: "\f40a";
340
+@ionicon-var-ios-cloudy: "\f410";
341
+@ionicon-var-ios-cloudy-night: "\f40e";
342
+@ionicon-var-ios-cloudy-night-outline: "\f40d";
343
+@ionicon-var-ios-cloudy-outline: "\f40f";
344
+@ionicon-var-ios-cog: "\f412";
345
+@ionicon-var-ios-cog-outline: "\f411";
346
+@ionicon-var-ios-color-filter: "\f414";
347
+@ionicon-var-ios-color-filter-outline: "\f413";
348
+@ionicon-var-ios-color-wand: "\f416";
349
+@ionicon-var-ios-color-wand-outline: "\f415";
350
+@ionicon-var-ios-compose: "\f418";
351
+@ionicon-var-ios-compose-outline: "\f417";
352
+@ionicon-var-ios-contact: "\f41a";
353
+@ionicon-var-ios-contact-outline: "\f419";
354
+@ionicon-var-ios-copy: "\f41c";
355
+@ionicon-var-ios-copy-outline: "\f41b";
356
+@ionicon-var-ios-crop: "\f41e";
357
+@ionicon-var-ios-crop-strong: "\f41d";
358
+@ionicon-var-ios-download: "\f420";
359
+@ionicon-var-ios-download-outline: "\f41f";
360
+@ionicon-var-ios-drag: "\f421";
361
+@ionicon-var-ios-email: "\f423";
362
+@ionicon-var-ios-email-outline: "\f422";
363
+@ionicon-var-ios-eye: "\f425";
364
+@ionicon-var-ios-eye-outline: "\f424";
365
+@ionicon-var-ios-fastforward: "\f427";
366
+@ionicon-var-ios-fastforward-outline: "\f426";
367
+@ionicon-var-ios-filing: "\f429";
368
+@ionicon-var-ios-filing-outline: "\f428";
369
+@ionicon-var-ios-film: "\f42b";
370
+@ionicon-var-ios-film-outline: "\f42a";
371
+@ionicon-var-ios-flag: "\f42d";
372
+@ionicon-var-ios-flag-outline: "\f42c";
373
+@ionicon-var-ios-flame: "\f42f";
374
+@ionicon-var-ios-flame-outline: "\f42e";
375
+@ionicon-var-ios-flask: "\f431";
376
+@ionicon-var-ios-flask-outline: "\f430";
377
+@ionicon-var-ios-flower: "\f433";
378
+@ionicon-var-ios-flower-outline: "\f432";
379
+@ionicon-var-ios-folder: "\f435";
380
+@ionicon-var-ios-folder-outline: "\f434";
381
+@ionicon-var-ios-football: "\f437";
382
+@ionicon-var-ios-football-outline: "\f436";
383
+@ionicon-var-ios-game-controller-a: "\f439";
384
+@ionicon-var-ios-game-controller-a-outline: "\f438";
385
+@ionicon-var-ios-game-controller-b: "\f43b";
386
+@ionicon-var-ios-game-controller-b-outline: "\f43a";
387
+@ionicon-var-ios-gear: "\f43d";
388
+@ionicon-var-ios-gear-outline: "\f43c";
389
+@ionicon-var-ios-glasses: "\f43f";
390
+@ionicon-var-ios-glasses-outline: "\f43e";
391
+@ionicon-var-ios-grid-view: "\f441";
392
+@ionicon-var-ios-grid-view-outline: "\f440";
393
+@ionicon-var-ios-heart: "\f443";
394
+@ionicon-var-ios-heart-outline: "\f442";
395
+@ionicon-var-ios-help: "\f446";
396
+@ionicon-var-ios-help-empty: "\f444";
397
+@ionicon-var-ios-help-outline: "\f445";
398
+@ionicon-var-ios-home: "\f448";
399
+@ionicon-var-ios-home-outline: "\f447";
400
+@ionicon-var-ios-infinite: "\f44a";
401
+@ionicon-var-ios-infinite-outline: "\f449";
402
+@ionicon-var-ios-information: "\f44d";
403
+@ionicon-var-ios-information-empty: "\f44b";
404
+@ionicon-var-ios-information-outline: "\f44c";
405
+@ionicon-var-ios-ionic-outline: "\f44e";
406
+@ionicon-var-ios-keypad: "\f450";
407
+@ionicon-var-ios-keypad-outline: "\f44f";
408
+@ionicon-var-ios-lightbulb: "\f452";
409
+@ionicon-var-ios-lightbulb-outline: "\f451";
410
+@ionicon-var-ios-list: "\f454";
411
+@ionicon-var-ios-list-outline: "\f453";
412
+@ionicon-var-ios-location: "\f456";
413
+@ionicon-var-ios-location-outline: "\f455";
414
+@ionicon-var-ios-locked: "\f458";
415
+@ionicon-var-ios-locked-outline: "\f457";
416
+@ionicon-var-ios-loop: "\f45a";
417
+@ionicon-var-ios-loop-strong: "\f459";
418
+@ionicon-var-ios-medical: "\f45c";
419
+@ionicon-var-ios-medical-outline: "\f45b";
420
+@ionicon-var-ios-medkit: "\f45e";
421
+@ionicon-var-ios-medkit-outline: "\f45d";
422
+@ionicon-var-ios-mic: "\f461";
423
+@ionicon-var-ios-mic-off: "\f45f";
424
+@ionicon-var-ios-mic-outline: "\f460";
425
+@ionicon-var-ios-minus: "\f464";
426
+@ionicon-var-ios-minus-empty: "\f462";
427
+@ionicon-var-ios-minus-outline: "\f463";
428
+@ionicon-var-ios-monitor: "\f466";
429
+@ionicon-var-ios-monitor-outline: "\f465";
430
+@ionicon-var-ios-moon: "\f468";
431
+@ionicon-var-ios-moon-outline: "\f467";
432
+@ionicon-var-ios-more: "\f46a";
433
+@ionicon-var-ios-more-outline: "\f469";
434
+@ionicon-var-ios-musical-note: "\f46b";
435
+@ionicon-var-ios-musical-notes: "\f46c";
436
+@ionicon-var-ios-navigate: "\f46e";
437
+@ionicon-var-ios-navigate-outline: "\f46d";
438
+@ionicon-var-ios-nutrition: "\f470";
439
+@ionicon-var-ios-nutrition-outline: "\f46f";
440
+@ionicon-var-ios-paper: "\f472";
441
+@ionicon-var-ios-paper-outline: "\f471";
442
+@ionicon-var-ios-paperplane: "\f474";
443
+@ionicon-var-ios-paperplane-outline: "\f473";
444
+@ionicon-var-ios-partlysunny: "\f476";
445
+@ionicon-var-ios-partlysunny-outline: "\f475";
446
+@ionicon-var-ios-pause: "\f478";
447
+@ionicon-var-ios-pause-outline: "\f477";
448
+@ionicon-var-ios-paw: "\f47a";
449
+@ionicon-var-ios-paw-outline: "\f479";
450
+@ionicon-var-ios-people: "\f47c";
451
+@ionicon-var-ios-people-outline: "\f47b";
452
+@ionicon-var-ios-person: "\f47e";
453
+@ionicon-var-ios-person-outline: "\f47d";
454
+@ionicon-var-ios-personadd: "\f480";
455
+@ionicon-var-ios-personadd-outline: "\f47f";
456
+@ionicon-var-ios-photos: "\f482";
457
+@ionicon-var-ios-photos-outline: "\f481";
458
+@ionicon-var-ios-pie: "\f484";
459
+@ionicon-var-ios-pie-outline: "\f483";
460
+@ionicon-var-ios-pint: "\f486";
461
+@ionicon-var-ios-pint-outline: "\f485";
462
+@ionicon-var-ios-play: "\f488";
463
+@ionicon-var-ios-play-outline: "\f487";
464
+@ionicon-var-ios-plus: "\f48b";
465
+@ionicon-var-ios-plus-empty: "\f489";
466
+@ionicon-var-ios-plus-outline: "\f48a";
467
+@ionicon-var-ios-pricetag: "\f48d";
468
+@ionicon-var-ios-pricetag-outline: "\f48c";
469
+@ionicon-var-ios-pricetags: "\f48f";
470
+@ionicon-var-ios-pricetags-outline: "\f48e";
471
+@ionicon-var-ios-printer: "\f491";
472
+@ionicon-var-ios-printer-outline: "\f490";
473
+@ionicon-var-ios-pulse: "\f493";
474
+@ionicon-var-ios-pulse-strong: "\f492";
475
+@ionicon-var-ios-rainy: "\f495";
476
+@ionicon-var-ios-rainy-outline: "\f494";
477
+@ionicon-var-ios-recording: "\f497";
478
+@ionicon-var-ios-recording-outline: "\f496";
479
+@ionicon-var-ios-redo: "\f499";
480
+@ionicon-var-ios-redo-outline: "\f498";
481
+@ionicon-var-ios-refresh: "\f49c";
482
+@ionicon-var-ios-refresh-empty: "\f49a";
483
+@ionicon-var-ios-refresh-outline: "\f49b";
484
+@ionicon-var-ios-reload: "\f49d";
485
+@ionicon-var-ios-reverse-camera: "\f49f";
486
+@ionicon-var-ios-reverse-camera-outline: "\f49e";
487
+@ionicon-var-ios-rewind: "\f4a1";
488
+@ionicon-var-ios-rewind-outline: "\f4a0";
489
+@ionicon-var-ios-rose: "\f4a3";
490
+@ionicon-var-ios-rose-outline: "\f4a2";
491
+@ionicon-var-ios-search: "\f4a5";
492
+@ionicon-var-ios-search-strong: "\f4a4";
493
+@ionicon-var-ios-settings: "\f4a7";
494
+@ionicon-var-ios-settings-strong: "\f4a6";
495
+@ionicon-var-ios-shuffle: "\f4a9";
496
+@ionicon-var-ios-shuffle-strong: "\f4a8";
497
+@ionicon-var-ios-skipbackward: "\f4ab";
498
+@ionicon-var-ios-skipbackward-outline: "\f4aa";
499
+@ionicon-var-ios-skipforward: "\f4ad";
500
+@ionicon-var-ios-skipforward-outline: "\f4ac";
501
+@ionicon-var-ios-snowy: "\f4ae";
502
+@ionicon-var-ios-speedometer: "\f4b0";
503
+@ionicon-var-ios-speedometer-outline: "\f4af";
504
+@ionicon-var-ios-star: "\f4b3";
505
+@ionicon-var-ios-star-half: "\f4b1";
506
+@ionicon-var-ios-star-outline: "\f4b2";
507
+@ionicon-var-ios-stopwatch: "\f4b5";
508
+@ionicon-var-ios-stopwatch-outline: "\f4b4";
509
+@ionicon-var-ios-sunny: "\f4b7";
510
+@ionicon-var-ios-sunny-outline: "\f4b6";
511
+@ionicon-var-ios-telephone: "\f4b9";
512
+@ionicon-var-ios-telephone-outline: "\f4b8";
513
+@ionicon-var-ios-tennisball: "\f4bb";
514
+@ionicon-var-ios-tennisball-outline: "\f4ba";
515
+@ionicon-var-ios-thunderstorm: "\f4bd";
516
+@ionicon-var-ios-thunderstorm-outline: "\f4bc";
517
+@ionicon-var-ios-time: "\f4bf";
518
+@ionicon-var-ios-time-outline: "\f4be";
519
+@ionicon-var-ios-timer: "\f4c1";
520
+@ionicon-var-ios-timer-outline: "\f4c0";
521
+@ionicon-var-ios-toggle: "\f4c3";
522
+@ionicon-var-ios-toggle-outline: "\f4c2";
523
+@ionicon-var-ios-trash: "\f4c5";
524
+@ionicon-var-ios-trash-outline: "\f4c4";
525
+@ionicon-var-ios-undo: "\f4c7";
526
+@ionicon-var-ios-undo-outline: "\f4c6";
527
+@ionicon-var-ios-unlocked: "\f4c9";
528
+@ionicon-var-ios-unlocked-outline: "\f4c8";
529
+@ionicon-var-ios-upload: "\f4cb";
530
+@ionicon-var-ios-upload-outline: "\f4ca";
531
+@ionicon-var-ios-videocam: "\f4cd";
532
+@ionicon-var-ios-videocam-outline: "\f4cc";
533
+@ionicon-var-ios-volume-high: "\f4ce";
534
+@ionicon-var-ios-volume-low: "\f4cf";
535
+@ionicon-var-ios-wineglass: "\f4d1";
536
+@ionicon-var-ios-wineglass-outline: "\f4d0";
537
+@ionicon-var-ios-world: "\f4d3";
538
+@ionicon-var-ios-world-outline: "\f4d2";
539
+@ionicon-var-ipad: "\f1f9";
540
+@ionicon-var-iphone: "\f1fa";
541
+@ionicon-var-ipod: "\f1fb";
542
+@ionicon-var-jet: "\f295";
543
+@ionicon-var-key: "\f296";
544
+@ionicon-var-knife: "\f297";
545
+@ionicon-var-laptop: "\f1fc";
546
+@ionicon-var-leaf: "\f1fd";
547
+@ionicon-var-levels: "\f298";
548
+@ionicon-var-lightbulb: "\f299";
549
+@ionicon-var-link: "\f1fe";
550
+@ionicon-var-load-a: "\f29a";
551
+@ionicon-var-load-b: "\f29b";
552
+@ionicon-var-load-c: "\f29c";
553
+@ionicon-var-load-d: "\f29d";
554
+@ionicon-var-location: "\f1ff";
555
+@ionicon-var-lock-combination: "\f4d4";
556
+@ionicon-var-locked: "\f200";
557
+@ionicon-var-log-in: "\f29e";
558
+@ionicon-var-log-out: "\f29f";
559
+@ionicon-var-loop: "\f201";
560
+@ionicon-var-magnet: "\f2a0";
561
+@ionicon-var-male: "\f2a1";
562
+@ionicon-var-man: "\f202";
563
+@ionicon-var-map: "\f203";
564
+@ionicon-var-medkit: "\f2a2";
565
+@ionicon-var-merge: "\f33f";
566
+@ionicon-var-mic-a: "\f204";
567
+@ionicon-var-mic-b: "\f205";
568
+@ionicon-var-mic-c: "\f206";
569
+@ionicon-var-minus: "\f209";
570
+@ionicon-var-minus-circled: "\f207";
571
+@ionicon-var-minus-round: "\f208";
572
+@ionicon-var-model-s: "\f2c1";
573
+@ionicon-var-monitor: "\f20a";
574
+@ionicon-var-more: "\f20b";
575
+@ionicon-var-mouse: "\f340";
576
+@ionicon-var-music-note: "\f20c";
577
+@ionicon-var-navicon: "\f20e";
578
+@ionicon-var-navicon-round: "\f20d";
579
+@ionicon-var-navigate: "\f2a3";
580
+@ionicon-var-network: "\f341";
581
+@ionicon-var-no-smoking: "\f2c2";
582
+@ionicon-var-nuclear: "\f2a4";
583
+@ionicon-var-outlet: "\f342";
584
+@ionicon-var-paintbrush: "\f4d5";
585
+@ionicon-var-paintbucket: "\f4d6";
586
+@ionicon-var-paper-airplane: "\f2c3";
587
+@ionicon-var-paperclip: "\f20f";
588
+@ionicon-var-pause: "\f210";
589
+@ionicon-var-person: "\f213";
590
+@ionicon-var-person-add: "\f211";
591
+@ionicon-var-person-stalker: "\f212";
592
+@ionicon-var-pie-graph: "\f2a5";
593
+@ionicon-var-pin: "\f2a6";
594
+@ionicon-var-pinpoint: "\f2a7";
595
+@ionicon-var-pizza: "\f2a8";
596
+@ionicon-var-plane: "\f214";
597
+@ionicon-var-planet: "\f343";
598
+@ionicon-var-play: "\f215";
599
+@ionicon-var-playstation: "\f30a";
600
+@ionicon-var-plus: "\f218";
601
+@ionicon-var-plus-circled: "\f216";
602
+@ionicon-var-plus-round: "\f217";
603
+@ionicon-var-podium: "\f344";
604
+@ionicon-var-pound: "\f219";
605
+@ionicon-var-power: "\f2a9";
606
+@ionicon-var-pricetag: "\f2aa";
607
+@ionicon-var-pricetags: "\f2ab";
608
+@ionicon-var-printer: "\f21a";
609
+@ionicon-var-pull-request: "\f345";
610
+@ionicon-var-qr-scanner: "\f346";
611
+@ionicon-var-quote: "\f347";
612
+@ionicon-var-radio-waves: "\f2ac";
613
+@ionicon-var-record: "\f21b";
614
+@ionicon-var-refresh: "\f21c";
615
+@ionicon-var-reply: "\f21e";
616
+@ionicon-var-reply-all: "\f21d";
617
+@ionicon-var-ribbon-a: "\f348";
618
+@ionicon-var-ribbon-b: "\f349";
619
+@ionicon-var-sad: "\f34a";
620
+@ionicon-var-sad-outline: "\f4d7";
621
+@ionicon-var-scissors: "\f34b";
622
+@ionicon-var-search: "\f21f";
623
+@ionicon-var-settings: "\f2ad";
624
+@ionicon-var-share: "\f220";
625
+@ionicon-var-shuffle: "\f221";
626
+@ionicon-var-skip-backward: "\f222";
627
+@ionicon-var-skip-forward: "\f223";
628
+@ionicon-var-social-android: "\f225";
629
+@ionicon-var-social-android-outline: "\f224";
630
+@ionicon-var-social-angular: "\f4d9";
631
+@ionicon-var-social-angular-outline: "\f4d8";
632
+@ionicon-var-social-apple: "\f227";
633
+@ionicon-var-social-apple-outline: "\f226";
634
+@ionicon-var-social-bitcoin: "\f2af";
635
+@ionicon-var-social-bitcoin-outline: "\f2ae";
636
+@ionicon-var-social-buffer: "\f229";
637
+@ionicon-var-social-buffer-outline: "\f228";
638
+@ionicon-var-social-chrome: "\f4db";
639
+@ionicon-var-social-chrome-outline: "\f4da";
640
+@ionicon-var-social-codepen: "\f4dd";
641
+@ionicon-var-social-codepen-outline: "\f4dc";
642
+@ionicon-var-social-css3: "\f4df";
643
+@ionicon-var-social-css3-outline: "\f4de";
644
+@ionicon-var-social-designernews: "\f22b";
645
+@ionicon-var-social-designernews-outline: "\f22a";
646
+@ionicon-var-social-dribbble: "\f22d";
647
+@ionicon-var-social-dribbble-outline: "\f22c";
648
+@ionicon-var-social-dropbox: "\f22f";
649
+@ionicon-var-social-dropbox-outline: "\f22e";
650
+@ionicon-var-social-euro: "\f4e1";
651
+@ionicon-var-social-euro-outline: "\f4e0";
652
+@ionicon-var-social-facebook: "\f231";
653
+@ionicon-var-social-facebook-outline: "\f230";
654
+@ionicon-var-social-foursquare: "\f34d";
655
+@ionicon-var-social-foursquare-outline: "\f34c";
656
+@ionicon-var-social-freebsd-devil: "\f2c4";
657
+@ionicon-var-social-github: "\f233";
658
+@ionicon-var-social-github-outline: "\f232";
659
+@ionicon-var-social-google: "\f34f";
660
+@ionicon-var-social-google-outline: "\f34e";
661
+@ionicon-var-social-googleplus: "\f235";
662
+@ionicon-var-social-googleplus-outline: "\f234";
663
+@ionicon-var-social-hackernews: "\f237";
664
+@ionicon-var-social-hackernews-outline: "\f236";
665
+@ionicon-var-social-html5: "\f4e3";
666
+@ionicon-var-social-html5-outline: "\f4e2";
667
+@ionicon-var-social-instagram: "\f351";
668
+@ionicon-var-social-instagram-outline: "\f350";
669
+@ionicon-var-social-javascript: "\f4e5";
670
+@ionicon-var-social-javascript-outline: "\f4e4";
671
+@ionicon-var-social-linkedin: "\f239";
672
+@ionicon-var-social-linkedin-outline: "\f238";
673
+@ionicon-var-social-markdown: "\f4e6";
674
+@ionicon-var-social-nodejs: "\f4e7";
675
+@ionicon-var-social-octocat: "\f4e8";
676
+@ionicon-var-social-pinterest: "\f2b1";
677
+@ionicon-var-social-pinterest-outline: "\f2b0";
678
+@ionicon-var-social-python: "\f4e9";
679
+@ionicon-var-social-reddit: "\f23b";
680
+@ionicon-var-social-reddit-outline: "\f23a";
681
+@ionicon-var-social-rss: "\f23d";
682
+@ionicon-var-social-rss-outline: "\f23c";
683
+@ionicon-var-social-sass: "\f4ea";
684
+@ionicon-var-social-skype: "\f23f";
685
+@ionicon-var-social-skype-outline: "\f23e";
686
+@ionicon-var-social-snapchat: "\f4ec";
687
+@ionicon-var-social-snapchat-outline: "\f4eb";
688
+@ionicon-var-social-tumblr: "\f241";
689
+@ionicon-var-social-tumblr-outline: "\f240";
690
+@ionicon-var-social-tux: "\f2c5";
691
+@ionicon-var-social-twitch: "\f4ee";
692
+@ionicon-var-social-twitch-outline: "\f4ed";
693
+@ionicon-var-social-twitter: "\f243";
694
+@ionicon-var-social-twitter-outline: "\f242";
695
+@ionicon-var-social-usd: "\f353";
696
+@ionicon-var-social-usd-outline: "\f352";
697
+@ionicon-var-social-vimeo: "\f245";
698
+@ionicon-var-social-vimeo-outline: "\f244";
699
+@ionicon-var-social-whatsapp: "\f4f0";
700
+@ionicon-var-social-whatsapp-outline: "\f4ef";
701
+@ionicon-var-social-windows: "\f247";
702
+@ionicon-var-social-windows-outline: "\f246";
703
+@ionicon-var-social-wordpress: "\f249";
704
+@ionicon-var-social-wordpress-outline: "\f248";
705
+@ionicon-var-social-yahoo: "\f24b";
706
+@ionicon-var-social-yahoo-outline: "\f24a";
707
+@ionicon-var-social-yen: "\f4f2";
708
+@ionicon-var-social-yen-outline: "\f4f1";
709
+@ionicon-var-social-youtube: "\f24d";
710
+@ionicon-var-social-youtube-outline: "\f24c";
711
+@ionicon-var-soup-can: "\f4f4";
712
+@ionicon-var-soup-can-outline: "\f4f3";
713
+@ionicon-var-speakerphone: "\f2b2";
714
+@ionicon-var-speedometer: "\f2b3";
715
+@ionicon-var-spoon: "\f2b4";
716
+@ionicon-var-star: "\f24e";
717
+@ionicon-var-stats-bars: "\f2b5";
718
+@ionicon-var-steam: "\f30b";
719
+@ionicon-var-stop: "\f24f";
720
+@ionicon-var-thermometer: "\f2b6";
721
+@ionicon-var-thumbsdown: "\f250";
722
+@ionicon-var-thumbsup: "\f251";
723
+@ionicon-var-toggle: "\f355";
724
+@ionicon-var-toggle-filled: "\f354";
725
+@ionicon-var-transgender: "\f4f5";
726
+@ionicon-var-trash-a: "\f252";
727
+@ionicon-var-trash-b: "\f253";
728
+@ionicon-var-trophy: "\f356";
729
+@ionicon-var-tshirt: "\f4f7";
730
+@ionicon-var-tshirt-outline: "\f4f6";
731
+@ionicon-var-umbrella: "\f2b7";
732
+@ionicon-var-university: "\f357";
733
+@ionicon-var-unlocked: "\f254";
734
+@ionicon-var-upload: "\f255";
735
+@ionicon-var-usb: "\f2b8";
736
+@ionicon-var-videocamera: "\f256";
737
+@ionicon-var-volume-high: "\f257";
738
+@ionicon-var-volume-low: "\f258";
739
+@ionicon-var-volume-medium: "\f259";
740
+@ionicon-var-volume-mute: "\f25a";
741
+@ionicon-var-wand: "\f358";
742
+@ionicon-var-waterdrop: "\f25b";
743
+@ionicon-var-wifi: "\f25c";
744
+@ionicon-var-wineglass: "\f2b9";
745
+@ionicon-var-woman: "\f25d";
746
+@ionicon-var-wrench: "\f2ba";
747
+@ionicon-var-xbox: "\f30c";

+ 3 - 0
www/lib/Ionicons/less/ionicons.less

@@ -0,0 +1,3 @@
1
+@import "_ionicons-variables";
2
+@import "_ionicons-font";
3
+@import "_ionicons-icons";

BIN
www/lib/Ionicons/png/512/alert-circled.png


+ 0 - 0
www/lib/Ionicons/png/512/alert.png


Some files were not shown because too many files changed in this diff

tum/whitesports - Gogs: Simplico Git Service

No Description

module.audio.mp3.php 101KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163
  1. <?php
  2. /////////////////////////////////////////////////////////////////
  3. /// getID3() by James Heinrich <info@getid3.org> //
  4. // available at https://github.com/JamesHeinrich/getID3 //
  5. // or https://www.getid3.org //
  6. // or http://getid3.sourceforge.net //
  7. // see readme.txt for more details //
  8. /////////////////////////////////////////////////////////////////
  9. // //
  10. // module.audio.mp3.php //
  11. // module for analyzing MP3 files //
  12. // dependencies: NONE //
  13. // ///
  14. /////////////////////////////////////////////////////////////////
  15. if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
  16. exit;
  17. }
  18. // number of frames to scan to determine if MPEG-audio sequence is valid
  19. // Lower this number to 5-20 for faster scanning
  20. // Increase this number to 50+ for most accurate detection of valid VBR/CBR
  21. // mpeg-audio streams
  22. define('GETID3_MP3_VALID_CHECK_FRAMES', 35);
  23. class getid3_mp3 extends getid3_handler
  24. {
  25. /**
  26. * Forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow,
  27. * unrecommended, but may provide data from otherwise-unusable files.
  28. *
  29. * @var bool
  30. */
  31. public $allow_bruteforce = false;
  32. /**
  33. * @return bool
  34. */
  35. public function Analyze() {
  36. $info = &$this->getid3->info;
  37. $initialOffset = $info['avdataoffset'];
  38. if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) {
  39. if ($this->allow_bruteforce) {
  40. $this->error('Rescanning file in BruteForce mode');
  41. $this->getOnlyMPEGaudioInfoBruteForce();
  42. }
  43. }
  44. if (isset($info['mpeg']['audio']['bitrate_mode'])) {
  45. $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
  46. }
  47. if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) {
  48. $synchoffsetwarning = 'Unknown data before synch ';
  49. if (isset($info['id3v2']['headerlength'])) {
  50. $synchoffsetwarning .= '(ID3v2 header ends at '.$info['id3v2']['headerlength'].', then '.($info['avdataoffset'] - $info['id3v2']['headerlength']).' bytes garbage, ';
  51. } elseif ($initialOffset > 0) {
  52. $synchoffsetwarning .= '(should be at '.$initialOffset.', ';
  53. } else {
  54. $synchoffsetwarning .= '(should be at beginning of file, ';
  55. }
  56. $synchoffsetwarning .= 'synch detected at '.$info['avdataoffset'].')';
  57. if (isset($info['audio']['bitrate_mode']) && ($info['audio']['bitrate_mode'] == 'cbr')) {
  58. if (!empty($info['id3v2']['headerlength']) && (($info['avdataoffset'] - $info['id3v2']['headerlength']) == $info['mpeg']['audio']['framelength'])) {
  59. $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90-3.92) DLL in CBR mode.';
  60. $info['audio']['codec'] = 'LAME';
  61. $CurrentDataLAMEversionString = 'LAME3.';
  62. } elseif (empty($info['id3v2']['headerlength']) && ($info['avdataoffset'] == $info['mpeg']['audio']['framelength'])) {
  63. $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90 - 3.92) DLL in CBR mode.';
  64. $info['audio']['codec'] = 'LAME';
  65. $CurrentDataLAMEversionString = 'LAME3.';
  66. }
  67. }
  68. $this->warning($synchoffsetwarning);
  69. }
  70. if (isset($info['mpeg']['audio']['LAME'])) {
  71. $info['audio']['codec'] = 'LAME';
  72. if (!empty($info['mpeg']['audio']['LAME']['long_version'])) {
  73. $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['long_version'], "\x00");
  74. } elseif (!empty($info['mpeg']['audio']['LAME']['short_version'])) {
  75. $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['short_version'], "\x00");
  76. }
  77. }
  78. $CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : (isset($info['audio']['encoder']) ? $info['audio']['encoder'] : ''));
  79. if (!empty($CurrentDataLAMEversionString) && (substr($CurrentDataLAMEversionString, 0, 6) == 'LAME3.') && !preg_match('[0-9\)]', substr($CurrentDataLAMEversionString, -1))) {
  80. // a version number of LAME that does not end with a number like "LAME3.92"
  81. // or with a closing parenthesis like "LAME3.88 (alpha)"
  82. // or a version of LAME with the LAMEtag-not-filled-in-DLL-mode bug (3.90-3.92)
  83. // not sure what the actual last frame length will be, but will be less than or equal to 1441
  84. $PossiblyLongerLAMEversion_FrameLength = 1441;
  85. // Not sure what version of LAME this is - look in padding of last frame for longer version string
  86. $PossibleLAMEversionStringOffset = $info['avdataend'] - $PossiblyLongerLAMEversion_FrameLength;
  87. $this->fseek($PossibleLAMEversionStringOffset);
  88. $PossiblyLongerLAMEversion_Data = $this->fread($PossiblyLongerLAMEversion_FrameLength);
  89. switch (substr($CurrentDataLAMEversionString, -1)) {
  90. case 'a':
  91. case 'b':
  92. // "LAME3.94a" will have a longer version string of "LAME3.94 (alpha)" for example
  93. // need to trim off "a" to match longer string
  94. $CurrentDataLAMEversionString = substr($CurrentDataLAMEversionString, 0, -1);
  95. break;
  96. }
  97. if (($PossiblyLongerLAMEversion_String = strstr($PossiblyLongerLAMEversion_Data, $CurrentDataLAMEversionString)) !== false) {
  98. if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) {
  99. $PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)"
  100. if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) {
  101. $info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString;
  102. }
  103. }
  104. }
  105. }
  106. if (!empty($info['audio']['encoder'])) {
  107. $info['audio']['encoder'] = rtrim($info['audio']['encoder'], "\x00 ");
  108. }
  109. switch (isset($info['mpeg']['audio']['layer']) ? $info['mpeg']['audio']['layer'] : '') {
  110. case 1:
  111. case 2:
  112. $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer'];
  113. break;
  114. }
  115. if (isset($info['fileformat']) && ($info['fileformat'] == 'mp3')) {
  116. switch ($info['audio']['dataformat']) {
  117. case 'mp1':
  118. case 'mp2':
  119. case 'mp3':
  120. $info['fileformat'] = $info['audio']['dataformat'];
  121. break;
  122. default:
  123. $this->warning('Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"');
  124. break;
  125. }
  126. }
  127. if (empty($info['fileformat'])) {
  128. unset($info['fileformat']);
  129. unset($info['audio']['bitrate_mode']);
  130. unset($info['avdataoffset']);
  131. unset($info['avdataend']);
  132. return false;
  133. }
  134. $info['mime_type'] = 'audio/mpeg';
  135. $info['audio']['lossless'] = false;
  136. // Calculate playtime
  137. if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) {
  138. // https://github.com/JamesHeinrich/getID3/issues/161
  139. // VBR header frame contains ~0.026s of silent audio data, but is not actually part of the original encoding and should be ignored
  140. $xingVBRheaderFrameLength = ((isset($info['mpeg']['audio']['VBR_frames']) && isset($info['mpeg']['audio']['framelength'])) ? $info['mpeg']['audio']['framelength'] : 0);
  141. $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset'] - $xingVBRheaderFrameLength) * 8 / $info['audio']['bitrate'];
  142. }
  143. $info['audio']['encoder_options'] = $this->GuessEncoderOptions();
  144. return true;
  145. }
  146. /**
  147. * @return string
  148. */
  149. public function GuessEncoderOptions() {
  150. // shortcuts
  151. $info = &$this->getid3->info;
  152. $thisfile_mpeg_audio = array();
  153. $thisfile_mpeg_audio_lame = array();
  154. if (!empty($info['mpeg']['audio'])) {
  155. $thisfile_mpeg_audio = &$info['mpeg']['audio'];
  156. if (!empty($thisfile_mpeg_audio['LAME'])) {
  157. $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME'];
  158. }
  159. }
  160. $encoder_options = '';
  161. static $NamedPresetBitrates = array(16, 24, 40, 56, 112, 128, 160, 192, 256);
  162. if (isset($thisfile_mpeg_audio['VBR_method']) && ($thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) {
  163. $encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality'];
  164. } elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && isset($thisfile_mpeg_audio_lame['preset_used_id']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) {
  165. $encoder_options = $thisfile_mpeg_audio_lame['preset_used'];
  166. } elseif (!empty($thisfile_mpeg_audio_lame['vbr_quality'])) {
  167. static $KnownEncoderValues = array();
  168. if (empty($KnownEncoderValues)) {
  169. //$KnownEncoderValues[abrbitrate_minbitrate][vbr_quality][raw_vbr_method][raw_noise_shaping][raw_stereo_mode][ath_type][lowpass_frequency] = 'preset name';
  170. $KnownEncoderValues[0xFF][58][1][1][3][2][20500] = '--alt-preset insane'; // 3.90, 3.90.1, 3.92
  171. $KnownEncoderValues[0xFF][58][1][1][3][2][20600] = '--alt-preset insane'; // 3.90.2, 3.90.3, 3.91
  172. $KnownEncoderValues[0xFF][57][1][1][3][4][20500] = '--alt-preset insane'; // 3.94, 3.95
  173. $KnownEncoderValues['**'][78][3][2][3][2][19500] = '--alt-preset extreme'; // 3.90, 3.90.1, 3.92
  174. $KnownEncoderValues['**'][78][3][2][3][2][19600] = '--alt-preset extreme'; // 3.90.2, 3.91
  175. $KnownEncoderValues['**'][78][3][1][3][2][19600] = '--alt-preset extreme'; // 3.90.3
  176. $KnownEncoderValues['**'][78][4][2][3][2][19500] = '--alt-preset fast extreme'; // 3.90, 3.90.1, 3.92
  177. $KnownEncoderValues['**'][78][4][2][3][2][19600] = '--alt-preset fast extreme'; // 3.90.2, 3.90.3, 3.91
  178. $KnownEncoderValues['**'][78][3][2][3][4][19000] = '--alt-preset standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
  179. $KnownEncoderValues['**'][78][3][1][3][4][19000] = '--alt-preset standard'; // 3.90.3
  180. $KnownEncoderValues['**'][78][4][2][3][4][19000] = '--alt-preset fast standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
  181. $KnownEncoderValues['**'][78][4][1][3][4][19000] = '--alt-preset fast standard'; // 3.90.3
  182. $KnownEncoderValues['**'][88][4][1][3][3][19500] = '--r3mix'; // 3.90, 3.90.1, 3.92
  183. $KnownEncoderValues['**'][88][4][1][3][3][19600] = '--r3mix'; // 3.90.2, 3.90.3, 3.91
  184. $KnownEncoderValues['**'][67][4][1][3][4][18000] = '--r3mix'; // 3.94, 3.95
  185. $KnownEncoderValues['**'][68][3][2][3][4][18000] = '--alt-preset medium'; // 3.90.3
  186. $KnownEncoderValues['**'][68][4][2][3][4][18000] = '--alt-preset fast medium'; // 3.90.3
  187. $KnownEncoderValues[0xFF][99][1][1][1][2][0] = '--preset studio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
  188. $KnownEncoderValues[0xFF][58][2][1][3][2][20600] = '--preset studio'; // 3.90.3, 3.93.1
  189. $KnownEncoderValues[0xFF][58][2][1][3][2][20500] = '--preset studio'; // 3.93
  190. $KnownEncoderValues[0xFF][57][2][1][3][4][20500] = '--preset studio'; // 3.94, 3.95
  191. $KnownEncoderValues[0xC0][88][1][1][1][2][0] = '--preset cd'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
  192. $KnownEncoderValues[0xC0][58][2][2][3][2][19600] = '--preset cd'; // 3.90.3, 3.93.1
  193. $KnownEncoderValues[0xC0][58][2][2][3][2][19500] = '--preset cd'; // 3.93
  194. $KnownEncoderValues[0xC0][57][2][1][3][4][19500] = '--preset cd'; // 3.94, 3.95
  195. $KnownEncoderValues[0xA0][78][1][1][3][2][18000] = '--preset hifi'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
  196. $KnownEncoderValues[0xA0][58][2][2][3][2][18000] = '--preset hifi'; // 3.90.3, 3.93, 3.93.1
  197. $KnownEncoderValues[0xA0][57][2][1][3][4][18000] = '--preset hifi'; // 3.94, 3.95
  198. $KnownEncoderValues[0x80][67][1][1][3][2][18000] = '--preset tape'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
  199. $KnownEncoderValues[0x80][67][1][1][3][2][15000] = '--preset radio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
  200. $KnownEncoderValues[0x70][67][1][1][3][2][15000] = '--preset fm'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
  201. $KnownEncoderValues[0x70][58][2][2][3][2][16000] = '--preset tape/radio/fm'; // 3.90.3, 3.93, 3.93.1
  202. $KnownEncoderValues[0x70][57][2][1][3][4][16000] = '--preset tape/radio/fm'; // 3.94, 3.95
  203. $KnownEncoderValues[0x38][58][2][2][0][2][10000] = '--preset voice'; // 3.90.3, 3.93, 3.93.1
  204. $KnownEncoderValues[0x38][57][2][1][0][4][15000] = '--preset voice'; // 3.94, 3.95
  205. $KnownEncoderValues[0x38][57][2][1][0][4][16000] = '--preset voice'; // 3.94a14
  206. $KnownEncoderValues[0x28][65][1][1][0][2][7500] = '--preset mw-us'; // 3.90, 3.90.1, 3.92
  207. $KnownEncoderValues[0x28][65][1][1][0][2][7600] = '--preset mw-us'; // 3.90.2, 3.91
  208. $KnownEncoderValues[0x28][58][2][2][0][2][7000] = '--preset mw-us'; // 3.90.3, 3.93, 3.93.1
  209. $KnownEncoderValues[0x28][57][2][1][0][4][10500] = '--preset mw-us'; // 3.94, 3.95
  210. $KnownEncoderValues[0x28][57][2][1][0][4][11200] = '--preset mw-us'; // 3.94a14
  211. $KnownEncoderValues[0x28][57][2][1][0][4][8800] = '--preset mw-us'; // 3.94a15
  212. $KnownEncoderValues[0x18][58][2][2][0][2][4000] = '--preset phon+/lw/mw-eu/sw'; // 3.90.3, 3.93.1
  213. $KnownEncoderValues[0x18][58][2][2][0][2][3900] = '--preset phon+/lw/mw-eu/sw'; // 3.93
  214. $KnownEncoderValues[0x18][57][2][1][0][4][5900] = '--preset phon+/lw/mw-eu/sw'; // 3.94, 3.95
  215. $KnownEncoderValues[0x18][57][2][1][0][4][6200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a14
  216. $KnownEncoderValues[0x18][57][2][1][0][4][3200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a15
  217. $KnownEncoderValues[0x10][58][2][2][0][2][3800] = '--preset phone'; // 3.90.3, 3.93.1
  218. $KnownEncoderValues[0x10][58][2][2][0][2][3700] = '--preset phone'; // 3.93
  219. $KnownEncoderValues[0x10][57][2][1][0][4][5600] = '--preset phone'; // 3.94, 3.95
  220. }
  221. 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']])) {
  222. $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']];
  223. } 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']])) {
  224. $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']];
  225. } elseif ($info['audio']['bitrate_mode'] == 'vbr') {
  226. // http://gabriel.mp3-tech.org/mp3infotag.html
  227. // int Quality = (100 - 10 * gfp->VBR_q - gfp->quality)h
  228. $LAME_V_value = 10 - ceil($thisfile_mpeg_audio_lame['vbr_quality'] / 10);
  229. $LAME_q_value = 100 - $thisfile_mpeg_audio_lame['vbr_quality'] - ($LAME_V_value * 10);
  230. $encoder_options = '-V'.$LAME_V_value.' -q'.$LAME_q_value;
  231. } elseif ($info['audio']['bitrate_mode'] == 'cbr') {
  232. $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
  233. } else {
  234. $encoder_options = strtoupper($info['audio']['bitrate_mode']);
  235. }
  236. } elseif (!empty($thisfile_mpeg_audio_lame['bitrate_abr'])) {
  237. $encoder_options = 'ABR'.$thisfile_mpeg_audio_lame['bitrate_abr'];
  238. } elseif (!empty($info['audio']['bitrate'])) {
  239. if ($info['audio']['bitrate_mode'] == 'cbr') {
  240. $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
  241. } else {
  242. $encoder_options = strtoupper($info['audio']['bitrate_mode']);
  243. }
  244. }
  245. if (!empty($thisfile_mpeg_audio_lame['bitrate_min'])) {
  246. $encoder_options .= ' -b'.$thisfile_mpeg_audio_lame['bitrate_min'];
  247. }
  248. if (!empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']) || !empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'])) {
  249. $encoder_options .= ' --nogap';
  250. }
  251. if (!empty($thisfile_mpeg_audio_lame['lowpass_frequency'])) {
  252. $ExplodedOptions = explode(' ', $encoder_options, 4);
  253. if ($ExplodedOptions[0] == '--r3mix') {
  254. $ExplodedOptions[1] = 'r3mix';
  255. }
  256. switch ($ExplodedOptions[0]) {
  257. case '--preset':
  258. case '--alt-preset':
  259. case '--r3mix':
  260. if ($ExplodedOptions[1] == 'fast') {
  261. $ExplodedOptions[1] .= ' '.$ExplodedOptions[2];
  262. }
  263. switch ($ExplodedOptions[1]) {
  264. case 'portable':
  265. case 'medium':
  266. case 'standard':
  267. case 'extreme':
  268. case 'insane':
  269. case 'fast portable':
  270. case 'fast medium':
  271. case 'fast standard':
  272. case 'fast extreme':
  273. case 'fast insane':
  274. case 'r3mix':
  275. static $ExpectedLowpass = array(
  276. 'insane|20500' => 20500,
  277. 'insane|20600' => 20600, // 3.90.2, 3.90.3, 3.91
  278. 'medium|18000' => 18000,
  279. 'fast medium|18000' => 18000,
  280. 'extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95
  281. 'extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1
  282. 'fast extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95
  283. 'fast extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1
  284. 'standard|19000' => 19000,
  285. 'fast standard|19000' => 19000,
  286. 'r3mix|19500' => 19500, // 3.90, 3.90.1, 3.92
  287. 'r3mix|19600' => 19600, // 3.90.2, 3.90.3, 3.91
  288. 'r3mix|18000' => 18000, // 3.94, 3.95
  289. );
  290. 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))) {
  291. $encoder_options .= ' --lowpass '.$thisfile_mpeg_audio_lame['lowpass_frequency'];
  292. }
  293. break;
  294. default:
  295. break;
  296. }
  297. break;
  298. }
  299. }
  300. if (isset($thisfile_mpeg_audio_lame['raw']['source_sample_freq'])) {
  301. if (($thisfile_mpeg_audio['sample_rate'] == 44100) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 1)) {
  302. $encoder_options .= ' --resample 44100';
  303. } elseif (($thisfile_mpeg_audio['sample_rate'] == 48000) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 2)) {
  304. $encoder_options .= ' --resample 48000';
  305. } elseif ($thisfile_mpeg_audio['sample_rate'] < 44100) {
  306. switch ($thisfile_mpeg_audio_lame['raw']['source_sample_freq']) {
  307. case 0: // <= 32000
  308. // may or may not be same as source frequency - ignore
  309. break;
  310. case 1: // 44100
  311. case 2: // 48000
  312. case 3: // 48000+
  313. $ExplodedOptions = explode(' ', $encoder_options, 4);
  314. switch ($ExplodedOptions[0]) {
  315. case '--preset':
  316. case '--alt-preset':
  317. switch ($ExplodedOptions[1]) {
  318. case 'fast':
  319. case 'portable':
  320. case 'medium':
  321. case 'standard':
  322. case 'extreme':
  323. case 'insane':
  324. $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
  325. break;
  326. default:
  327. static $ExpectedResampledRate = array(
  328. 'phon+/lw/mw-eu/sw|16000' => 16000,
  329. 'mw-us|24000' => 24000, // 3.95
  330. 'mw-us|32000' => 32000, // 3.93
  331. 'mw-us|16000' => 16000, // 3.92
  332. 'phone|16000' => 16000,
  333. 'phone|11025' => 11025, // 3.94a15
  334. 'radio|32000' => 32000, // 3.94a15
  335. 'fm/radio|32000' => 32000, // 3.92
  336. 'fm|32000' => 32000, // 3.90
  337. 'voice|32000' => 32000);
  338. if (!isset($ExpectedResampledRate[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio['sample_rate']])) {
  339. $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
  340. }
  341. break;
  342. }
  343. break;
  344. case '--r3mix':
  345. default:
  346. $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
  347. break;
  348. }
  349. break;
  350. }
  351. }
  352. }
  353. if (empty($encoder_options) && !empty($info['audio']['bitrate']) && !empty($info['audio']['bitrate_mode'])) {
  354. //$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
  355. $encoder_options = strtoupper($info['audio']['bitrate_mode']);
  356. }
  357. return $encoder_options;
  358. }
  359. /**
  360. * @param int $offset
  361. * @param array $info
  362. * @param bool $recursivesearch
  363. * @param bool $ScanAsCBR
  364. * @param bool $FastMPEGheaderScan
  365. *
  366. * @return bool
  367. */
  368. public function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) {
  369. static $MPEGaudioVersionLookup;
  370. static $MPEGaudioLayerLookup;
  371. static $MPEGaudioBitrateLookup;
  372. static $MPEGaudioFrequencyLookup;
  373. static $MPEGaudioChannelModeLookup;
  374. static $MPEGaudioModeExtensionLookup;
  375. static $MPEGaudioEmphasisLookup;
  376. if (empty($MPEGaudioVersionLookup)) {
  377. $MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
  378. $MPEGaudioLayerLookup = self::MPEGaudioLayerArray();
  379. $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
  380. $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray();
  381. $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray();
  382. $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
  383. $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray();
  384. }
  385. if ($this->fseek($offset) != 0) {
  386. $this->error('decodeMPEGaudioHeader() failed to seek to next offset at '.$offset);
  387. return false;
  388. }
  389. //$headerstring = $this->fread(1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame
  390. $headerstring = $this->fread(226); // LAME header at offset 36 + 190 bytes of Xing/LAME data
  391. // MP3 audio frame structure:
  392. // $aa $aa $aa $aa [$bb $bb] $cc...
  393. // where $aa..$aa is the four-byte mpeg-audio header (below)
  394. // $bb $bb is the optional 2-byte CRC
  395. // and $cc... is the audio data
  396. $head4 = substr($headerstring, 0, 4);
  397. $head4_key = getid3_lib::PrintHexBytes($head4, true, false, false);
  398. static $MPEGaudioHeaderDecodeCache = array();
  399. if (isset($MPEGaudioHeaderDecodeCache[$head4_key])) {
  400. $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4_key];
  401. } else {
  402. $MPEGheaderRawArray = self::MPEGaudioHeaderDecode($head4);
  403. $MPEGaudioHeaderDecodeCache[$head4_key] = $MPEGheaderRawArray;
  404. }
  405. static $MPEGaudioHeaderValidCache = array();
  406. if (!isset($MPEGaudioHeaderValidCache[$head4_key])) { // Not in cache
  407. //$MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true); // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1)
  408. $MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false);
  409. }
  410. // shortcut
  411. if (!isset($info['mpeg']['audio'])) {
  412. $info['mpeg']['audio'] = array();
  413. }
  414. $thisfile_mpeg_audio = &$info['mpeg']['audio'];
  415. if ($MPEGaudioHeaderValidCache[$head4_key]) {
  416. $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray;
  417. } else {
  418. $this->error('Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset);
  419. return false;
  420. }
  421. if (!$FastMPEGheaderScan) {
  422. $thisfile_mpeg_audio['version'] = $MPEGaudioVersionLookup[$thisfile_mpeg_audio['raw']['version']];
  423. $thisfile_mpeg_audio['layer'] = $MPEGaudioLayerLookup[$thisfile_mpeg_audio['raw']['layer']];
  424. $thisfile_mpeg_audio['channelmode'] = $MPEGaudioChannelModeLookup[$thisfile_mpeg_audio['raw']['channelmode']];
  425. $thisfile_mpeg_audio['channels'] = (($thisfile_mpeg_audio['channelmode'] == 'mono') ? 1 : 2);
  426. $thisfile_mpeg_audio['sample_rate'] = $MPEGaudioFrequencyLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['raw']['sample_rate']];
  427. $thisfile_mpeg_audio['protection'] = !$thisfile_mpeg_audio['raw']['protection'];
  428. $thisfile_mpeg_audio['private'] = (bool) $thisfile_mpeg_audio['raw']['private'];
  429. $thisfile_mpeg_audio['modeextension'] = $MPEGaudioModeExtensionLookup[$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['modeextension']];
  430. $thisfile_mpeg_audio['copyright'] = (bool) $thisfile_mpeg_audio['raw']['copyright'];
  431. $thisfile_mpeg_audio['original'] = (bool) $thisfile_mpeg_audio['raw']['original'];
  432. $thisfile_mpeg_audio['emphasis'] = $MPEGaudioEmphasisLookup[$thisfile_mpeg_audio['raw']['emphasis']];
  433. $info['audio']['channels'] = $thisfile_mpeg_audio['channels'];
  434. $info['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate'];
  435. if ($thisfile_mpeg_audio['protection']) {
  436. $thisfile_mpeg_audio['crc'] = getid3_lib::BigEndian2Int(substr($headerstring, 4, 2));
  437. }
  438. }
  439. if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) {
  440. // http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0
  441. $this->warning('Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1');
  442. $thisfile_mpeg_audio['raw']['bitrate'] = 0;
  443. }
  444. $thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding'];
  445. $thisfile_mpeg_audio['bitrate'] = $MPEGaudioBitrateLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['bitrate']];
  446. if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $info['avdataoffset'])) {
  447. // only skip multiple frame check if free-format bitstream found at beginning of file
  448. // otherwise is quite possibly simply corrupted data
  449. $recursivesearch = false;
  450. }
  451. // For Layer 2 there are some combinations of bitrate and mode which are not allowed.
  452. if (!$FastMPEGheaderScan && ($thisfile_mpeg_audio['layer'] == '2')) {
  453. $info['audio']['dataformat'] = 'mp2';
  454. switch ($thisfile_mpeg_audio['channelmode']) {
  455. case 'mono':
  456. if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) {
  457. // these are ok
  458. } else {
  459. $this->error($thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.');
  460. return false;
  461. }
  462. break;
  463. case 'stereo':
  464. case 'joint stereo':
  465. case 'dual channel':
  466. if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) {
  467. // these are ok
  468. } else {
  469. $this->error(intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.');
  470. return false;
  471. }
  472. break;
  473. }
  474. }
  475. if ($info['audio']['sample_rate'] > 0) {
  476. $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']);
  477. }
  478. $nextframetestoffset = $offset + 1;
  479. if ($thisfile_mpeg_audio['bitrate'] != 'free') {
  480. $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
  481. if (isset($thisfile_mpeg_audio['framelength'])) {
  482. $nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength'];
  483. } else {
  484. $this->error('Frame at offset('.$offset.') is has an invalid frame length.');
  485. return false;
  486. }
  487. }
  488. $ExpectedNumberOfAudioBytes = 0;
  489. ////////////////////////////////////////////////////////////////////////////////////
  490. // Variable-bitrate headers
  491. if (substr($headerstring, 4 + 32, 4) == 'VBRI') {
  492. // Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36)
  493. // specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html
  494. $thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
  495. $thisfile_mpeg_audio['VBR_method'] = 'Fraunhofer';
  496. $info['audio']['codec'] = 'Fraunhofer';
  497. $SideInfoData = substr($headerstring, 4 + 2, 32);
  498. $FraunhoferVBROffset = 36;
  499. $thisfile_mpeg_audio['VBR_encoder_version'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 4, 2)); // VbriVersion
  500. $thisfile_mpeg_audio['VBR_encoder_delay'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 6, 2)); // VbriDelay
  501. $thisfile_mpeg_audio['VBR_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 8, 2)); // VbriQuality
  502. $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4)); // VbriStreamBytes
  503. $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4)); // VbriStreamFrames
  504. $thisfile_mpeg_audio['VBR_seek_offsets'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2)); // VbriTableSize
  505. $thisfile_mpeg_audio['VBR_seek_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 2)); // VbriTableScale
  506. $thisfile_mpeg_audio['VBR_entry_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 22, 2)); // VbriEntryBytes
  507. $thisfile_mpeg_audio['VBR_entry_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2)); // VbriEntryFrames
  508. $ExpectedNumberOfAudioBytes = $thisfile_mpeg_audio['VBR_bytes'];
  509. $previousbyteoffset = $offset;
  510. for ($i = 0; $i < $thisfile_mpeg_audio['VBR_seek_offsets']; $i++) {
  511. $Fraunhofer_OffsetN = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, $thisfile_mpeg_audio['VBR_entry_bytes']));
  512. $FraunhoferVBROffset += $thisfile_mpeg_audio['VBR_entry_bytes'];
  513. $thisfile_mpeg_audio['VBR_offsets_relative'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']);
  514. $thisfile_mpeg_audio['VBR_offsets_absolute'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']) + $previousbyteoffset;
  515. $previousbyteoffset += $Fraunhofer_OffsetN;
  516. }
  517. } else {
  518. // Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36)
  519. // depending on MPEG layer and number of channels
  520. $VBRidOffset = self::XingVBRidOffset($thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['channelmode']);
  521. $SideInfoData = substr($headerstring, 4 + 2, $VBRidOffset - 4);
  522. if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) {
  523. // 'Xing' is traditional Xing VBR frame
  524. // 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.)
  525. // 'Info' *can* legally be used to specify a VBR file as well, however.
  526. // http://www.multiweb.cz/twoinches/MP3inside.htm
  527. //00..03 = "Xing" or "Info"
  528. //04..07 = Flags:
  529. // 0x01 Frames Flag set if value for number of frames in file is stored
  530. // 0x02 Bytes Flag set if value for filesize in bytes is stored
  531. // 0x04 TOC Flag set if values for TOC are stored
  532. // 0x08 VBR Scale Flag set if values for VBR scale is stored
  533. //08..11 Frames: Number of frames in file (including the first Xing/Info one)
  534. //12..15 Bytes: File length in Bytes
  535. //16..115 TOC (Table of Contents):
  536. // Contains of 100 indexes (one Byte length) for easier lookup in file. Approximately solves problem with moving inside file.
  537. // Each Byte has a value according this formula:
  538. // (TOC[i] / 256) * fileLenInBytes
  539. // 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:
  540. // TOC[(60/240)*100] = TOC[25]
  541. // and corresponding Byte in file is then approximately at:
  542. // (TOC[25]/256) * 5000000
  543. //116..119 VBR Scale
  544. // should be safe to leave this at 'vbr' and let it be overriden to 'cbr' if a CBR preset/mode is used by LAME
  545. // if (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Xing') {
  546. $thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
  547. $thisfile_mpeg_audio['VBR_method'] = 'Xing';
  548. // } else {
  549. // $ScanAsCBR = true;
  550. // $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
  551. // }
  552. $thisfile_mpeg_audio['xing_flags_raw'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4));
  553. $thisfile_mpeg_audio['xing_flags']['frames'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000001);
  554. $thisfile_mpeg_audio['xing_flags']['bytes'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000002);
  555. $thisfile_mpeg_audio['xing_flags']['toc'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000004);
  556. $thisfile_mpeg_audio['xing_flags']['vbr_scale'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000008);
  557. if ($thisfile_mpeg_audio['xing_flags']['frames']) {
  558. $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 8, 4));
  559. //$thisfile_mpeg_audio['VBR_frames']--; // don't count header Xing/Info frame
  560. }
  561. if ($thisfile_mpeg_audio['xing_flags']['bytes']) {
  562. $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4));
  563. }
  564. //if (($thisfile_mpeg_audio['bitrate'] == 'free') && !empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
  565. //if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
  566. if (!empty($thisfile_mpeg_audio['VBR_frames'])) {
  567. $used_filesize = 0;
  568. if (!empty($thisfile_mpeg_audio['VBR_bytes'])) {
  569. $used_filesize = $thisfile_mpeg_audio['VBR_bytes'];
  570. } elseif (!empty($info['filesize'])) {
  571. $used_filesize = $info['filesize'];
  572. $used_filesize -= (isset($info['id3v2']['headerlength']) ? intval($info['id3v2']['headerlength']) : 0);
  573. $used_filesize -= (isset($info['id3v1']) ? 128 : 0);
  574. $used_filesize -= (isset($info['tag_offset_end']) ? $info['tag_offset_end'] - $info['tag_offset_start'] : 0);
  575. $this->warning('MP3.Xing header missing VBR_bytes, assuming MPEG audio portion of file is '.number_format($used_filesize).' bytes');
  576. }
  577. $framelengthfloat = $used_filesize / $thisfile_mpeg_audio['VBR_frames'];
  578. if ($thisfile_mpeg_audio['layer'] == '1') {
  579. // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
  580. //$info['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12;
  581. $info['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 12;
  582. } else {
  583. // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
  584. //$info['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144;
  585. $info['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 144;
  586. }
  587. $thisfile_mpeg_audio['framelength'] = floor($framelengthfloat);
  588. }
  589. if ($thisfile_mpeg_audio['xing_flags']['toc']) {
  590. $LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100);
  591. for ($i = 0; $i < 100; $i++) {
  592. $thisfile_mpeg_audio['toc'][$i] = ord($LAMEtocData[$i]);
  593. }
  594. }
  595. if ($thisfile_mpeg_audio['xing_flags']['vbr_scale']) {
  596. $thisfile_mpeg_audio['VBR_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4));
  597. }
  598. // http://gabriel.mp3-tech.org/mp3infotag.html
  599. if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') {
  600. // shortcut
  601. $thisfile_mpeg_audio['LAME'] = array();
  602. $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME'];
  603. $thisfile_mpeg_audio_lame['long_version'] = substr($headerstring, $VBRidOffset + 120, 20);
  604. $thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9);
  605. $thisfile_mpeg_audio_lame['numeric_version'] = str_replace('LAME', '', $thisfile_mpeg_audio_lame['short_version']);
  606. if (preg_match('#^LAME([0-9\\.a-z]+)#', $thisfile_mpeg_audio_lame['long_version'], $matches)) {
  607. $thisfile_mpeg_audio_lame['short_version'] = $matches[0];
  608. $thisfile_mpeg_audio_lame['numeric_version'] = $matches[1];
  609. }
  610. foreach (explode('.', $thisfile_mpeg_audio_lame['numeric_version']) as $key => $number) {
  611. $thisfile_mpeg_audio_lame['integer_version'][$key] = intval($number);
  612. }
  613. //if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') {
  614. if ((($thisfile_mpeg_audio_lame['integer_version'][0] * 1000) + $thisfile_mpeg_audio_lame['integer_version'][1]) >= 3090) { // cannot use string version compare, may have "LAME3.90" or "LAME3.100" -- see https://github.com/JamesHeinrich/getID3/issues/207
  615. // extra 11 chars are not part of version string when LAMEtag present
  616. unset($thisfile_mpeg_audio_lame['long_version']);
  617. // It the LAME tag was only introduced in LAME v3.90
  618. // http://www.hydrogenaudio.org/?act=ST&f=15&t=9933
  619. // Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html
  620. // are assuming a 'Xing' identifier offset of 0x24, which is the case for
  621. // MPEG-1 non-mono, but not for other combinations
  622. $LAMEtagOffsetContant = $VBRidOffset - 0x24;
  623. // shortcuts
  624. $thisfile_mpeg_audio_lame['RGAD'] = array('track'=>array(), 'album'=>array());
  625. $thisfile_mpeg_audio_lame_RGAD = &$thisfile_mpeg_audio_lame['RGAD'];
  626. $thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track'];
  627. $thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album'];
  628. $thisfile_mpeg_audio_lame['raw'] = array();
  629. $thisfile_mpeg_audio_lame_raw = &$thisfile_mpeg_audio_lame['raw'];
  630. // byte $9B VBR Quality
  631. // This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications.
  632. // Actually overwrites original Xing bytes
  633. unset($thisfile_mpeg_audio['VBR_scale']);
  634. $thisfile_mpeg_audio_lame['vbr_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1));
  635. // bytes $9C-$A4 Encoder short VersionString
  636. $thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9);
  637. // byte $A5 Info Tag revision + VBR method
  638. $LAMEtagRevisionVBRmethod = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1));
  639. $thisfile_mpeg_audio_lame['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4;
  640. $thisfile_mpeg_audio_lame_raw['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F;
  641. $thisfile_mpeg_audio_lame['vbr_method'] = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']);
  642. $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'
  643. // byte $A6 Lowpass filter value
  644. $thisfile_mpeg_audio_lame['lowpass_frequency'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100;
  645. // bytes $A7-$AE Replay Gain
  646. // http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html
  647. // bytes $A7-$AA : 32 bit floating point "Peak signal amplitude"
  648. if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') {
  649. // LAME 3.94a16 and later - 9.23 fixed point
  650. // ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375
  651. $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608);
  652. } else {
  653. // LAME 3.94a15 and earlier - 32-bit floating point
  654. // Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15
  655. $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = getid3_lib::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4));
  656. }
  657. if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) {
  658. unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
  659. } else {
  660. $thisfile_mpeg_audio_lame_RGAD['peak_db'] = getid3_lib::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
  661. }
  662. $thisfile_mpeg_audio_lame_raw['RGAD_track'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2));
  663. $thisfile_mpeg_audio_lame_raw['RGAD_album'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2));
  664. if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) {
  665. $thisfile_mpeg_audio_lame_RGAD_track['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13;
  666. $thisfile_mpeg_audio_lame_RGAD_track['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10;
  667. $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9;
  668. $thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF;
  669. $thisfile_mpeg_audio_lame_RGAD_track['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']);
  670. $thisfile_mpeg_audio_lame_RGAD_track['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']);
  671. $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']);
  672. if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
  673. $info['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
  674. }
  675. $info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator'];
  676. $info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db'];
  677. } else {
  678. unset($thisfile_mpeg_audio_lame_RGAD['track']);
  679. }
  680. if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) {
  681. $thisfile_mpeg_audio_lame_RGAD_album['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13;
  682. $thisfile_mpeg_audio_lame_RGAD_album['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10;
  683. $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9;
  684. $thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF;
  685. $thisfile_mpeg_audio_lame_RGAD_album['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']);
  686. $thisfile_mpeg_audio_lame_RGAD_album['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']);
  687. $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']);
  688. if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
  689. $info['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
  690. }
  691. $info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator'];
  692. $info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db'];
  693. } else {
  694. unset($thisfile_mpeg_audio_lame_RGAD['album']);
  695. }
  696. if (empty($thisfile_mpeg_audio_lame_RGAD)) {
  697. unset($thisfile_mpeg_audio_lame['RGAD']);
  698. }
  699. // byte $AF Encoding flags + ATH Type
  700. $EncodingFlagsATHtype = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1));
  701. $thisfile_mpeg_audio_lame['encoding_flags']['nspsytune'] = (bool) ($EncodingFlagsATHtype & 0x10);
  702. $thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20);
  703. $thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'] = (bool) ($EncodingFlagsATHtype & 0x40);
  704. $thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] = (bool) ($EncodingFlagsATHtype & 0x80);
  705. $thisfile_mpeg_audio_lame['ath_type'] = $EncodingFlagsATHtype & 0x0F;
  706. // byte $B0 if ABR {specified bitrate} else {minimal bitrate}
  707. $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1));
  708. if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR)
  709. $thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
  710. } elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR)
  711. // ignore
  712. } elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate
  713. $thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
  714. }
  715. // bytes $B1-$B3 Encoder delays
  716. $EncoderDelays = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3));
  717. $thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12;
  718. $thisfile_mpeg_audio_lame['end_padding'] = $EncoderDelays & 0x000FFF;
  719. // byte $B4 Misc
  720. $MiscByte = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1));
  721. $thisfile_mpeg_audio_lame_raw['noise_shaping'] = ($MiscByte & 0x03);
  722. $thisfile_mpeg_audio_lame_raw['stereo_mode'] = ($MiscByte & 0x1C) >> 2;
  723. $thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5;
  724. $thisfile_mpeg_audio_lame_raw['source_sample_freq'] = ($MiscByte & 0xC0) >> 6;
  725. $thisfile_mpeg_audio_lame['noise_shaping'] = $thisfile_mpeg_audio_lame_raw['noise_shaping'];
  726. $thisfile_mpeg_audio_lame['stereo_mode'] = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']);
  727. $thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality'];
  728. $thisfile_mpeg_audio_lame['source_sample_freq'] = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']);
  729. // byte $B5 MP3 Gain
  730. $thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true);
  731. $thisfile_mpeg_audio_lame['mp3_gain_db'] = (getid3_lib::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain'];
  732. $thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6));
  733. // bytes $B6-$B7 Preset and surround info
  734. $PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2));
  735. // Reserved = ($PresetSurroundBytes & 0xC000);
  736. $thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800);
  737. $thisfile_mpeg_audio_lame['surround_info'] = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']);
  738. $thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF);
  739. $thisfile_mpeg_audio_lame['preset_used'] = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame);
  740. if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) {
  741. $this->warning('Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org');
  742. }
  743. if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) {
  744. // this may change if 3.90.4 ever comes out
  745. $thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3';
  746. }
  747. // bytes $B8-$BB MusicLength
  748. $thisfile_mpeg_audio_lame['audio_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4));
  749. $ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']);
  750. // bytes $BC-$BD MusicCRC
  751. $thisfile_mpeg_audio_lame['music_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2));
  752. // bytes $BE-$BF CRC-16 of Info Tag
  753. $thisfile_mpeg_audio_lame['lame_tag_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2));
  754. // LAME CBR
  755. if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) {
  756. $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
  757. $thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']);
  758. $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
  759. //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) {
  760. // $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min'];
  761. //}
  762. }
  763. }
  764. }
  765. } else {
  766. // not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header)
  767. $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
  768. if ($recursivesearch) {
  769. $thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
  770. if ($this->RecursiveFrameScanning($offset, $nextframetestoffset, true)) {
  771. $recursivesearch = false;
  772. $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
  773. }
  774. if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') {
  775. $this->warning('VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.');
  776. }
  777. }
  778. }
  779. }
  780. if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($info['avdataend'] - $info['avdataoffset']))) {
  781. if ($ExpectedNumberOfAudioBytes > ($info['avdataend'] - $info['avdataoffset'])) {
  782. if ($this->isDependencyFor('matroska') || $this->isDependencyFor('riff')) {
  783. // ignore, audio data is broken into chunks so will always be data "missing"
  784. }
  785. elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) {
  786. $this->warning('Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)');
  787. }
  788. else {
  789. $this->warning('Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)');
  790. }
  791. } else {
  792. if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) {
  793. // $prenullbytefileoffset = $this->ftell();
  794. // $this->fseek($info['avdataend']);
  795. // $PossibleNullByte = $this->fread(1);
  796. // $this->fseek($prenullbytefileoffset);
  797. // if ($PossibleNullByte === "\x00") {
  798. $info['avdataend']--;
  799. // $this->warning('Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored');
  800. // } else {
  801. // $this->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)');
  802. // }
  803. } else {
  804. $this->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)');
  805. }
  806. }
  807. }
  808. if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($info['audio']['bitrate'])) {
  809. if (($offset == $info['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) {
  810. $framebytelength = $this->FreeFormatFrameLength($offset, true);
  811. if ($framebytelength > 0) {
  812. $thisfile_mpeg_audio['framelength'] = $framebytelength;
  813. if ($thisfile_mpeg_audio['layer'] == '1') {
  814. // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
  815. $info['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12;
  816. } else {
  817. // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
  818. $info['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144;
  819. }
  820. } else {
  821. $this->error('Error calculating frame length of free-format MP3 without Xing/LAME header');
  822. }
  823. }
  824. }
  825. if (isset($thisfile_mpeg_audio['VBR_frames']) ? $thisfile_mpeg_audio['VBR_frames'] : '') {
  826. switch ($thisfile_mpeg_audio['bitrate_mode']) {
  827. case 'vbr':
  828. case 'abr':
  829. $bytes_per_frame = 1152;
  830. if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) {
  831. $bytes_per_frame = 384;
  832. } elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) {
  833. $bytes_per_frame = 576;
  834. }
  835. $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);
  836. if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) {
  837. $info['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate'];
  838. $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion
  839. }
  840. break;
  841. }
  842. }
  843. // End variable-bitrate headers
  844. ////////////////////////////////////////////////////////////////////////////////////
  845. if ($recursivesearch) {
  846. if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) {
  847. return false;
  848. }
  849. }
  850. //if (false) {
  851. // // experimental side info parsing section - not returning anything useful yet
  852. //
  853. // $SideInfoBitstream = getid3_lib::BigEndian2Bin($SideInfoData);
  854. // $SideInfoOffset = 0;
  855. //
  856. // if ($thisfile_mpeg_audio['version'] == '1') {
  857. // if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
  858. // // MPEG-1 (mono)
  859. // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
  860. // $SideInfoOffset += 9;
  861. // $SideInfoOffset += 5;
  862. // } else {
  863. // // MPEG-1 (stereo, joint-stereo, dual-channel)
  864. // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
  865. // $SideInfoOffset += 9;
  866. // $SideInfoOffset += 3;
  867. // }
  868. // } else { // 2 or 2.5
  869. // if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
  870. // // MPEG-2, MPEG-2.5 (mono)
  871. // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
  872. // $SideInfoOffset += 8;
  873. // $SideInfoOffset += 1;
  874. // } else {
  875. // // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel)
  876. // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
  877. // $SideInfoOffset += 8;
  878. // $SideInfoOffset += 2;
  879. // }
  880. // }
  881. //
  882. // if ($thisfile_mpeg_audio['version'] == '1') {
  883. // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
  884. // for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) {
  885. // $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1);
  886. // $SideInfoOffset += 2;
  887. // }
  888. // }
  889. // }
  890. // for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) {
  891. // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
  892. // $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12);
  893. // $SideInfoOffset += 12;
  894. // $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
  895. // $SideInfoOffset += 9;
  896. // $thisfile_mpeg_audio['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8);
  897. // $SideInfoOffset += 8;
  898. // if ($thisfile_mpeg_audio['version'] == '1') {
  899. // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
  900. // $SideInfoOffset += 4;
  901. // } else {
  902. // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
  903. // $SideInfoOffset += 9;
  904. // }
  905. // $thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
  906. // $SideInfoOffset += 1;
  907. //
  908. // if ($thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] == '1') {
  909. //
  910. // $thisfile_mpeg_audio['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2);
  911. // $SideInfoOffset += 2;
  912. // $thisfile_mpeg_audio['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
  913. // $SideInfoOffset += 1;
  914. //
  915. // for ($region = 0; $region < 2; $region++) {
  916. // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
  917. // $SideInfoOffset += 5;
  918. // }
  919. // $thisfile_mpeg_audio['table_select'][$granule][$channel][2] = 0;
  920. //
  921. // for ($window = 0; $window < 3; $window++) {
  922. // $thisfile_mpeg_audio['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3);
  923. // $SideInfoOffset += 3;
  924. // }
  925. //
  926. // } else {
  927. //
  928. // for ($region = 0; $region < 3; $region++) {
  929. // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
  930. // $SideInfoOffset += 5;
  931. // }
  932. //
  933. // $thisfile_mpeg_audio['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
  934. // $SideInfoOffset += 4;
  935. // $thisfile_mpeg_audio['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3);
  936. // $SideInfoOffset += 3;
  937. // $thisfile_mpeg_audio['block_type'][$granule][$channel] = 0;
  938. // }
  939. //
  940. // if ($thisfile_mpeg_audio['version'] == '1') {
  941. // $thisfile_mpeg_audio['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
  942. // $SideInfoOffset += 1;
  943. // }
  944. // $thisfile_mpeg_audio['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
  945. // $SideInfoOffset += 1;
  946. // $thisfile_mpeg_audio['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
  947. // $SideInfoOffset += 1;
  948. // }
  949. // }
  950. //}
  951. return true;
  952. }
  953. /**
  954. * @param int $offset
  955. * @param int $nextframetestoffset
  956. * @param bool $ScanAsCBR
  957. *
  958. * @return bool
  959. */
  960. public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) {
  961. $info = &$this->getid3->info;
  962. $firstframetestarray = array('error' => array(), 'warning'=> array(), 'avdataend' => $info['avdataend'], 'avdataoffset' => $info['avdataoffset']);
  963. $this->decodeMPEGaudioHeader($offset, $firstframetestarray, false);
  964. for ($i = 0; $i < GETID3_MP3_VALID_CHECK_FRAMES; $i++) {
  965. // check next GETID3_MP3_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch
  966. if (($nextframetestoffset + 4) >= $info['avdataend']) {
  967. // end of file
  968. return true;
  969. }
  970. $nextframetestarray = array('error' => array(), 'warning' => array(), 'avdataend' => $info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
  971. if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) {
  972. if ($ScanAsCBR) {
  973. // force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header
  974. if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) {
  975. return false;
  976. }
  977. }
  978. // next frame is OK, get ready to check the one after that
  979. if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) {
  980. $nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength'];
  981. } else {
  982. $this->error('Frame at offset ('.$offset.') is has an invalid frame length.');
  983. return false;
  984. }
  985. } elseif (!empty($firstframetestarray['mpeg']['audio']['framelength']) && (($nextframetestoffset + $firstframetestarray['mpeg']['audio']['framelength']) > $info['avdataend'])) {
  986. // 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
  987. return true;
  988. } else {
  989. // next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence
  990. $this->warning('Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.');
  991. return false;
  992. }
  993. }
  994. return true;
  995. }
  996. /**
  997. * @param int $offset
  998. * @param bool $deepscan
  999. *
  1000. * @return int|false
  1001. */
  1002. public function FreeFormatFrameLength($offset, $deepscan=false) {
  1003. $info = &$this->getid3->info;
  1004. $this->fseek($offset);
  1005. $MPEGaudioData = $this->fread(32768);
  1006. $SyncPattern1 = substr($MPEGaudioData, 0, 4);
  1007. // may be different pattern due to padding
  1008. $SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) | 0x02).$SyncPattern1[3];
  1009. if ($SyncPattern2 === $SyncPattern1) {
  1010. $SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) & 0xFD).$SyncPattern1[3];
  1011. }
  1012. $framelength = false;
  1013. $framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4);
  1014. $framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4);
  1015. if ($framelength1 > 4) {
  1016. $framelength = $framelength1;
  1017. }
  1018. if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
  1019. $framelength = $framelength2;
  1020. }
  1021. if (!$framelength) {
  1022. // LAME 3.88 has a different value for modeextension on the first frame vs the rest
  1023. $framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4);
  1024. $framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4);
  1025. if ($framelength1 > 4) {
  1026. $framelength = $framelength1;
  1027. }
  1028. if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
  1029. $framelength = $framelength2;
  1030. }
  1031. if (!$framelength) {
  1032. $this->error('Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset);
  1033. return false;
  1034. } else {
  1035. $this->warning('ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)');
  1036. $info['audio']['codec'] = 'LAME';
  1037. $info['audio']['encoder'] = 'LAME3.88';
  1038. $SyncPattern1 = substr($SyncPattern1, 0, 3);
  1039. $SyncPattern2 = substr($SyncPattern2, 0, 3);
  1040. }
  1041. }
  1042. if ($deepscan) {
  1043. $ActualFrameLengthValues = array();
  1044. $nextoffset = $offset + $framelength;
  1045. while ($nextoffset < ($info['avdataend'] - 6)) {
  1046. $this->fseek($nextoffset - 1);
  1047. $NextSyncPattern = $this->fread(6);
  1048. if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) {
  1049. // good - found where expected
  1050. $ActualFrameLengthValues[] = $framelength;
  1051. } elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) {
  1052. // ok - found one byte earlier than expected (last frame wasn't padded, first frame was)
  1053. $ActualFrameLengthValues[] = ($framelength - 1);
  1054. $nextoffset--;
  1055. } elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) {
  1056. // ok - found one byte later than expected (last frame was padded, first frame wasn't)
  1057. $ActualFrameLengthValues[] = ($framelength + 1);
  1058. $nextoffset++;
  1059. } else {
  1060. $this->error('Did not find expected free-format sync pattern at offset '.$nextoffset);
  1061. return false;
  1062. }
  1063. $nextoffset += $framelength;
  1064. }
  1065. if (count($ActualFrameLengthValues) > 0) {
  1066. $framelength = intval(round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues)));
  1067. }
  1068. }
  1069. return $framelength;
  1070. }
  1071. /**
  1072. * @return bool
  1073. */
  1074. public function getOnlyMPEGaudioInfoBruteForce() {
  1075. $MPEGaudioHeaderDecodeCache = array();
  1076. $MPEGaudioHeaderValidCache = array();
  1077. $MPEGaudioHeaderLengthCache = array();
  1078. $MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
  1079. $MPEGaudioLayerLookup = self::MPEGaudioLayerArray();
  1080. $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
  1081. $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray();
  1082. $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray();
  1083. $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
  1084. $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray();
  1085. $LongMPEGversionLookup = array();
  1086. $LongMPEGlayerLookup = array();
  1087. $LongMPEGbitrateLookup = array();
  1088. $LongMPEGpaddingLookup = array();
  1089. $LongMPEGfrequencyLookup = array();
  1090. $Distribution['bitrate'] = array();
  1091. $Distribution['frequency'] = array();
  1092. $Distribution['layer'] = array();
  1093. $Distribution['version'] = array();
  1094. $Distribution['padding'] = array();
  1095. $info = &$this->getid3->info;
  1096. $this->fseek($info['avdataoffset']);
  1097. $max_frames_scan = 5000;
  1098. $frames_scanned = 0;
  1099. $previousvalidframe = $info['avdataoffset'];
  1100. while ($this->ftell() < $info['avdataend']) {
  1101. set_time_limit(30);
  1102. $head4 = $this->fread(4);
  1103. if (strlen($head4) < 4) {
  1104. break;
  1105. }
  1106. if ($head4[0] != "\xFF") {
  1107. for ($i = 1; $i < 4; $i++) {
  1108. if ($head4[$i] == "\xFF") {
  1109. $this->fseek($i - 4, SEEK_CUR);
  1110. continue 2;
  1111. }
  1112. }
  1113. continue;
  1114. }
  1115. if (!isset($MPEGaudioHeaderDecodeCache[$head4])) {
  1116. $MPEGaudioHeaderDecodeCache[$head4] = self::MPEGaudioHeaderDecode($head4);
  1117. }
  1118. if (!isset($MPEGaudioHeaderValidCache[$head4])) {
  1119. $MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false);
  1120. }
  1121. if ($MPEGaudioHeaderValidCache[$head4]) {
  1122. if (!isset($MPEGaudioHeaderLengthCache[$head4])) {
  1123. $LongMPEGversionLookup[$head4] = $MPEGaudioVersionLookup[$MPEGaudioHeaderDecodeCache[$head4]['version']];
  1124. $LongMPEGlayerLookup[$head4] = $MPEGaudioLayerLookup[$MPEGaudioHeaderDecodeCache[$head4]['layer']];
  1125. $LongMPEGbitrateLookup[$head4] = $MPEGaudioBitrateLookup[$LongMPEGversionLookup[$head4]][$LongMPEGlayerLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['bitrate']];
  1126. $LongMPEGpaddingLookup[$head4] = (bool) $MPEGaudioHeaderDecodeCache[$head4]['padding'];
  1127. $LongMPEGfrequencyLookup[$head4] = $MPEGaudioFrequencyLookup[$LongMPEGversionLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['sample_rate']];
  1128. $MPEGaudioHeaderLengthCache[$head4] = self::MPEGaudioFrameLength(
  1129. $LongMPEGbitrateLookup[$head4],
  1130. $LongMPEGversionLookup[$head4],
  1131. $LongMPEGlayerLookup[$head4],
  1132. $LongMPEGpaddingLookup[$head4],
  1133. $LongMPEGfrequencyLookup[$head4]);
  1134. }
  1135. if ($MPEGaudioHeaderLengthCache[$head4] > 4) {
  1136. $WhereWeWere = $this->ftell();
  1137. $this->fseek($MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR);
  1138. $next4 = $this->fread(4);
  1139. if ($next4[0] == "\xFF") {
  1140. if (!isset($MPEGaudioHeaderDecodeCache[$next4])) {
  1141. $MPEGaudioHeaderDecodeCache[$next4] = self::MPEGaudioHeaderDecode($next4);
  1142. }
  1143. if (!isset($MPEGaudioHeaderValidCache[$next4])) {
  1144. $MPEGaudioHeaderValidCache[$next4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false);
  1145. }
  1146. if ($MPEGaudioHeaderValidCache[$next4]) {
  1147. $this->fseek(-4, SEEK_CUR);
  1148. $Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]] = isset($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]) ? ++$Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]] : 1;
  1149. $Distribution['layer'][$LongMPEGlayerLookup[$head4]] = isset($Distribution['layer'][$LongMPEGlayerLookup[$head4]]) ? ++$Distribution['layer'][$LongMPEGlayerLookup[$head4]] : 1;
  1150. $Distribution['version'][$LongMPEGversionLookup[$head4]] = isset($Distribution['version'][$LongMPEGversionLookup[$head4]]) ? ++$Distribution['version'][$LongMPEGversionLookup[$head4]] : 1;
  1151. $Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] = isset($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]) ? ++$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] : 1;
  1152. $Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] = isset($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]) ? ++$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] : 1;
  1153. if (++$frames_scanned >= $max_frames_scan) {
  1154. $pct_data_scanned = ($this->ftell() - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']);
  1155. $this->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.');
  1156. foreach ($Distribution as $key1 => $value1) {
  1157. foreach ($value1 as $key2 => $value2) {
  1158. $Distribution[$key1][$key2] = round($value2 / $pct_data_scanned);
  1159. }
  1160. }
  1161. break;
  1162. }
  1163. continue;
  1164. }
  1165. }
  1166. unset($next4);
  1167. $this->fseek($WhereWeWere - 3);
  1168. }
  1169. }
  1170. }
  1171. foreach ($Distribution as $key => $value) {
  1172. ksort($Distribution[$key], SORT_NUMERIC);
  1173. }
  1174. ksort($Distribution['version'], SORT_STRING);
  1175. $info['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate'];
  1176. $info['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency'];
  1177. $info['mpeg']['audio']['layer_distribution'] = $Distribution['layer'];
  1178. $info['mpeg']['audio']['version_distribution'] = $Distribution['version'];
  1179. $info['mpeg']['audio']['padding_distribution'] = $Distribution['padding'];
  1180. if (count($Distribution['version']) > 1) {
  1181. $this->error('Corrupt file - more than one MPEG version detected');
  1182. }
  1183. if (count($Distribution['layer']) > 1) {
  1184. $this->error('Corrupt file - more than one MPEG layer detected');
  1185. }
  1186. if (count($Distribution['frequency']) > 1) {
  1187. $this->error('Corrupt file - more than one MPEG sample rate detected');
  1188. }
  1189. $bittotal = 0;
  1190. foreach ($Distribution['bitrate'] as $bitratevalue => $bitratecount) {
  1191. if ($bitratevalue != 'free') {
  1192. $bittotal += ($bitratevalue * $bitratecount);
  1193. }
  1194. }
  1195. $info['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']);
  1196. if ($info['mpeg']['audio']['frame_count'] == 0) {
  1197. $this->error('no MPEG audio frames found');
  1198. return false;
  1199. }
  1200. $info['mpeg']['audio']['bitrate'] = ($bittotal / $info['mpeg']['audio']['frame_count']);
  1201. $info['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr');
  1202. $info['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true);
  1203. $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate'];
  1204. $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode'];
  1205. $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate'];
  1206. $info['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true);
  1207. $info['fileformat'] = $info['audio']['dataformat'];
  1208. return true;
  1209. }
  1210. /**
  1211. * @param int $avdataoffset
  1212. * @param bool $BitrateHistogram
  1213. *
  1214. * @return bool
  1215. */
  1216. public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
  1217. // looks for synch, decodes MPEG audio header
  1218. $info = &$this->getid3->info;
  1219. static $MPEGaudioVersionLookup;
  1220. static $MPEGaudioLayerLookup;
  1221. static $MPEGaudioBitrateLookup;
  1222. if (empty($MPEGaudioVersionLookup)) {
  1223. $MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
  1224. $MPEGaudioLayerLookup = self::MPEGaudioLayerArray();
  1225. $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
  1226. }
  1227. $this->fseek($avdataoffset);
  1228. $sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset);
  1229. if ($sync_seek_buffer_size <= 0) {
  1230. $this->error('Invalid $sync_seek_buffer_size at offset '.$avdataoffset);
  1231. return false;
  1232. }
  1233. $header = $this->fread($sync_seek_buffer_size);
  1234. $sync_seek_buffer_size = strlen($header);
  1235. $SynchSeekOffset = 0;
  1236. while ($SynchSeekOffset < $sync_seek_buffer_size) {
  1237. if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !feof($this->getid3->fp)) {
  1238. if ($SynchSeekOffset > $sync_seek_buffer_size) {
  1239. // if a synch's not found within the first 128k bytes, then give up
  1240. $this->error('Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB');
  1241. if (isset($info['audio']['bitrate'])) {
  1242. unset($info['audio']['bitrate']);
  1243. }
  1244. if (isset($info['mpeg']['audio'])) {
  1245. unset($info['mpeg']['audio']);
  1246. }
  1247. if (empty($info['mpeg'])) {
  1248. unset($info['mpeg']);
  1249. }
  1250. return false;
  1251. } elseif (feof($this->getid3->fp)) {
  1252. $this->error('Could not find valid MPEG audio synch before end of file');
  1253. if (isset($info['audio']['bitrate'])) {
  1254. unset($info['audio']['bitrate']);
  1255. }
  1256. if (isset($info['mpeg']['audio'])) {
  1257. unset($info['mpeg']['audio']);
  1258. }
  1259. if (isset($info['mpeg']) && (!is_array($info['mpeg']) || (count($info['mpeg']) == 0))) {
  1260. unset($info['mpeg']);
  1261. }
  1262. return false;
  1263. }
  1264. }
  1265. if (($SynchSeekOffset + 1) >= strlen($header)) {
  1266. $this->error('Could not find valid MPEG synch before end of file');
  1267. return false;
  1268. }
  1269. if (($header[$SynchSeekOffset] == "\xFF") && ($header[($SynchSeekOffset + 1)] > "\xE0")) { // synch detected
  1270. $FirstFrameAVDataOffset = null;
  1271. if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) {
  1272. $FirstFrameThisfileInfo = $info;
  1273. $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset;
  1274. if (!$this->decodeMPEGaudioHeader($FirstFrameAVDataOffset, $FirstFrameThisfileInfo, false)) {
  1275. // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's
  1276. // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below
  1277. unset($FirstFrameThisfileInfo);
  1278. }
  1279. }
  1280. $dummy = $info; // only overwrite real data if valid header found
  1281. if ($this->decodeMPEGaudioHeader($avdataoffset + $SynchSeekOffset, $dummy, true)) {
  1282. $info = $dummy;
  1283. $info['avdataoffset'] = $avdataoffset + $SynchSeekOffset;
  1284. switch (isset($info['fileformat']) ? $info['fileformat'] : '') {
  1285. case '':
  1286. case 'id3':
  1287. case 'ape':
  1288. case 'mp3':
  1289. $info['fileformat'] = 'mp3';
  1290. $info['audio']['dataformat'] = 'mp3';
  1291. break;
  1292. }
  1293. if (isset($FirstFrameThisfileInfo) && isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) {
  1294. if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) {
  1295. // If there is garbage data between a valid VBR header frame and a sequence
  1296. // of valid MPEG-audio frames the VBR data is no longer discarded.
  1297. $info = $FirstFrameThisfileInfo;
  1298. $info['avdataoffset'] = $FirstFrameAVDataOffset;
  1299. $info['fileformat'] = 'mp3';
  1300. $info['audio']['dataformat'] = 'mp3';
  1301. $dummy = $info;
  1302. unset($dummy['mpeg']['audio']);
  1303. $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength'];
  1304. $GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset;
  1305. if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) {
  1306. $info = $dummy;
  1307. $info['avdataoffset'] = $GarbageOffsetEnd;
  1308. $this->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);
  1309. } else {
  1310. $this->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.')');
  1311. }
  1312. }
  1313. }
  1314. if (isset($info['mpeg']['audio']['bitrate_mode']) && ($info['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($info['mpeg']['audio']['VBR_method'])) {
  1315. // VBR file with no VBR header
  1316. $BitrateHistogram = true;
  1317. }
  1318. if ($BitrateHistogram) {
  1319. $info['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0);
  1320. $info['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0);
  1321. if ($info['mpeg']['audio']['version'] == '1') {
  1322. if ($info['mpeg']['audio']['layer'] == 3) {
  1323. $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);
  1324. } elseif ($info['mpeg']['audio']['layer'] == 2) {
  1325. $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);
  1326. } elseif ($info['mpeg']['audio']['layer'] == 1) {
  1327. $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);
  1328. }
  1329. } elseif ($info['mpeg']['audio']['layer'] == 1) {
  1330. $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);
  1331. } else {
  1332. $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);
  1333. }
  1334. $dummy = array('error'=>$info['error'], 'warning'=>$info['warning'], 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
  1335. $synchstartoffset = $info['avdataoffset'];
  1336. $this->fseek($info['avdataoffset']);
  1337. // you can play with these numbers:
  1338. $max_frames_scan = 50000;
  1339. $max_scan_segments = 10;
  1340. // don't play with these numbers:
  1341. $FastMode = false;
  1342. $SynchErrorsFound = 0;
  1343. $frames_scanned = 0;
  1344. $this_scan_segment = 0;
  1345. $frames_scan_per_segment = ceil($max_frames_scan / $max_scan_segments);
  1346. $pct_data_scanned = 0;
  1347. for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) {
  1348. $frames_scanned_this_segment = 0;
  1349. if ($this->ftell() >= $info['avdataend']) {
  1350. break;
  1351. }
  1352. $scan_start_offset[$current_segment] = max($this->ftell(), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments)));
  1353. if ($current_segment > 0) {
  1354. $this->fseek($scan_start_offset[$current_segment]);
  1355. $buffer_4k = $this->fread(4096);
  1356. for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) {
  1357. if (($buffer_4k[$j] == "\xFF") && ($buffer_4k[($j + 1)] > "\xE0")) { // synch detected
  1358. if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) {
  1359. $calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength'];
  1360. if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) {
  1361. $scan_start_offset[$current_segment] += $j;
  1362. break;
  1363. }
  1364. }
  1365. }
  1366. }
  1367. }
  1368. $synchstartoffset = $scan_start_offset[$current_segment];
  1369. while (($synchstartoffset < $info['avdataend']) && $this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) {
  1370. $FastMode = true;
  1371. $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']];
  1372. if (empty($dummy['mpeg']['audio']['framelength'])) {
  1373. $SynchErrorsFound++;
  1374. $synchstartoffset++;
  1375. } else {
  1376. getid3_lib::safe_inc($info['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]);
  1377. getid3_lib::safe_inc($info['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]);
  1378. getid3_lib::safe_inc($info['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]);
  1379. $synchstartoffset += $dummy['mpeg']['audio']['framelength'];
  1380. }
  1381. $frames_scanned++;
  1382. if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) {
  1383. $this_pct_scanned = ($this->ftell() - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']);
  1384. if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) {
  1385. // file likely contains < $max_frames_scan, just scan as one segment
  1386. $max_scan_segments = 1;
  1387. $frames_scan_per_segment = $max_frames_scan;
  1388. } else {
  1389. $pct_data_scanned += $this_pct_scanned;
  1390. break;
  1391. }
  1392. }
  1393. }
  1394. }
  1395. if ($pct_data_scanned > 0) {
  1396. $this->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.');
  1397. foreach ($info['mpeg']['audio'] as $key1 => $value1) {
  1398. if (!preg_match('#_distribution$#i', $key1)) {
  1399. continue;
  1400. }
  1401. foreach ($value1 as $key2 => $value2) {
  1402. $info['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned);
  1403. }
  1404. }
  1405. }
  1406. if ($SynchErrorsFound > 0) {
  1407. $this->warning('Found '.$SynchErrorsFound.' synch errors in histogram analysis');
  1408. //return false;
  1409. }
  1410. $bittotal = 0;
  1411. $framecounter = 0;
  1412. foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) {
  1413. $framecounter += $bitratecount;
  1414. if ($bitratevalue != 'free') {
  1415. $bittotal += ($bitratevalue * $bitratecount);
  1416. }
  1417. }
  1418. if ($framecounter == 0) {
  1419. $this->error('Corrupt MP3 file: framecounter == zero');
  1420. return false;
  1421. }
  1422. $info['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter);
  1423. $info['mpeg']['audio']['bitrate'] = ($bittotal / $framecounter);
  1424. $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate'];
  1425. // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently
  1426. $distinct_bitrates = 0;
  1427. foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) {
  1428. if ($bitrate_count > 0) {
  1429. $distinct_bitrates++;
  1430. }
  1431. }
  1432. if ($distinct_bitrates > 1) {
  1433. $info['mpeg']['audio']['bitrate_mode'] = 'vbr';
  1434. } else {
  1435. $info['mpeg']['audio']['bitrate_mode'] = 'cbr';
  1436. }
  1437. $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode'];
  1438. }
  1439. break; // exit while()
  1440. }
  1441. }
  1442. $SynchSeekOffset++;
  1443. if (($avdataoffset + $SynchSeekOffset) >= $info['avdataend']) {
  1444. // end of file/data
  1445. if (empty($info['mpeg']['audio'])) {
  1446. $this->error('could not find valid MPEG synch before end of file');
  1447. if (isset($info['audio']['bitrate'])) {
  1448. unset($info['audio']['bitrate']);
  1449. }
  1450. if (isset($info['mpeg']['audio'])) {
  1451. unset($info['mpeg']['audio']);
  1452. }
  1453. if (isset($info['mpeg']) && (!is_array($info['mpeg']) || empty($info['mpeg']))) {
  1454. unset($info['mpeg']);
  1455. }
  1456. return false;
  1457. }
  1458. break;
  1459. }
  1460. }
  1461. $info['audio']['channels'] = $info['mpeg']['audio']['channels'];
  1462. $info['audio']['channelmode'] = $info['mpeg']['audio']['channelmode'];
  1463. $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate'];
  1464. return true;
  1465. }
  1466. /**
  1467. * @return array
  1468. */
  1469. public static function MPEGaudioVersionArray() {
  1470. static $MPEGaudioVersion = array('2.5', false, '2', '1');
  1471. return $MPEGaudioVersion;
  1472. }
  1473. /**
  1474. * @return array
  1475. */
  1476. public static function MPEGaudioLayerArray() {
  1477. static $MPEGaudioLayer = array(false, 3, 2, 1);
  1478. return $MPEGaudioLayer;
  1479. }
  1480. /**
  1481. * @return array
  1482. */
  1483. public static function MPEGaudioBitrateArray() {
  1484. static $MPEGaudioBitrate;
  1485. if (empty($MPEGaudioBitrate)) {
  1486. $MPEGaudioBitrate = array (
  1487. '1' => array (1 => array('free', 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000),
  1488. 2 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000),
  1489. 3 => array('free', 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000)
  1490. ),
  1491. '2' => array (1 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000),
  1492. 2 => array('free', 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000),
  1493. )
  1494. );
  1495. $MPEGaudioBitrate['2'][3] = $MPEGaudioBitrate['2'][2];
  1496. $MPEGaudioBitrate['2.5'] = $MPEGaudioBitrate['2'];
  1497. }
  1498. return $MPEGaudioBitrate;
  1499. }
  1500. /**
  1501. * @return array
  1502. */
  1503. public static function MPEGaudioFrequencyArray() {
  1504. static $MPEGaudioFrequency;
  1505. if (empty($MPEGaudioFrequency)) {
  1506. $MPEGaudioFrequency = array (
  1507. '1' => array(44100, 48000, 32000),
  1508. '2' => array(22050, 24000, 16000),
  1509. '2.5' => array(11025, 12000, 8000)
  1510. );
  1511. }
  1512. return $MPEGaudioFrequency;
  1513. }
  1514. /**
  1515. * @return array
  1516. */
  1517. public static function MPEGaudioChannelModeArray() {
  1518. static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono');
  1519. return $MPEGaudioChannelMode;
  1520. }
  1521. /**
  1522. * @return array
  1523. */
  1524. public static function MPEGaudioModeExtensionArray() {
  1525. static $MPEGaudioModeExtension;
  1526. if (empty($MPEGaudioModeExtension)) {
  1527. $MPEGaudioModeExtension = array (
  1528. 1 => array('4-31', '8-31', '12-31', '16-31'),
  1529. 2 => array('4-31', '8-31', '12-31', '16-31'),
  1530. 3 => array('', 'IS', 'MS', 'IS+MS')
  1531. );
  1532. }
  1533. return $MPEGaudioModeExtension;
  1534. }
  1535. /**
  1536. * @return array
  1537. */
  1538. public static function MPEGaudioEmphasisArray() {
  1539. static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17');
  1540. return $MPEGaudioEmphasis;
  1541. }
  1542. /**
  1543. * @param string $head4
  1544. * @param bool $allowBitrate15
  1545. *
  1546. * @return bool
  1547. */
  1548. public static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) {
  1549. return self::MPEGaudioHeaderValid(self::MPEGaudioHeaderDecode($head4), false, $allowBitrate15);
  1550. }
  1551. /**
  1552. * @param array $rawarray
  1553. * @param bool $echoerrors
  1554. * @param bool $allowBitrate15
  1555. *
  1556. * @return bool
  1557. */
  1558. public static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) {
  1559. if (!isset($rawarray['synch']) || ($rawarray['synch'] & 0x0FFE) != 0x0FFE) {
  1560. return false;
  1561. }
  1562. static $MPEGaudioVersionLookup;
  1563. static $MPEGaudioLayerLookup;
  1564. static $MPEGaudioBitrateLookup;
  1565. static $MPEGaudioFrequencyLookup;
  1566. static $MPEGaudioChannelModeLookup;
  1567. static $MPEGaudioModeExtensionLookup;
  1568. static $MPEGaudioEmphasisLookup;
  1569. if (empty($MPEGaudioVersionLookup)) {
  1570. $MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
  1571. $MPEGaudioLayerLookup = self::MPEGaudioLayerArray();
  1572. $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
  1573. $MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray();
  1574. $MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray();
  1575. $MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
  1576. $MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray();
  1577. }
  1578. if (isset($MPEGaudioVersionLookup[$rawarray['version']])) {
  1579. $decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']];
  1580. } else {
  1581. echo ($echoerrors ? "\n".'invalid Version ('.$rawarray['version'].')' : '');
  1582. return false;
  1583. }
  1584. if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) {
  1585. $decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']];
  1586. } else {
  1587. echo ($echoerrors ? "\n".'invalid Layer ('.$rawarray['layer'].')' : '');
  1588. return false;
  1589. }
  1590. if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) {
  1591. echo ($echoerrors ? "\n".'invalid Bitrate ('.$rawarray['bitrate'].')' : '');
  1592. if ($rawarray['bitrate'] == 15) {
  1593. // known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0
  1594. // let it go through here otherwise file will not be identified
  1595. if (!$allowBitrate15) {
  1596. return false;
  1597. }
  1598. } else {
  1599. return false;
  1600. }
  1601. }
  1602. if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) {
  1603. echo ($echoerrors ? "\n".'invalid Frequency ('.$rawarray['sample_rate'].')' : '');
  1604. return false;
  1605. }
  1606. if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) {
  1607. echo ($echoerrors ? "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')' : '');
  1608. return false;
  1609. }
  1610. if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) {
  1611. echo ($echoerrors ? "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')' : '');
  1612. return false;
  1613. }
  1614. if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) {
  1615. echo ($echoerrors ? "\n".'invalid Emphasis ('.$rawarray['emphasis'].')' : '');
  1616. return false;
  1617. }
  1618. // These are just either set or not set, you can't mess that up :)
  1619. // $rawarray['protection'];
  1620. // $rawarray['padding'];
  1621. // $rawarray['private'];
  1622. // $rawarray['copyright'];
  1623. // $rawarray['original'];
  1624. return true;
  1625. }
  1626. /**
  1627. * @param string $Header4Bytes
  1628. *
  1629. * @return array|false
  1630. */
  1631. public static function MPEGaudioHeaderDecode($Header4Bytes) {
  1632. // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM
  1633. // A - Frame sync (all bits set)
  1634. // B - MPEG Audio version ID
  1635. // C - Layer description
  1636. // D - Protection bit
  1637. // E - Bitrate index
  1638. // F - Sampling rate frequency index
  1639. // G - Padding bit
  1640. // H - Private bit
  1641. // I - Channel Mode
  1642. // J - Mode extension (Only if Joint stereo)
  1643. // K - Copyright
  1644. // L - Original
  1645. // M - Emphasis
  1646. if (strlen($Header4Bytes) != 4) {
  1647. return false;
  1648. }
  1649. $MPEGrawHeader['synch'] = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4;
  1650. $MPEGrawHeader['version'] = (ord($Header4Bytes[1]) & 0x18) >> 3; // BB
  1651. $MPEGrawHeader['layer'] = (ord($Header4Bytes[1]) & 0x06) >> 1; // CC
  1652. $MPEGrawHeader['protection'] = (ord($Header4Bytes[1]) & 0x01); // D
  1653. $MPEGrawHeader['bitrate'] = (ord($Header4Bytes[2]) & 0xF0) >> 4; // EEEE
  1654. $MPEGrawHeader['sample_rate'] = (ord($Header4Bytes[2]) & 0x0C) >> 2; // FF
  1655. $MPEGrawHeader['padding'] = (ord($Header4Bytes[2]) & 0x02) >> 1; // G
  1656. $MPEGrawHeader['private'] = (ord($Header4Bytes[2]) & 0x01); // H
  1657. $MPEGrawHeader['channelmode'] = (ord($Header4Bytes[3]) & 0xC0) >> 6; // II
  1658. $MPEGrawHeader['modeextension'] = (ord($Header4Bytes[3]) & 0x30) >> 4; // JJ
  1659. $MPEGrawHeader['copyright'] = (ord($Header4Bytes[3]) & 0x08) >> 3; // K
  1660. $MPEGrawHeader['original'] = (ord($Header4Bytes[3]) & 0x04) >> 2; // L
  1661. $MPEGrawHeader['emphasis'] = (ord($Header4Bytes[3]) & 0x03); // MM
  1662. return $MPEGrawHeader;
  1663. }
  1664. /**
  1665. * @param int|string $bitrate
  1666. * @param string $version
  1667. * @param string $layer
  1668. * @param bool $padding
  1669. * @param int $samplerate
  1670. *
  1671. * @return int|false
  1672. */
  1673. public static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) {
  1674. static $AudioFrameLengthCache = array();
  1675. if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) {
  1676. $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false;
  1677. if ($bitrate != 'free') {
  1678. if ($version == '1') {
  1679. if ($layer == '1') {
  1680. // For Layer I slot is 32 bits long
  1681. $FrameLengthCoefficient = 48;
  1682. $SlotLength = 4;
  1683. } else { // Layer 2 / 3
  1684. // for Layer 2 and Layer 3 slot is 8 bits long.
  1685. $FrameLengthCoefficient = 144;
  1686. $SlotLength = 1;
  1687. }
  1688. } else { // MPEG-2 / MPEG-2.5
  1689. if ($layer == '1') {
  1690. // For Layer I slot is 32 bits long
  1691. $FrameLengthCoefficient = 24;
  1692. $SlotLength = 4;
  1693. } elseif ($layer == '2') {
  1694. // for Layer 2 and Layer 3 slot is 8 bits long.
  1695. $FrameLengthCoefficient = 144;
  1696. $SlotLength = 1;
  1697. } else { // layer 3
  1698. // for Layer 2 and Layer 3 slot is 8 bits long.
  1699. $FrameLengthCoefficient = 72;
  1700. $SlotLength = 1;
  1701. }
  1702. }
  1703. // FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding
  1704. if ($samplerate > 0) {
  1705. $NewFramelength = ($FrameLengthCoefficient * $bitrate) / $samplerate;
  1706. $NewFramelength = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer 2/3, 4 bytes for Layer I)
  1707. if ($padding) {
  1708. $NewFramelength += $SlotLength;
  1709. }
  1710. $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength;
  1711. }
  1712. }
  1713. }
  1714. return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate];
  1715. }
  1716. /**
  1717. * @param float|int $bit_rate
  1718. *
  1719. * @return int|float|string
  1720. */
  1721. public static function ClosestStandardMP3Bitrate($bit_rate) {
  1722. static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000);
  1723. static $bit_rate_table = array (0=>'-');
  1724. $round_bit_rate = intval(round($bit_rate, -3));
  1725. if (!isset($bit_rate_table[$round_bit_rate])) {
  1726. if ($round_bit_rate > max($standard_bit_rates)) {
  1727. $bit_rate_table[$round_bit_rate] = round($bit_rate, 2 - strlen($bit_rate));
  1728. } else {
  1729. $bit_rate_table[$round_bit_rate] = max($standard_bit_rates);
  1730. foreach ($standard_bit_rates as $standard_bit_rate) {
  1731. if ($round_bit_rate >= $standard_bit_rate + (($bit_rate_table[$round_bit_rate] - $standard_bit_rate) / 2)) {
  1732. break;
  1733. }
  1734. $bit_rate_table[$round_bit_rate] = $standard_bit_rate;
  1735. }
  1736. }
  1737. }
  1738. return $bit_rate_table[$round_bit_rate];
  1739. }
  1740. /**
  1741. * @param string $version
  1742. * @param string $channelmode
  1743. *
  1744. * @return int
  1745. */
  1746. public static function XingVBRidOffset($version, $channelmode) {
  1747. static $XingVBRidOffsetCache = array();
  1748. if (empty($XingVBRidOffsetCache)) {
  1749. $XingVBRidOffsetCache = array (
  1750. '1' => array ('mono' => 0x15, // 4 + 17 = 21
  1751. 'stereo' => 0x24, // 4 + 32 = 36
  1752. 'joint stereo' => 0x24,
  1753. 'dual channel' => 0x24
  1754. ),
  1755. '2' => array ('mono' => 0x0D, // 4 + 9 = 13
  1756. 'stereo' => 0x15, // 4 + 17 = 21
  1757. 'joint stereo' => 0x15,
  1758. 'dual channel' => 0x15
  1759. ),
  1760. '2.5' => array ('mono' => 0x15,
  1761. 'stereo' => 0x15,
  1762. 'joint stereo' => 0x15,
  1763. 'dual channel' => 0x15
  1764. )
  1765. );
  1766. }
  1767. return $XingVBRidOffsetCache[$version][$channelmode];
  1768. }
  1769. /**
  1770. * @param int $VBRmethodID
  1771. *
  1772. * @return string
  1773. */
  1774. public static function LAMEvbrMethodLookup($VBRmethodID) {
  1775. static $LAMEvbrMethodLookup = array(
  1776. 0x00 => 'unknown',
  1777. 0x01 => 'cbr',
  1778. 0x02 => 'abr',
  1779. 0x03 => 'vbr-old / vbr-rh',
  1780. 0x04 => 'vbr-new / vbr-mtrh',
  1781. 0x05 => 'vbr-mt',
  1782. 0x06 => 'vbr (full vbr method 4)',
  1783. 0x08 => 'cbr (constant bitrate 2 pass)',
  1784. 0x09 => 'abr (2 pass)',
  1785. 0x0F => 'reserved'
  1786. );
  1787. return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : '');
  1788. }
  1789. /**
  1790. * @param int $StereoModeID
  1791. *
  1792. * @return string
  1793. */
  1794. public static function LAMEmiscStereoModeLookup($StereoModeID) {
  1795. static $LAMEmiscStereoModeLookup = array(
  1796. 0 => 'mono',
  1797. 1 => 'stereo',
  1798. 2 => 'dual mono',
  1799. 3 => 'joint stereo',
  1800. 4 => 'forced stereo',
  1801. 5 => 'auto',
  1802. 6 => 'intensity stereo',
  1803. 7 => 'other'
  1804. );
  1805. return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : '');
  1806. }
  1807. /**
  1808. * @param int $SourceSampleFrequencyID
  1809. *
  1810. * @return string
  1811. */
  1812. public static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) {
  1813. static $LAMEmiscSourceSampleFrequencyLookup = array(
  1814. 0 => '<= 32 kHz',
  1815. 1 => '44.1 kHz',
  1816. 2 => '48 kHz',
  1817. 3 => '> 48kHz'
  1818. );
  1819. return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : '');
  1820. }
  1821. /**
  1822. * @param int $SurroundInfoID
  1823. *
  1824. * @return string
  1825. */
  1826. public static function LAMEsurroundInfoLookup($SurroundInfoID) {
  1827. static $LAMEsurroundInfoLookup = array(
  1828. 0 => 'no surround info',
  1829. 1 => 'DPL encoding',
  1830. 2 => 'DPL2 encoding',
  1831. 3 => 'Ambisonic encoding'
  1832. );
  1833. return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved');
  1834. }
  1835. /**
  1836. * @param array $LAMEtag
  1837. *
  1838. * @return string
  1839. */
  1840. public static function LAMEpresetUsedLookup($LAMEtag) {
  1841. if ($LAMEtag['preset_used_id'] == 0) {
  1842. // no preset used (LAME >=3.93)
  1843. // no preset recorded (LAME <3.93)
  1844. return '';
  1845. }
  1846. $LAMEpresetUsedLookup = array();
  1847. ///// THIS PART CANNOT BE STATIC .
  1848. for ($i = 8; $i <= 320; $i++) {
  1849. switch ($LAMEtag['vbr_method']) {
  1850. case 'cbr':
  1851. $LAMEpresetUsedLookup[$i] = '--alt-preset '.$LAMEtag['vbr_method'].' '.$i;
  1852. break;
  1853. case 'abr':
  1854. default: // other VBR modes shouldn't be here(?)
  1855. $LAMEpresetUsedLookup[$i] = '--alt-preset '.$i;
  1856. break;
  1857. }
  1858. }
  1859. // named old-style presets (studio, phone, voice, etc) are handled in GuessEncoderOptions()
  1860. // named alt-presets
  1861. $LAMEpresetUsedLookup[1000] = '--r3mix';
  1862. $LAMEpresetUsedLookup[1001] = '--alt-preset standard';
  1863. $LAMEpresetUsedLookup[1002] = '--alt-preset extreme';
  1864. $LAMEpresetUsedLookup[1003] = '--alt-preset insane';
  1865. $LAMEpresetUsedLookup[1004] = '--alt-preset fast standard';
  1866. $LAMEpresetUsedLookup[1005] = '--alt-preset fast extreme';
  1867. $LAMEpresetUsedLookup[1006] = '--alt-preset medium';
  1868. $LAMEpresetUsedLookup[1007] = '--alt-preset fast medium';
  1869. // LAME 3.94 additions/changes
  1870. $LAMEpresetUsedLookup[1010] = '--preset portable'; // 3.94a15 Oct 21 2003
  1871. $LAMEpresetUsedLookup[1015] = '--preset radio'; // 3.94a15 Oct 21 2003
  1872. $LAMEpresetUsedLookup[320] = '--preset insane'; // 3.94a15 Nov 12 2003
  1873. $LAMEpresetUsedLookup[410] = '-V9';
  1874. $LAMEpresetUsedLookup[420] = '-V8';
  1875. $LAMEpresetUsedLookup[440] = '-V6';
  1876. $LAMEpresetUsedLookup[430] = '--preset radio'; // 3.94a15 Nov 12 2003
  1877. $LAMEpresetUsedLookup[450] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'portable'; // 3.94a15 Nov 12 2003
  1878. $LAMEpresetUsedLookup[460] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'medium'; // 3.94a15 Nov 12 2003
  1879. $LAMEpresetUsedLookup[470] = '--r3mix'; // 3.94b1 Dec 18 2003
  1880. $LAMEpresetUsedLookup[480] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'standard'; // 3.94a15 Nov 12 2003
  1881. $LAMEpresetUsedLookup[490] = '-V1';
  1882. $LAMEpresetUsedLookup[500] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'extreme'; // 3.94a15 Nov 12 2003
  1883. return (isset($LAMEpresetUsedLookup[$LAMEtag['preset_used_id']]) ? $LAMEpresetUsedLookup[$LAMEtag['preset_used_id']] : 'new/unknown preset: '.$LAMEtag['preset_used_id'].' - report to info@getid3.org');
  1884. }
  1885. }