ExportChapter.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. <?php
  2. namespace App\Console\Commands;
  3. use Illuminate\Console\Command;
  4. use Illuminate\Support\Facades\Storage;
  5. use App\Models\ProgressChapter;
  6. use App\Models\Channel;
  7. use App\Models\PaliText;
  8. use App\Models\Sentence;
  9. use App\Http\Api\ChannelApi;
  10. use App\Http\Api\MdRender;
  11. use App\Tools\Export;
  12. use Illuminate\Support\Facades\Log;
  13. use App\Tools\RedisClusters;
  14. class ExportChapter extends Command
  15. {
  16. protected $exportStatusKey = 'export/status';
  17. protected $exportStatusExpiry = 3600;
  18. protected $currExportStatusKey = '';
  19. /**
  20. * The name and signature of the console command.
  21. * php artisan export:chapter 213 1913 a19eaf75-c63f-4b84-8125-1bce18311e23 213-1913 --format=html
  22. * php artisan export:chapter 168 915 19f53a65-81db-4b7d-8144-ac33f1217d34 168-915.html --format=html
  23. * php artisan export:chapter 168 915 19f53a65-81db-4b7d-8144-ac33f1217d34 168-915.html --format=html --origin=true
  24. * @var string
  25. */
  26. protected $signature = 'export:chapter {book} {para} {channel} {filename} {--origin=false} {--translation=true} {--debug} {--format=tex} ';
  27. /**
  28. * The console command description.
  29. *
  30. * @var string
  31. */
  32. protected $description = 'Command description';
  33. /**
  34. * Create a new command instance.
  35. *
  36. * @return void
  37. */
  38. public function __construct()
  39. {
  40. parent::__construct();
  41. }
  42. /**
  43. * progress: 0-1, error -1
  44. * message: string
  45. */
  46. protected function setStatus($progress,$message=''){
  47. RedisClusters::put($this->currExportStatusKey,
  48. [
  49. 'progress'=>$progress,
  50. 'message'=>$message,
  51. ],
  52. $this->exportStatusExpiry);
  53. $percent = (int)($progress * 100);
  54. $this->info("[{$percent}%]".$message);
  55. }
  56. public function getStatus($filename){
  57. return RedisClusters::get($this->exportStatusKey.'/'.$filename);
  58. }
  59. /**
  60. * Execute the console command.
  61. *
  62. * @return int
  63. */
  64. public function handle()
  65. {
  66. $this->info('task export chapter start');
  67. Log::debug('task export chapter start');
  68. if(\App\Tools\Tools::isStop()){
  69. return 0;
  70. }
  71. $m = new \Mustache_Engine(array('entity_flags'=>ENT_QUOTES,
  72. 'delimiters' => '[[ ]]',
  73. 'escape'=>function ($value){
  74. return $value;
  75. }));
  76. $tplParagraph = file_get_contents(resource_path("mustache/".$this->option('format')."/paragraph.".$this->option('format')));
  77. MdRender::init();
  78. $this->currExportStatusKey = $this->exportStatusKey . '/' . $this->argument('filename');
  79. switch ($this->option('format')) {
  80. case 'md':
  81. $renderFormat='markdown';
  82. break;
  83. case 'html':
  84. $renderFormat='html';
  85. break;
  86. default:
  87. $renderFormat=$this->option('format');
  88. break;
  89. }
  90. $book = $this->argument('book');
  91. $para = $this->argument('para');
  92. //获取原文channel
  93. $orgChannelId = ChannelApi::getSysChannel('_System_Pali_VRI_');
  94. $tranChannelsId = explode('_',$this->argument('channel'));
  95. $channelsId = array_merge([$orgChannelId],$tranChannelsId);
  96. $channels = array();
  97. $channelsIndex = array();
  98. foreach ($channelsId as $key => $id) {
  99. $channels[] = ChannelApi::getById($id);
  100. $channelsIndex[$id] = ChannelApi::getById($id);
  101. }
  102. $bookMeta = array();
  103. $bookMeta['book_author'] = "";
  104. foreach ($channels as $key => $channel) {
  105. $bookMeta['book_author'] .= $channel['name'] . ' ';
  106. }
  107. $chapter = PaliText::where('book',$book)
  108. ->where('paragraph',$para)->first();
  109. if(!$chapter){
  110. return $this->error("no data");
  111. }
  112. $currProgress = 0;
  113. $this->setStatus($currProgress,'start');
  114. if(empty($chapter->toc)){
  115. $bookMeta['title'] = "unknown";
  116. }else{
  117. $bookMeta['book_title'] = '';
  118. foreach ($channelsId as $key => $id) {
  119. $title = ProgressChapter::where('book',$book)->where('para',$para)
  120. ->where('channel_id',$id)
  121. ->value('title');
  122. $bookMeta['book_title'] .= $title;
  123. }
  124. $bookMeta['sub_title'] = $chapter->toc;
  125. }
  126. $subChapter = PaliText::where('book',$book)->where('parent',$para)
  127. ->where('level','<',8)
  128. ->orderBy('paragraph')
  129. ->get();
  130. if(count($subChapter) === 0){
  131. //没有子章节
  132. $subChapter = PaliText::where('book',$book)->where('paragraph',$para)
  133. ->where('level','<',8)
  134. ->orderBy('paragraph')
  135. ->get();
  136. }
  137. $chapterParagraph = PaliText::where('book',$book)->where('paragraph',$para)->value('chapter_len');
  138. if($chapterParagraph >0 ){
  139. $step = 0.9 / $chapterParagraph;
  140. }else{
  141. $step = 0.9;
  142. Log::error('段落长度不能为0',['book'=>$book,'para'=>$para]);
  143. }
  144. $outputChannelsId = [];
  145. if($this->option('origin') === 'true'){
  146. $outputChannelsId[] = $orgChannelId;
  147. }
  148. if($this->option('translation') === 'true'){
  149. $outputChannelsId = array_merge($outputChannelsId,$tranChannelsId);
  150. }
  151. $sections = array();
  152. foreach ($subChapter as $key => $sub) {
  153. # 看这个章节是否存在译文
  154. $hasChapter = false;
  155. if($this->option('origin') === 'true'){
  156. $hasChapter = true;
  157. }
  158. if($this->option('translation') === 'true'){
  159. foreach ($tranChannelsId as $id) {
  160. if(ProgressChapter::where('book',$book)->where('para',$sub->paragraph)
  161. ->where('channel_id',$id)
  162. ->exists()){
  163. $hasChapter = true;
  164. }
  165. }
  166. }
  167. if(!$hasChapter){
  168. //不存在需要导出的数据
  169. continue;
  170. }
  171. $filename = "{$sub->paragraph}.".$this->option('format');
  172. $bookMeta['sections'][] = ['filename'=>$filename];
  173. $paliTitle = PaliText::where('book',$book)
  174. ->where('paragraph',$sub->paragraph)
  175. ->value('toc');
  176. $sectionTitle = $paliTitle;
  177. if($this->option('translation') === 'true'){
  178. $chapter = ProgressChapter::where('book',$book)->where('para',$sub->paragraph)
  179. ->where('channel_id',$tranChannelsId[0])
  180. ->first();
  181. if($chapter && !empty($chapter->title)){
  182. $sectionTitle = $chapter->title;
  183. }
  184. }
  185. $content = array();
  186. $chapterStart = $sub->paragraph+1;
  187. $chapterEnd = $sub->paragraph + $sub->chapter_len;
  188. $chapterBody = PaliText::where('book',$book)
  189. ->whereBetween('paragraph',[$chapterStart,$chapterEnd])
  190. ->orderBy('paragraph')->get();
  191. foreach ($chapterBody as $body) {
  192. $currProgress += $step;
  193. $this->setStatus($currProgress,'export chapter '.$body->paragraph);
  194. $paraData = array();
  195. $paraData['translations'] = array();
  196. foreach ($outputChannelsId as $key => $channelId) {
  197. $translationData = Sentence::where('book_id',$book)
  198. ->where('paragraph',$body->paragraph)
  199. ->where('channel_uid',$channelId)
  200. ->orderBy('word_start')->get();
  201. $sentContent = array();
  202. foreach ($translationData as $sent) {
  203. $texText = MdRender::render($sent->content,
  204. [$sent->channel_uid],
  205. null,
  206. 'read',
  207. $channelsIndex[$channelId]['type'],
  208. $sent->content_type,
  209. $renderFormat
  210. );
  211. $sentContent[] = trim($texText);
  212. }
  213. $paraContent = implode(' ',$sentContent);
  214. if($channelsIndex[$channelId]['type'] === 'original'){
  215. $paraData['origin'] = $paraContent;
  216. }else{
  217. $paraData['translations'][] = ['content'=>$paraContent];
  218. }
  219. }
  220. if($body->level > 7){
  221. $content[] = $m->render($tplParagraph,$paraData);
  222. }else{
  223. $currLevel = $body->level - $sub->level;
  224. if($currLevel<=0){
  225. $currLevel = 1;
  226. }
  227. if(count($paraData['translations'])===0){
  228. $subSessionTitle = PaliText::where('book',$book)
  229. ->where('paragraph',$body->paragraph)
  230. ->value('toc');
  231. }else{
  232. $subSessionTitle = $paraData['translations'][0]['content'];
  233. }
  234. switch ($this->option('format')) {
  235. case 'tex':
  236. $subStr = array_fill(0,$currLevel,'sub');
  237. $content[] = '\\'. implode('',$subStr) . "section{".$subSessionTitle.'}';
  238. break;
  239. case 'md':
  240. $subStr = array_fill(0,$currLevel,'#');
  241. $content[] = implode('',$subStr) . " ".$subSessionTitle;
  242. break;
  243. case 'html':
  244. $level = $currLevel+2;
  245. $content[] = "<h{$currLevel}".$subSessionTitle."</h{$currLevel}";
  246. break;
  247. }
  248. }
  249. $content[] = "\n\n";
  250. }
  251. $sections[] = [
  252. 'name'=>$filename,
  253. 'body'=>[
  254. 'title'=>$sectionTitle,
  255. 'content'=>implode('',$content)
  256. ]
  257. ];
  258. }
  259. $this->setStatus(0.9,'export content done');
  260. Log::debug('导出结束');
  261. $tex = array();
  262. $tpl = file_get_contents(resource_path("mustache/".$this->option('format')."/main.".$this->option('format')));
  263. $texContent = $m->render($tpl,$bookMeta);
  264. $tex[] = ['name'=>'main.'.$this->option('format'),
  265. 'content'=>$texContent
  266. ];
  267. foreach ($sections as $key => $section) {
  268. $tpl = file_get_contents(resource_path("mustache/".$this->option('format')."/section.".$this->option('format')));
  269. $texContent = $m->render($tpl,$section['body']);
  270. $tex[] = ['name'=>$section['name'],
  271. 'content'=>$texContent
  272. ];
  273. }
  274. Log::debug('footnote start');
  275. //footnote
  276. $tplFile = resource_path("mustache/".$this->option('format')."/footnote.".$this->option('format'));
  277. if(isset($GLOBALS['note']) &&
  278. is_array($GLOBALS['note']) &&
  279. count($GLOBALS['note'])>0 &&
  280. file_exists($tplFile)){
  281. $tpl = file_get_contents($tplFile);
  282. $texContent = $m->render($tpl,['footnote'=>$GLOBALS['note']]);
  283. $tex[] = ['name'=>'footnote.'.$this->option('format'),
  284. 'content'=>$texContent
  285. ];
  286. }
  287. if($this->option('debug')){
  288. $dir = "export/".$this->option('format')."/{$book}-{$para}-{$channelId}/";
  289. foreach ($tex as $key => $section) {
  290. Storage::disk('local')->put($dir.$section['name'], $section['content']);
  291. }
  292. }
  293. Log::debug('footnote finished');
  294. Log::debug('upload start');
  295. $this->setStatus(0.95,'export content done');
  296. //upload
  297. $fileDate = '';
  298. switch ($this->option('format')) {
  299. case 'tex':
  300. $data = Export::ToPdf($tex);
  301. if($data['ok']){
  302. $this->info($data['content-type']);
  303. $fileDate = $data['data'];
  304. }else{
  305. $this->error($data['code'].'-'.$data['message']);
  306. }
  307. break;
  308. case 'html':
  309. $file = array();
  310. foreach ($tex as $key => $section) {
  311. $file[] = $section['content'];
  312. }
  313. $fileDate = implode('',$file);
  314. break;
  315. }
  316. $filename = $this->argument('filename');
  317. $bucket = config('mint.attachments.bucket_name.temporary');
  318. $tmpFile = $bucket.'/'. $filename ;
  319. Storage::put($tmpFile, $fileDate);
  320. $this->setStatus(1,'export chapter done');
  321. Log::debug('export chapter done, upload filename='.$tmpFile);
  322. return 0;
  323. }
  324. }