├── CHANGELOG.md ├── .idea ├── .gitignore ├── vcs.xml ├── modules.xml ├── youtube_subs.iml ├── libraries │ ├── Dart_SDK.xml │ └── Dart_Packages.xml └── dbnavigator.xml ├── .gitignore ├── web ├── styles.css ├── main.dart └── index.html ├── example ├── convert.dart └── example.txt ├── pubspec.yaml ├── README.md ├── analysis_options.yaml ├── test └── youtube_subs_test.dart ├── lib └── youtube_subs.dart └── pubspec.lock /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version. 4 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build output. 6 | build/ 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /web/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | } 3 | 4 | #wrapper { 5 | max-width: 900px; 6 | margin: 0 auto; 7 | padding: 5vw; 8 | } 9 | 10 | .row { 11 | padding-top: 1em; 12 | } 13 | 14 | .row label { 15 | display: block; 16 | } 17 | 18 | .row button { 19 | display: block; 20 | } -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/convert.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:youtube_subs/youtube_subs.dart'; 5 | 6 | void main(List args) async { 7 | final string = await stdin.transform(Utf8Decoder()).join(); 8 | final subs = YouTubeSubs.fromString(string); 9 | print(subs.render().join()); 10 | } 11 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: youtube_subs 2 | description: An absolute bare-bones web app. 3 | version: 1.0.0 4 | # homepage: https://www.example.com 5 | 6 | environment: 7 | sdk: '>=2.16.1 <3.0.0' 8 | 9 | 10 | dependencies: 11 | characters: ^1.2.0 12 | test: ^1.20.2 13 | 14 | dev_dependencies: 15 | build_runner: ^2.1.4 16 | build_web_compilers: ^3.2.1 17 | lints: ^1.0.0 18 | -------------------------------------------------------------------------------- /web/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | 3 | import 'package:youtube_subs/youtube_subs.dart'; 4 | 5 | void main() { 6 | var button = querySelector('#submit')! as ButtonElement; 7 | button.onClick.listen(_handleSubmit); 8 | } 9 | 10 | void _handleSubmit(MouseEvent event) { 11 | var input = querySelector('#input')! as TextAreaElement; 12 | var output = querySelector('#output')! as TextAreaElement; 13 | 14 | var subs = YouTubeSubs.fromString(input.value ?? 'N/A'); 15 | var result = subs.render().join(); 16 | 17 | output.value = result; 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Takes autogenerated subtitles (from YouTube or other such tool) 2 | and massages them into normal-looking English. 3 | 4 | Deployed to 5 | [filiph.github.io/youtube_subs](https://filiph.github.io/youtube_subs/) 6 | for easy online access. 7 | 8 | ## Development 9 | 10 | This is a pure Dart web app. To run, debug, build, etc., use 11 | [`webdev`](https://dart.dev/tools/webdev). If you're using an IDE like 12 | WebStorm, with a Dart plugin, this will all work out of the box. 13 | 14 | To build and publish to GitHub Pages: 15 | 16 | peanut && git push origin --set-upstream gh-pages 17 | 18 | (You'll need `peanut` installed, with something like 19 | `pub global activate peanut`.) 20 | -------------------------------------------------------------------------------- /.idea/youtube_subs.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the static analysis results for your project (errors, 2 | # warnings, and lints). 3 | # 4 | # This enables the 'recommended' set of lints from `package:lints`. 5 | # This set helps identify many issues that may lead to problems when running 6 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic 7 | # style and format. 8 | # 9 | # If you want a smaller set of lints you can change this to specify 10 | # 'package:lints/core.yaml'. These are just the most critical lints 11 | # (the recommended set includes the core lints). 12 | # The core lints are also what is used by pub.dev for scoring packages. 13 | 14 | include: package:lints/recommended.yaml 15 | 16 | # Uncomment the following section to specify additional rules. 17 | 18 | # linter: 19 | # rules: 20 | # - camel_case_types 21 | 22 | # analyzer: 23 | # exclude: 24 | # - path/to/excluded/files/** 25 | 26 | # For more information about the core and recommended set of lints, see 27 | # https://dart.dev/go/core-lints 28 | 29 | # For additional information about configuring this file, see 30 | # https://dart.dev/guides/language/analysis-options 31 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | YouTube subtitles prettifier 10 | 11 | 12 | 13 | 14 | 15 |
16 |

YouTube subtitles prettifier

17 | 18 |
19 | 20 | 22 | 23 |
24 |
25 | 26 | 28 |
29 | 30 |

31 | Created by Filip Hracek. 32 | Code here. 33 |

34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/youtube_subs_test.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:test/test.dart'; 3 | import 'package:youtube_subs/youtube_subs.dart'; 4 | 5 | void main() { 6 | test('runs', () { 7 | final subs = YouTubeSubs.fromString(r'''hello there, it's march 13. i want to give you a little update on the sample flutter game that i'm working on. just so i think it's better to show it in motion at this point rather than, you know, giving you updates in the doc. which, of course, the dock is still open and still updated. all right. so i have it here in uh on an ipad. and here on an android device. and also well i can i can show you later but um i guess the game is for mobile phones so this is probably the the most important a salient part. i'll open it i think i think you can i think you can hear that it has music. i think you can maybe see uh yeah i think you can see what's what's going on there? so we have the play button of course. we have achievements, leaderboards and settings. if i click on leaderboards, for example, i get the leaderboards which currently has one person on it. which is me. and then achievements. i haven't like added actual achievements but it works. it just says there are no achievements right now. cool. all right. so i can go to play. you can hear there's a little bit of uh sound as well. so and and there's also this kind of um custom transition. i can select a level uh which are now... i love the sounds. i i kind of like them. um then uh i mean what was i saying... yeah oh the the music, of course, is um... we have all the the rights to to use it. it's uh cc0 and cc by. but i also have a permission from the author to use it for this particular project, and even include the audio files in the github repository so everyone can use them. and this is great music for these types of games. played on a mobile phone with pretty bad speakers. great great stuff! okay cool. so uh, again, what i wanted to show you is 8 | 9 | that it all works okay. so i'll i'll win a game if i can i can do that.'''); 10 | expect(() => subs.render(), returnsNormally); 11 | }); 12 | } -------------------------------------------------------------------------------- /lib/youtube_subs.dart: -------------------------------------------------------------------------------- 1 | import 'package:characters/characters.dart'; 2 | 3 | class YouTubeSubs { 4 | final List<_Sentence> sentences; 5 | 6 | YouTubeSubs(this.sentences); 7 | 8 | factory YouTubeSubs.fromString(String string) { 9 | var chars = string.characters; 10 | 11 | var sentences = <_Sentence>[]; 12 | var sentenceStart = 0; 13 | for (int i = 0; i < chars.length; i++) { 14 | final char = chars.characterAt(i); 15 | if (_sentenceEndPunctuation.hasMatch(char.string)) { 16 | // Possible end of sentence. 17 | if (chars.length == i + 1 || 18 | _whitespace.hasMatch(chars.characterAt(i + 1).string)) { 19 | var sentenceChars = chars.getRange(sentenceStart, i); 20 | var sentenceEndPunctuation = char; 21 | sentences.add( 22 | _Sentence.fromCharacters(sentenceChars, sentenceEndPunctuation)); 23 | // The punctuation plus the whitespace. 24 | sentenceStart = i + 2; 25 | } 26 | } 27 | } 28 | 29 | return YouTubeSubs(sentences); 30 | } 31 | 32 | Iterable render() sync* { 33 | for (final sentence in sentences) { 34 | yield* sentence.render(); 35 | yield ' '; 36 | } 37 | } 38 | 39 | @override 40 | String toString() => 'YouTubeSubs(${sentences.length} sentences)'; 41 | } 42 | 43 | class _Sentence { 44 | final List<_Word> words; 45 | 46 | _Sentence(this.words, this.endPunctuation); 47 | 48 | factory _Sentence.fromCharacters( 49 | Characters rawSentence, Characters endPunctuation) { 50 | final words = <_Word>[]; 51 | 52 | var wordStart = 0; 53 | for (int i = 0; i < rawSentence.length; i++) { 54 | if (_whitespace.hasMatch(rawSentence.characterAt(i).string)) { 55 | var wordChars = rawSentence.getRange(wordStart, i); 56 | 57 | final isNotEmpty = 58 | wordChars.isNotEmpty && !_whitespace.hasMatch(wordChars.string); 59 | final isNotFillWord = !_fillWords.contains(wordChars.string); 60 | bool isNotRepeatingLastWord = true; 61 | if (words.isNotEmpty) { 62 | if (words.last.text == wordChars) { 63 | isNotRepeatingLastWord = false; 64 | } 65 | } 66 | if (isNotEmpty && isNotFillWord && isNotRepeatingLastWord) { 67 | words.add(_Word(wordChars)); 68 | } 69 | // The punctuation plus the whitespace. 70 | wordStart = i + 1; 71 | } 72 | } 73 | var wordChars = rawSentence.getRange(wordStart); 74 | words.add(_Word(wordChars)); 75 | 76 | return _Sentence(words, endPunctuation); 77 | } 78 | 79 | Characters endPunctuation; 80 | 81 | Iterable render() sync* { 82 | yield* words.first.render(capitalized: true); 83 | 84 | for (final word in words.skip(1)) { 85 | yield ' '; 86 | yield* word.render(); 87 | } 88 | 89 | yield endPunctuation.string; 90 | } 91 | } 92 | 93 | final _sentenceEndPunctuation = RegExp(r'[\!\?\.]+'); 94 | 95 | final _whitespace = RegExp(r'^\s+$'); 96 | 97 | class _Word { 98 | final Characters text; 99 | 100 | _Word(this.text) 101 | : assert(text.isNotEmpty), 102 | assert(text.characterAt(0).isNotEmpty); 103 | 104 | Iterable render({bool capitalized = false}) sync* { 105 | for (var exception in _specialCapitalization) { 106 | if (exception.toLowerCase() == text.toLowerCase().string) { 107 | yield exception; 108 | return; 109 | } 110 | } 111 | 112 | // Special cases. 113 | final autoAllCaps = _alwaysAllCaps.contains(text.string); 114 | if (autoAllCaps) { 115 | yield text.toUpperCase().string; 116 | return; 117 | } 118 | 119 | final autoCapitalized = _alwaysCapitalized.contains(text.string); 120 | if (capitalized || autoCapitalized) { 121 | yield text.characterAt(0).toUpperCase().string; 122 | } else { 123 | yield text.characterAt(0).string; 124 | } 125 | 126 | yield text.getRange(1).string; 127 | } 128 | } 129 | 130 | const _alwaysCapitalized = [ 131 | 'i', 132 | "i'll", 133 | "i'm", 134 | "i'd", 135 | 'mac', 136 | 'apple', 137 | 'google', 138 | 'flutter', 139 | 'android', 140 | 'filip', 141 | 'prague', 142 | 'czech', 143 | 'linux', 144 | 'windows', 145 | 'speedometer', 146 | 'udemy', 147 | ]; 148 | 149 | const _alwaysAllCaps = [ 150 | 'ide', 151 | 'cpu', 152 | 'ram', 153 | 'usa', 154 | ]; 155 | 156 | const _specialCapitalization = [ 157 | 'MacBook', 158 | 'JavaScript', 159 | 'Xcode', 160 | 'YouTube', 161 | 'GitHub', 162 | ]; 163 | 164 | const _fillWords = [ 165 | 'um', 166 | 'uh', 167 | ]; 168 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "38.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "3.4.1" 18 | archive: 19 | dependency: transitive 20 | description: 21 | name: archive 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "3.3.0" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.3.0" 32 | async: 33 | dependency: transitive 34 | description: 35 | name: async 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.8.2" 39 | bazel_worker: 40 | dependency: transitive 41 | description: 42 | name: bazel_worker 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.0.1" 46 | boolean_selector: 47 | dependency: transitive 48 | description: 49 | name: boolean_selector 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.0" 53 | build: 54 | dependency: transitive 55 | description: 56 | name: build 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.2.1" 60 | build_config: 61 | dependency: transitive 62 | description: 63 | name: build_config 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.0.0" 67 | build_daemon: 68 | dependency: transitive 69 | description: 70 | name: build_daemon 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "3.0.1" 74 | build_modules: 75 | dependency: transitive 76 | description: 77 | name: build_modules 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "4.0.4" 81 | build_resolvers: 82 | dependency: transitive 83 | description: 84 | name: build_resolvers 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "2.0.6" 88 | build_runner: 89 | dependency: "direct dev" 90 | description: 91 | name: build_runner 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "2.1.8" 95 | build_runner_core: 96 | dependency: transitive 97 | description: 98 | name: build_runner_core 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "7.2.3" 102 | build_web_compilers: 103 | dependency: "direct dev" 104 | description: 105 | name: build_web_compilers 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "3.2.2" 109 | built_collection: 110 | dependency: transitive 111 | description: 112 | name: built_collection 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "5.1.1" 116 | built_value: 117 | dependency: transitive 118 | description: 119 | name: built_value 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "8.1.4" 123 | characters: 124 | dependency: "direct main" 125 | description: 126 | name: characters 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.2.0" 130 | charcode: 131 | dependency: transitive 132 | description: 133 | name: charcode 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "1.3.1" 137 | checked_yaml: 138 | dependency: transitive 139 | description: 140 | name: checked_yaml 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "2.0.1" 144 | code_builder: 145 | dependency: transitive 146 | description: 147 | name: code_builder 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "4.1.0" 151 | collection: 152 | dependency: transitive 153 | description: 154 | name: collection 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "1.16.0" 158 | convert: 159 | dependency: transitive 160 | description: 161 | name: convert 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "3.0.1" 165 | coverage: 166 | dependency: transitive 167 | description: 168 | name: coverage 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "1.2.0" 172 | crypto: 173 | dependency: transitive 174 | description: 175 | name: crypto 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "3.0.1" 179 | dart_style: 180 | dependency: transitive 181 | description: 182 | name: dart_style 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "2.2.2" 186 | file: 187 | dependency: transitive 188 | description: 189 | name: file 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "6.1.2" 193 | fixnum: 194 | dependency: transitive 195 | description: 196 | name: fixnum 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "1.0.0" 200 | frontend_server_client: 201 | dependency: transitive 202 | description: 203 | name: frontend_server_client 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "2.1.2" 207 | glob: 208 | dependency: transitive 209 | description: 210 | name: glob 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "2.0.2" 214 | graphs: 215 | dependency: transitive 216 | description: 217 | name: graphs 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "2.1.0" 221 | http_multi_server: 222 | dependency: transitive 223 | description: 224 | name: http_multi_server 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "3.2.0" 228 | http_parser: 229 | dependency: transitive 230 | description: 231 | name: http_parser 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "4.0.0" 235 | io: 236 | dependency: transitive 237 | description: 238 | name: io 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "1.0.3" 242 | js: 243 | dependency: transitive 244 | description: 245 | name: js 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "0.6.4" 249 | json_annotation: 250 | dependency: transitive 251 | description: 252 | name: json_annotation 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "4.4.0" 256 | lints: 257 | dependency: "direct dev" 258 | description: 259 | name: lints 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "1.0.1" 263 | logging: 264 | dependency: transitive 265 | description: 266 | name: logging 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "1.0.2" 270 | matcher: 271 | dependency: transitive 272 | description: 273 | name: matcher 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "0.12.11" 277 | meta: 278 | dependency: transitive 279 | description: 280 | name: meta 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "1.7.0" 284 | mime: 285 | dependency: transitive 286 | description: 287 | name: mime 288 | url: "https://pub.dartlang.org" 289 | source: hosted 290 | version: "1.0.1" 291 | node_preamble: 292 | dependency: transitive 293 | description: 294 | name: node_preamble 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "2.0.1" 298 | package_config: 299 | dependency: transitive 300 | description: 301 | name: package_config 302 | url: "https://pub.dartlang.org" 303 | source: hosted 304 | version: "2.0.2" 305 | path: 306 | dependency: transitive 307 | description: 308 | name: path 309 | url: "https://pub.dartlang.org" 310 | source: hosted 311 | version: "1.8.1" 312 | pool: 313 | dependency: transitive 314 | description: 315 | name: pool 316 | url: "https://pub.dartlang.org" 317 | source: hosted 318 | version: "1.5.0" 319 | protobuf: 320 | dependency: transitive 321 | description: 322 | name: protobuf 323 | url: "https://pub.dartlang.org" 324 | source: hosted 325 | version: "2.0.1" 326 | pub_semver: 327 | dependency: transitive 328 | description: 329 | name: pub_semver 330 | url: "https://pub.dartlang.org" 331 | source: hosted 332 | version: "2.1.1" 333 | pubspec_parse: 334 | dependency: transitive 335 | description: 336 | name: pubspec_parse 337 | url: "https://pub.dartlang.org" 338 | source: hosted 339 | version: "1.2.0" 340 | scratch_space: 341 | dependency: transitive 342 | description: 343 | name: scratch_space 344 | url: "https://pub.dartlang.org" 345 | source: hosted 346 | version: "1.0.1" 347 | shelf: 348 | dependency: transitive 349 | description: 350 | name: shelf 351 | url: "https://pub.dartlang.org" 352 | source: hosted 353 | version: "1.2.0" 354 | shelf_packages_handler: 355 | dependency: transitive 356 | description: 357 | name: shelf_packages_handler 358 | url: "https://pub.dartlang.org" 359 | source: hosted 360 | version: "3.0.0" 361 | shelf_static: 362 | dependency: transitive 363 | description: 364 | name: shelf_static 365 | url: "https://pub.dartlang.org" 366 | source: hosted 367 | version: "1.1.0" 368 | shelf_web_socket: 369 | dependency: transitive 370 | description: 371 | name: shelf_web_socket 372 | url: "https://pub.dartlang.org" 373 | source: hosted 374 | version: "1.0.1" 375 | source_map_stack_trace: 376 | dependency: transitive 377 | description: 378 | name: source_map_stack_trace 379 | url: "https://pub.dartlang.org" 380 | source: hosted 381 | version: "2.1.0" 382 | source_maps: 383 | dependency: transitive 384 | description: 385 | name: source_maps 386 | url: "https://pub.dartlang.org" 387 | source: hosted 388 | version: "0.10.10" 389 | source_span: 390 | dependency: transitive 391 | description: 392 | name: source_span 393 | url: "https://pub.dartlang.org" 394 | source: hosted 395 | version: "1.8.2" 396 | stack_trace: 397 | dependency: transitive 398 | description: 399 | name: stack_trace 400 | url: "https://pub.dartlang.org" 401 | source: hosted 402 | version: "1.10.0" 403 | stream_channel: 404 | dependency: transitive 405 | description: 406 | name: stream_channel 407 | url: "https://pub.dartlang.org" 408 | source: hosted 409 | version: "2.1.0" 410 | stream_transform: 411 | dependency: transitive 412 | description: 413 | name: stream_transform 414 | url: "https://pub.dartlang.org" 415 | source: hosted 416 | version: "2.0.0" 417 | string_scanner: 418 | dependency: transitive 419 | description: 420 | name: string_scanner 421 | url: "https://pub.dartlang.org" 422 | source: hosted 423 | version: "1.1.0" 424 | term_glyph: 425 | dependency: transitive 426 | description: 427 | name: term_glyph 428 | url: "https://pub.dartlang.org" 429 | source: hosted 430 | version: "1.2.0" 431 | test: 432 | dependency: "direct main" 433 | description: 434 | name: test 435 | url: "https://pub.dartlang.org" 436 | source: hosted 437 | version: "1.20.2" 438 | test_api: 439 | dependency: transitive 440 | description: 441 | name: test_api 442 | url: "https://pub.dartlang.org" 443 | source: hosted 444 | version: "0.4.9" 445 | test_core: 446 | dependency: transitive 447 | description: 448 | name: test_core 449 | url: "https://pub.dartlang.org" 450 | source: hosted 451 | version: "0.4.11" 452 | timing: 453 | dependency: transitive 454 | description: 455 | name: timing 456 | url: "https://pub.dartlang.org" 457 | source: hosted 458 | version: "1.0.0" 459 | typed_data: 460 | dependency: transitive 461 | description: 462 | name: typed_data 463 | url: "https://pub.dartlang.org" 464 | source: hosted 465 | version: "1.3.0" 466 | vm_service: 467 | dependency: transitive 468 | description: 469 | name: vm_service 470 | url: "https://pub.dartlang.org" 471 | source: hosted 472 | version: "8.2.2" 473 | watcher: 474 | dependency: transitive 475 | description: 476 | name: watcher 477 | url: "https://pub.dartlang.org" 478 | source: hosted 479 | version: "1.0.1" 480 | web_socket_channel: 481 | dependency: transitive 482 | description: 483 | name: web_socket_channel 484 | url: "https://pub.dartlang.org" 485 | source: hosted 486 | version: "2.1.0" 487 | webkit_inspection_protocol: 488 | dependency: transitive 489 | description: 490 | name: webkit_inspection_protocol 491 | url: "https://pub.dartlang.org" 492 | source: hosted 493 | version: "1.0.0" 494 | yaml: 495 | dependency: transitive 496 | description: 497 | name: yaml 498 | url: "https://pub.dartlang.org" 499 | source: hosted 500 | version: "3.1.0" 501 | sdks: 502 | dart: ">=2.16.1 <3.0.0" 503 | -------------------------------------------------------------------------------- /example/example.txt: -------------------------------------------------------------------------------- 1 | no. the answer is no. at least in my opinion. but let's get to it first. 2 | 3 | look, i am a cheapo. i i wear inexpensive clothes. i drive used, you know, toyota. this office is one of the cheapest in the city. the the the furniture that you see here, all the furniture here, is something that they wanted to throw away. and i was like, can you give it to me instead? and they were like, yeah, sure. and so so yeah, i am a cheap person. 4 | 5 | but, i don't mind spending money for things that i find important. especially equipment. i have a business here. i'm a lecturer and a game developer, and of course i understand that businesses have expenses expenses. i like the analogy with farmers and tractors, right? so so if i didn't have a software development and lecturing business, and instead i had an agricultural business, i would be buying a tractor once in a while. and tractors, i learned today, are pretty expensive. like fifty thousand dollars for a used, but still pretty high-end, tractor, apparently. that's that's pretty expensive. but if you want to do your job well, you sometimes need a tractor. and and that tractor, you probably don't want to be too cheap about the tractor because it's something that you will be using eight hours a day at least. it needs to do all the things. it needs to be reliable and stuff. so, the same way that i would be buying good tractors if i were a farmer, i don't mind spending money for equipment that lets me be a good developer. like a computer! 6 | 7 | so when i learned that there is a new machine on the market that has, in some ways, unprecedented computing power, i was like: i'm listening. and i'm of course talking about the new Mac Studio with the M1 Ultra chip. and then someone said that "this thing right here is literally the best mac ever made for programming". 8 | 9 | i was ready to investigate. when there is a new tractor on the market that is apparently the best for the job that i'm trying to do then i should probably look into it. so, the first thing that i did was i looked at YouTube videos. not the apple event because that's basically like a one hour long advertisement. "Mac Studio is a breakthrough". 10 | "M1 Ultra is the next breakthrough". "Silicon enables us to provide experiences that no one else can". But on people who are reviewing stuff. and i looked at them and their benchmarks, and i spent a good, i don't know, half an hour, maybe, or even more, just kind of looking at youtube videos, getting excited. then i realized, okay, so this is a lot of money. i should probably be a lot more analytical. and so this video is about my process of thinking about it, in an analytical sense. and how i went about it. in the end, i realized that the Mac Studio with the Ultra chip is not worth it. definitely not for me, and i would wager that not for most developers out there. and then i was like, did i just spend four hours of my day trying to get to this conclusion? and, like, making spreadsheets and watching videos and doing benchmarks and so on? so, i realized, okay what i'll do, is: i'll spend another four hours, at least, making a video about it. just, like and subscribe okay? 11 | 12 | first, let's talk about why even choose a mac at all for software development. and i'm not trying to persuade anyone about anything. and i realize that this is a topic for more than a single video even, let alone just a part of a video. but i just want to dispel the myth that macs are used only because of marketing. only because of the shininess of this thing. that's not true. 13 | 14 | to be honest, this myth is not completely unfounded, right? apple spends a ton of money for marketing, and they are known for bending the facts in order to tell the narrative that, you know, apple products are best at everything, and they were the first to do everything, and they're like super innovative and stuff like this. which is not always the truth. 15 | 16 | it does make sense to be very wary of anything that apple is trying to sell you. 17 | 18 | but i have a very real comparison here. about three years ago i needed to buy a new development computer for personal use. i used, you know, i was employed at google, so obviously i had no trouble having a development computer for work. but for personal use, i didn't have anything, and i decided to go for a window / linux laptop. and i also decided to go for the top of the line of those things, right? so i spent a lot of money. my windows / linux laptop is almost two times more expensive than my current macbook so it's not like... i hate it when sometimes people compare, you know, a $800 windows laptop with a $2,000 macbook pro, and they're like, oh, the macbook Pro has a better finish or whatever. it just doesn't make any sense. but this is the opposite. the linux / windows machine is much more expensive than the macbook. and yet here we are. i'm using the macbook for everything. for for development, for video editing work, for graphics work, for everything. i was very motivated to switch to linux / windows. and it just didn't work. 19 | 20 | one reason for this is that the lowly macbook works faster. and it shouldn't, right? by sheer power, it should be slower. but for almost anything that matters, the macbook works faster than the windows / linux laptop. i think i'll show you some benchmarks later that show even my windows / linux laptop, so that you can compare what i see in normal life. 21 | 22 | there are of course some unfair reasons, too. for example, it is not linux's fault that there are no good graphical um design suites on linux. at least in my opinion. and i am a game developer. so so i do need to sometimes work on graphics. i also sometimes need to work on music, and sound, and stuff like this. these all are much better on macbook than on a linux or windows machine. in, again, in my opinion. look, i i want to stress that this is not an attack on anyone's choices. i'm not trying to persuade anyone that they should switch to mac or whatever. it has a lot of issues as well. i'm just trying to dispel the myth that uh the only reason why developers, especially in recent years, switch to mac, is because of marketing. or because of shininess. it's it's not true. 23 | 24 | okay, back to the mac Studio with the Ultra chip. at first, i was really impressed, i have to admit. i looked at the benchmarks, and i started putting them into the spreadsheet, and they mostly looked amazing. everything was faster, some things were much faster. like, two times faster than the top of the line that is currently the case. so i was very, like, oh, okay, so this might be a thing! i might actually be buying this! whoa! but then i noticed one thing. there were benchmarks where the mac Studio completely killed. like as i said, some benchmarks two times faster than the top of the line up till today, right? and then there were ones that were still often faster but not by much. and sometimes almost by nothing. and the ones where mac Studio ultra killed was things like very parallelizable um things. like ray tracing, or sometimes export of video and stuff like this. and the ones where it didn't completely kill where things that were more like, in my purview. things like, for example, xcode export. or even just some, you know, export of video. but like a normal video and not the perfect thing that could be exported from the mac studio ultra, right? 25 | 26 | so, yes the benchmarks were always very impressive, but the really _really_ impressive ones, the ones that would blow your mind, were very specific for long-running batch jobs that you ideally want on some server somewhere. and the ones that were only, well, impressive still but not mind-blowing, were the ones that you would expect from a computer, the one that you use every day. so this is the problem with benchmarks versus real world use, right? 27 | 28 | benchmarks, for them to be like a single meaningful number, they need to do something that's long-running, sustained performance. something like a big export, ray tracer running for a whole video or something. and then that makes complete sense, and then for that they are great. but real world use, most of the time, you're not using your computer as in, like, you're just watching a renderer render things. you are writing code, and hot reloading, or even sometimes recompiling, but, generally, 99% of the time, you're not using your multi-core processor on 100%. 29 | 30 | the best benchmark that i found that kind of gives you an idea of how fast a computer works, how fast it is in these day-to-day, minute-to-minute tasks, is speedometer. speedometer basically runs a bunch of web apps in quick succession. and so it it shows how how the computer responds to single core use, because JavaScript is single core (mostly). it also shows how it works in multi-core use because the browser is generally using many cores. and then it also uses the gpu because browsers use gpu to render stuff. but, more importantly, it's about the kind of... it loads the app and immediately runs it, and does a bunch of things, but it's it's not a sustained performance. it's not rendering anything, or building anything. it's it's just, like, quick, very fast things to run, right? so, it's closer to how how normal use of your computer feels like when you're using it. in many, ways it's close to how you use an IDE when you're typing. most of the time, of course, your computer is idle, just waiting for your input. but then you type a new character, and then oftentimes it needs to reparse. which is a single core... parsing a file is a single core computation. but then it also sometimes needs to analyze more than just that one file. it needs to run an analyzer on, maybe, your whole project. or a big chunk of it. which is something between single core and multi-core. and also it needs to update the the UI, of course, so that's that's GPU. these kinds of things, right? and you want to know how your computer will respond when typing that kind of code, to see if this kind of workload, writing code, will be snappy and fast. you can't just look at technical specifications and see, you know, the the processor speed, or anything like that. there are other things in the layout of the computer that are more important. for example, access to memory. how fast can the computer, the CPU, access RAM and you can just look at the maximum theoretical speed. there's more things at play. so the speed could be half of that in in real life. so that's why, if you look at speedometer benchmarks, you'll see that mac studio ultra isn't killing. it's it's not that much better than my old mac, and it's almost no better than a recent macbook Pro with the Max CPU. basically half of the cores. you just can't, for this kind of workload, use all these cores, all this extra power. this also goes back to my expensive windows / linux laptop. it has a speedometer score of 73. versus my macbook Pro that has 183. and it really feels like that. when you use those computers, the macbook, even though it shouldn't, by technical specification, it feels much faster. um especially when browsing the web. 31 | 32 | at first i thought that it's just my imagination but it's not. it's really faster. 33 | 34 | so, okay, so my current macbook has 183. if you look at the mac Studio Ultra, it has 292. so a lot faster. but again, at this point, does it really matter? it's not a linearly perceivable scale, right? at this point, i'm very happy about the responsiveness of my computer. so not a big deal. but also, if you compare the Studio Ultra to the Macbook Pro Max that is cheaper and older, and has half the cores, and half almost everything, they have the same score. they have 293 speedometer score. so really, at this point, it's overkill. 35 | 36 | but speedometer score is about how fast, how snappy it is to use your computer. but software development is also about building, about compilation, about long-running batch tasks, right? true, but it depends. most of us software developers use some kind of a shortened build, a shortened compilation, than what was common in the 90s, right? i'm a software developer using flutter. and flutter has "Hot Reload", which means, i make a change, i hit save, and under a second, even with this computer, under a second i have that change compiled and put into my running app. which means, i hit save, i switch tabs to the app or game, and it's already there. so if something gives me a hundred millisecond faster compile time for "Hot Reload", it doesn't matter. it really doesn't. and it's not just flutter, of course. these days, even if you don't have something like Hot Reload, or Hot Restart, you have at least some part-compilation that lets you build, or at least try your app or game, at some level, maybe just part of it, but without compiling the whole massive thing at once. 37 | 38 | and then there are the times where you have to build everything at once, right? like, for example, i need to profile my app so that i can see how it behaves when it's fully compiled in profile mode. so i do that. and that takes time. it could take several seconds, often half a minute, minute. for a really big project, it could take several minutes or even tens of minutes, right? but it's not like a ray tracing job. it's not going to completely halt my progress. i'm not just looking at the terminal as my app or game is building. i am doing other stuff because the building itself is actually just using maybe, i don't know, half of the resources that the computer can give it. and so the rest of the computer is for me to just to keep working. a good developer starts the compilation, starts the build process, and goes to do something else. even if that something else is, you know, getting some coffee. 39 | 40 | but you don't just stare at the terminal. 41 | 42 | and with that we should be looking at the time difference between the different devices, right? so, my device takes 130 seconds to build something in xcode, right? i found a benchmark and that's what it says. 130 seconds for my device. the mac Studio Ultra takes 67 seconds only. so that's half as much. which is great, right? so this is fantastic. but again, you're doing something else um at the time. you're not just staring at the screen. you're not doing this every minute or every hour. you're doing it, i think, at least in my case, i'm definitely not doing a big build every hour. and it's it's just not... it doesn't correspond to the power of of mac Studio Ultra. mac Studio Ultra, in long-running benchmarks, completely destroys my computer here. like, it's four times, five times faster to export video (in very very specific circumstances) or to render a 3d object. but in xcode, it is only half, i mean twice as fast. and again, you're doing something else while it's fast, so does it really matter? and here i'm comparing my pretty lowly 13-inch macbook Pro from 2020. if you compare to something like the newer 16-inch macbook Pro with the Max chip (not the Ultra, of course, but the Max chip), it's still slower than the mac Studio Ultra, but not by much. i think it takes 93 seconds versus 67 seconds. and again, this is something that happens in the background, so not a big deal. 43 | 44 | plus, i should point out that this is an xcode _benchmark_, right? this is designed to to push the limits, and to give the computer as much work as possible in a limited amount of time. it's a benchmark. if we were comparing a build that needs to do more than just exercise the cpu, but an actual xcode build, or android studio build, the differences would be even lower. because a build often does things that are not CPU-bound. it goes to the network and stuff like this. so the difference there is not that big. 45 | 46 | so here we are. if you look at the Cinebench benchmarks, and all the other, like GeekBench benchmarks, and stuff like this, and look at the scores of my current computer, for example, and this beast, the Mac Studio Ultra, you'd be, like, of course, this is such a great investment. Like, you're spending a significant amount of money but you're getting three times as much value from this as from that other thing. so, of course! 47 | 48 | but now we're realizing that the real world benchmarks, and the real world value that you get from this, is much lower than what the benchmarks might suggest. and i'm not even talking about the fact that most people these days, if they build code so they need a significant amount of compute, they will probably do it in the cloud. using some CI/CD service. for example, i use Codemagic, which means that, if i want a release build, i commit my code, tag it with something, and send it to github. and then something else will pick it up. in my case, Codemagic's CI/CD pipeline will pick it up. and they will use their computers to build everything and i don't even need to care about it. and then i get an email, right? 49 | 50 | for them, for Codemagic, it might make sense to buy a beast like this. and then sell it as a premium service for their customers. so, for example, let's say that your build times improve by five to ten percent in the next five to ten years. like, every build that you do from now on in a CI/CD pipeline is five to ten percent faster. how much money would you give for that opportunity? would it be five thousand eight hundred dollars? not for me. for some bigger studio, sure. for a big company, no problem. but for for a solo dev, or for a small studio, i don't think you would go for that. 51 | 52 | and of course then we have the thing that mac Studio is just a box. you don't get a monitor. you don't get keyboard and mouse. and so that's additional things that you need to buy. but that's not the real issue here. the fact is that laptops are better. many times before, i thought about, okay, maybe i will buy a big box that sits under my desk. but it always gets completely steamrolled by the fact that laptops are portable and you can use them anywhere. and i often do! i will take my laptop from this office and i go somewhere and i have everything in it. so i can keep programming. the builds that i have there are there. i don't need to think about synchronizing stuff. i can work, if i have like half an hour at home, i can keep writing code. of course, i have a small screen, so it's not great, but for writing code, if you know what you're writing, and you're writing code, you can do it. 53 | 54 | whereas if you have a mac Studio somewhere, you're not going to lug it around. it's going to be there. and if there's a specific environment there that you have set up, you will have to duplicate it anywhere else that you need to work from. 55 | 56 | for me, having one single computer, even in this world of cloud, is so much value. i can do everything from here. and i can take it on a train. i can take it and i can work from home. i can work when meeting someone, before i see them. it's just mind-blowingly powerful to have a full computer including screen and mouse and keyboard in one package that i can put into my backpack. so, yeah, mac Studio Ultra is not worth it for software developers, especially not for, like, solo software developers or smaller studios. if you want an upgrade, then i would suggest get the macbook Pro 16-inch or 14-inch or whatever, with the Max chip. It is basically the same performance, for most purposes. it is cheaper than the mac Studio. it is portable. it's a laptop! 57 | 58 | i like how Marques Brownlee concluded his review of mac Studio Ultra, and he was looking at it from the perspective of video editing. "This thing is overkill. And i love that that's the narrative around this computer. like, do you really need that much power? probably not". see? you don't need it. 59 | 60 | i hope i saved you some time. and don't forget that if you want to save more time, i have a Udemy course on how to use android Studio shortcuts. to be better and faster at programming. doesn't require a better computer! and that's it from me. see you around. -------------------------------------------------------------------------------- /.idea/dbnavigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_Packages.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | --------------------------------------------------------------------------------