11package main
22
33import (
4+ "archive/zip"
45 "bytes"
56 "encoding/json"
67 "flag"
@@ -31,6 +32,97 @@ type EpisodeBatch struct {
3132 maxEp int
3233}
3334
35+ type ComicFile interface {
36+ addImage ([]byte ) error
37+ save (outFile string ) error
38+ }
39+
40+ type PDFComicFile struct {
41+ pdf * gopdf.GoPdf
42+ }
43+
44+ // validate PDFComicFile implements ComicFile
45+ var _ ComicFile = & PDFComicFile {}
46+
47+ func newPDFComicFile () * PDFComicFile {
48+ pdf := gopdf.GoPdf {}
49+ pdf .Start (gopdf.Config {Unit : gopdf .UnitPT , PageSize : * gopdf .PageSizeA4 })
50+ return & PDFComicFile {pdf : & pdf }
51+ }
52+
53+ func (c * PDFComicFile ) addImage (img []byte ) error {
54+ holder , err := gopdf .ImageHolderByBytes (img )
55+ if err != nil {
56+ return err
57+ }
58+
59+ d , _ , err := image .DecodeConfig (bytes .NewReader (img ))
60+ if err != nil {
61+ return err
62+ }
63+
64+ // gopdf assumes dpi 128 https://github.com/signintech/gopdf/issues/168
65+ // W and H are in points, 1 point = 1/72 inch
66+ // convert pixels (Width and Height) to points
67+ // subtract 1 point to account for margins
68+ c .pdf .AddPageWithOption (gopdf.PageOption {PageSize : & gopdf.Rect {
69+ W : float64 (d .Width )* 72 / 128 - 1 ,
70+ H : float64 (d .Height )* 72 / 128 - 1 ,
71+ }})
72+ return c .pdf .ImageByHolder (holder , 0 , 0 , nil )
73+ }
74+
75+ func (c * PDFComicFile ) save (outputPath string ) error {
76+ return c .pdf .WritePdf (outputPath )
77+ }
78+
79+ type CBZComicFile struct {
80+ zipWriter * zip.Writer
81+ buffer * bytes.Buffer
82+ numFiles int
83+ }
84+
85+ // validate CBZComicFile implements ComicFile
86+ var _ ComicFile = & CBZComicFile {}
87+
88+ func newCBZComicFile () (* CBZComicFile , error ) {
89+ buffer := new (bytes.Buffer )
90+ zipWriter := zip .NewWriter (buffer )
91+ return & CBZComicFile {zipWriter : zipWriter , buffer : buffer , numFiles : 0 }, nil
92+ }
93+
94+ func (c * CBZComicFile ) addImage (img []byte ) error {
95+ f , err := c .zipWriter .Create (fmt .Sprintf ("%010d.jpg" , c .numFiles ))
96+ if err != nil {
97+ return err
98+ }
99+ _ , err = f .Write (img )
100+ if err != nil {
101+ return err
102+ }
103+ c .numFiles ++
104+ return nil
105+ }
106+
107+ func (c * CBZComicFile ) save (outputPath string ) error {
108+ if err := c .zipWriter .Close (); err != nil {
109+ return err
110+ }
111+ file , err := os .Create (outputPath )
112+ if err != nil {
113+ return err
114+ }
115+ defer func (file * os.File ) {
116+ err := file .Close ()
117+ if err != nil {
118+ fmt .Println (err .Error ())
119+ os .Exit (1 )
120+ }
121+ }(file )
122+ _ , err = c .buffer .WriteTo (file )
123+ return err
124+ }
125+
34126func getOzPageImgLinks (doc soup.Root ) []string {
35127 // regex find the documentURL, e.g:
36128 // viewerOptions: {
@@ -253,37 +345,37 @@ func fetchImage(imgLink string) []byte {
253345 return buff .Bytes ()
254346}
255347
256- func addImgToPdf ( pdf * gopdf. GoPdf , imgLink string ) error {
257- img := fetchImage ( imgLink )
258- holder , err := gopdf . ImageHolderByBytes ( img )
259- if err != nil {
260- return err
261- }
262-
263- d , _ , err := image . DecodeConfig ( bytes . NewReader ( img ))
264- if err != nil {
265- return err
348+ func getComicFile ( format string ) ComicFile {
349+ var comic ComicFile
350+ var err error
351+ comic = newPDFComicFile ()
352+ if format == "cbz" {
353+ comic , err = newCBZComicFile ()
354+ if err != nil {
355+ fmt . Println ( err . Error ( ))
356+ os . Exit ( 1 )
357+ }
266358 }
359+ return comic
360+ }
267361
268- // gopdf assumes dpi 128 https://github.com/signintech/gopdf/issues/168
269- // W and H are in points, 1 point = 1/72 inch
270- // convert pixels (Width and Height) to points
271- // subtract 1 point to account for margins
272- pdf .AddPageWithOption (gopdf.PageOption {PageSize : & gopdf.Rect {
273- W : float64 (d .Width )* 72 / 128 - 1 ,
274- H : float64 (d .Height )* 72 / 128 - 1 ,
275- }})
276- return pdf .ImageByHolder (holder , 0 , 0 , nil )
362+ type Opts struct {
363+ url string
364+ minEp int
365+ maxEp int
366+ epsPerFile int
367+ format string
277368}
278369
279- func main () {
280- if len (os . Args ) < 2 {
370+ func parseOpts ( args [] string ) Opts {
371+ if len (args ) < 2 {
281372 fmt .Println ("Usage: webtoon-dl <url>" )
282373 os .Exit (1 )
283374 }
284375 minEp := flag .Int ("min-ep" , 0 , "Minimum episode number to download (inclusive)" )
285376 maxEp := flag .Int ("max-ep" , math .MaxInt , "Maximum episode number to download (inclusive)" )
286377 epsPerFile := flag .Int ("eps-per-file" , 10 , "Number of episodes to put in each PDF file" )
378+ format := flag .String ("format" , "pdf" , "Output format (pdf or cbz)" )
287379 flag .Parse ()
288380 if * minEp > * maxEp {
289381 fmt .Println ("min-ep must be less than or equal to max-ep" )
@@ -299,50 +391,72 @@ func main() {
299391 }
300392
301393 url := os .Args [len (os .Args )- 1 ]
302- episodeBatches := getEpisodeBatches (url , * minEp , * maxEp , * epsPerFile )
394+ return Opts {
395+ url : url ,
396+ minEp : * minEp ,
397+ maxEp : * maxEp ,
398+ epsPerFile : * epsPerFile ,
399+ format : * format ,
400+ }
401+ }
402+
403+ func getOutFile (opts Opts , episodeBatch EpisodeBatch ) string {
404+ outURL := strings .ReplaceAll (opts .url , "http://" , "" )
405+ outURL = strings .ReplaceAll (outURL , "https://" , "" )
406+ outURL = strings .ReplaceAll (outURL , "www." , "" )
407+ outURL = strings .ReplaceAll (outURL , "webtoons.com/" , "" )
408+ outURL = strings .Split (outURL , "?" )[0 ]
409+ outURL = strings .ReplaceAll (outURL , "/viewer" , "" )
410+ outURL = strings .ReplaceAll (outURL , "/" , "-" )
411+ if episodeBatch .minEp != episodeBatch .maxEp {
412+ outURL = fmt .Sprintf ("%s-epNo%d-epNo%d.%s" , outURL , episodeBatch .minEp , episodeBatch .maxEp , opts .format )
413+ } else {
414+ outURL = fmt .Sprintf ("%s-epNo%d.%s" , outURL , episodeBatch .minEp , opts .format )
415+ }
416+ return outURL
417+ }
303418
419+ func main () {
420+ opts := parseOpts (os .Args )
421+ episodeBatches := getEpisodeBatches (opts .url , opts .minEp , opts .maxEp , opts .epsPerFile )
304422 totalPages := 0
305423 for _ , episodeBatch := range episodeBatches {
306424 totalPages += len (episodeBatch .imgLinks )
307425 }
308426 totalEpisodes := episodeBatches [len (episodeBatches )- 1 ].maxEp - episodeBatches [0 ].minEp + 1
309427 fmt .Println (fmt .Sprintf ("found %d total image links across %d episodes" , totalPages , totalEpisodes ))
310- fmt .Println (fmt .Sprintf ("saving into %d files with max of %d episodes per file" , len (episodeBatches ), * epsPerFile ))
428+ fmt .Println (fmt .Sprintf ("saving into %d files with max of %d episodes per file" , len (episodeBatches ), opts . epsPerFile ))
311429
312430 for _ , episodeBatch := range episodeBatches {
313- pdf := gopdf.GoPdf {}
314- pdf .Start (gopdf.Config {Unit : gopdf .UnitPT , PageSize : * gopdf .PageSizeA4 })
431+ var err error
432+ outFile := getOutFile (opts , episodeBatch )
433+ comicFile := getComicFile (opts .format )
315434 for idx , imgLink := range episodeBatch .imgLinks {
316435 if strings .Contains (imgLink , ".gif" ) {
317436 fmt .Println (fmt .Sprintf ("WARNING: skipping gif %s" , imgLink ))
318437 continue
319438 }
320- err := addImgToPdf ( & pdf , imgLink )
439+ err := comicFile . addImage ( fetchImage ( imgLink ) )
321440 if err != nil {
322441 fmt .Println (err .Error ())
323442 os .Exit (1 )
324443 }
325- fmt .Println (fmt .Sprintf ("saving episodes %d through %d: added page %d/%d" , episodeBatch .minEp , episodeBatch .maxEp , idx + 1 , len (episodeBatch .imgLinks )))
326- }
327-
328- outURL := strings .ReplaceAll (url , "http://" , "" )
329- outURL = strings .ReplaceAll (outURL , "https://" , "" )
330- outURL = strings .ReplaceAll (outURL , "www." , "" )
331- outURL = strings .ReplaceAll (outURL , "webtoons.com/" , "" )
332- outURL = strings .Split (outURL , "?" )[0 ]
333- outURL = strings .ReplaceAll (outURL , "/viewer" , "" )
334- outURL = strings .ReplaceAll (outURL , "/" , "-" )
335- if episodeBatch .minEp != episodeBatch .maxEp {
336- outURL = fmt .Sprintf ("%s-epNo%d-epNo%d" , outURL , episodeBatch .minEp , episodeBatch .maxEp )
337- } else {
338- outURL = fmt .Sprintf ("%s-epNo%d" , outURL , episodeBatch .minEp )
444+ fmt .Println (
445+ fmt .Sprintf (
446+ "saving episodes %d through %d of %d: added page %d/%d" ,
447+ episodeBatch .minEp ,
448+ episodeBatch .maxEp ,
449+ totalEpisodes ,
450+ idx + 1 ,
451+ len (episodeBatch .imgLinks ),
452+ ),
453+ )
339454 }
340- outPath := outURL + ".pdf"
341- err := pdf .WritePdf (outPath )
455+ err = comicFile .save (outFile )
342456 if err != nil {
343457 fmt .Println (err .Error ())
344458 os .Exit (1 )
345459 }
346- fmt .Println (fmt .Sprintf ("saved to %s" , outPath ))
460+ fmt .Println (fmt .Sprintf ("saved to %s" , outFile ))
347461 }
348462}
0 commit comments