diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1ac19d..986aed8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: '1.20' + go-version: '1.24' - name: Install dependencies run: go mod download diff --git a/go.mod b/go.mod index f50c273..9faaf37 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,17 @@ module github.com/asticode/go-astisub -go 1.13 +go 1.24.0 require ( - github.com/asticode/go-astikit v0.20.0 - github.com/asticode/go-astits v1.8.0 + github.com/asticode/go-astikit v0.58.0 + github.com/asticode/go-astits v1.15.0 github.com/stretchr/testify v1.4.0 - golang.org/x/net v0.0.0-20200904194848-62affa334b73 - golang.org/x/text v0.3.2 + golang.org/x/net v0.49.0 + golang.org/x/text v0.33.0 +) + +require ( + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v2 v2.2.2 // indirect ) diff --git a/go.sum b/go.sum index dea5cec..3b32195 100644 --- a/go.sum +++ b/go.sum @@ -1,27 +1,151 @@ -github.com/asticode/go-astikit v0.20.0 h1:+7N+J4E4lWx2QOkRdOf6DafWJMv6O4RRfgClwQokrH8= -github.com/asticode/go-astikit v0.20.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= -github.com/asticode/go-astits v1.8.0 h1:rf6aiiGn/QhlFjNON1n5plqF3Fs025XLUwiQ0NB6oZg= -github.com/asticode/go-astits v1.8.0/go.mod h1:DkOWmBNQpnr9mv24KfZjq4JawCFX1FCqjLVGvO0DygQ= +github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0= +github.com/asticode/go-astikit v0.58.0 h1:WXNpaxCPNFReikHiXvzyDv49NpV/GMD6PV80iem6WGo= +github.com/asticode/go-astikit v0.58.0/go.mod h1:fV43j20UZYfXzP9oBn33udkvCvDvCDhzjVqoLFuuYZE= +github.com/asticode/go-astits v1.15.0 h1:yRyCiUc8Jj4F7clt2GDxHghMpWuFL5rkaLuGUd2/0J4= +github.com/asticode/go-astits v1.15.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKqjFE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= +golang.org/x/telemetry v0.0.0-20250807160809-1a19826ec488/go.mod h1:fGb/2+tgXXjhjHsTNdVEEMZNWA0quBnfrO+AfoDSAKw= +golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE= +golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE= +golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/srt.go b/srt.go index fbf0daf..59e4f60 100644 --- a/srt.go +++ b/srt.go @@ -8,6 +8,7 @@ import ( "time" "unicode/utf8" + "github.com/asticode/go-astikit" "golang.org/x/net/html" ) @@ -223,49 +224,60 @@ func (s Subtitles) WriteToSRT(o io.Writer) (err error) { return } + // Init chainer + c := astikit.NewWriteChainer(o) + // Add BOM header - var c []byte - c = append(c, BytesBOM...) + if _, err = c.Write(astikit.WriteWithLabel("bom", BytesBOM)); err != nil { + return + } // Loop through subtitles for k, v := range s.Items { // Add time boundaries - c = append(c, []byte(strconv.Itoa(k+1))...) - c = append(c, bytesLineSeparator...) - c = append(c, []byte(formatDurationSRT(v.StartAt))...) - c = append(c, bytesSRTTimeBoundariesSeparator...) - c = append(c, []byte(formatDurationSRT(v.EndAt))...) - c = append(c, bytesLineSeparator...) + if _, err = c.Write( + astikit.WriteWithLabel("index", []byte(strconv.Itoa(k+1))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + astikit.WriteWithLabel("start at", []byte(formatDurationSRT(v.StartAt))), + astikit.WriteWithLabel("time boundaries separator", bytesSRTTimeBoundariesSeparator), + astikit.WriteWithLabel("end at", []byte(formatDurationSRT(v.EndAt))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } // Loop through lines for _, l := range v.Lines { - c = append(c, []byte(l.srtBytes())...) + if err = l.writeSRT(c); err != nil { + err = fmt.Errorf("astisub: writing line item failed: %w", err) + return + } } // Add new line - c = append(c, bytesLineSeparator...) - } - - // Remove last new line - c = c[:len(c)-1] - - // Write - if _, err = o.Write(c); err != nil { - err = fmt.Errorf("astisub: writing failed: %w", err) - return + if k < len(s.Items)-1 { + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { + return + } + } } return } -func (l Line) srtBytes() (c []byte) { +func (l Line) writeSRT(c *astikit.WriteChainer) (err error) { for _, li := range l.Items { - c = append(c, li.srtBytes()...) + if err = li.writeSRT(c); err != nil { + err = fmt.Errorf("astisub: writing line item failed: %w", err) + return + } + } + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { + return } - c = append(c, bytesLineSeparator...) return } -func (li LineItem) srtBytes() (c []byte) { +func (li LineItem) writeSRT(c *astikit.WriteChainer) (err error) { // Get color var color string if li.InlineStyle != nil && li.InlineStyle.SRTColor != nil { @@ -283,34 +295,20 @@ func (li LineItem) srtBytes() (c []byte) { pos = li.InlineStyle.SRTPosition } - // Append - if color != "" { - c = append(c, []byte("")...) - } - if b { - c = append(c, []byte("")...) - } - if i { - c = append(c, []byte("")...) - } - if u { - c = append(c, []byte("")...) - } - if pos != 0 { - c = append(c, []byte(fmt.Sprintf(`{\an%d}`, pos))...) - } - c = append(c, []byte(escapeHTML(li.Text))...) - if u { - c = append(c, []byte("")...) - } - if i { - c = append(c, []byte("")...) - } - if b { - c = append(c, []byte("")...) - } - if color != "" { - c = append(c, []byte("")...) + // Write + if _, err = c.Write( + astikit.WriteWithCondition("font color", []byte(""), color != ""), + astikit.WriteWithCondition("bold", []byte(""), b), + astikit.WriteWithCondition("italics", []byte(""), i), + astikit.WriteWithCondition("underline", []byte(""), u), + astikit.WriteWithCondition("position", []byte(fmt.Sprintf(`{\an%d}`, pos)), pos != 0), + astikit.WriteWithLabel("text", []byte(escapeHTML(li.Text))), + astikit.WriteWithCondition("underline close", []byte(""), u), + astikit.WriteWithCondition("italics close", []byte(""), i), + astikit.WriteWithCondition("bold close", []byte(""), b), + astikit.WriteWithCondition("font close", []byte(""), color != ""), + ); err != nil { + return } return } diff --git a/srt_test.go b/srt_test.go index 0c57597..e605c25 100644 --- a/srt_test.go +++ b/srt_test.go @@ -171,3 +171,16 @@ func TestSRTParseDuration(t *testing.T) { assert.Equal(t, 5*time.Second+985*time.Millisecond, s.Items[1].EndAt) assert.Equal(t, "Duration without colon milliseconds", s.Items[1].Lines[0].String()) } + +func BenchmarkWriteToSRT(b *testing.B) { + s, err := astisub.OpenFile("./testdata/example-in.srt") + if err != nil { + b.Fatal(err) + } + w := &bytes.Buffer{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + w.Reset() + s.WriteToSRT(w) + } +} diff --git a/ssa.go b/ssa.go index 228ab2a..5bc8ad0 100644 --- a/ssa.go +++ b/ssa.go @@ -409,57 +409,157 @@ func (b *ssaScriptInfo) metadata() *Metadata { } } -// bytes returns the block as bytes -func (b *ssaScriptInfo) bytes() (o []byte) { - o = []byte("[Script Info]") - o = append(o, bytesLineSeparator...) - for _, c := range b.comments { - o = appendStringToBytesWithNewLine(o, "; "+c) +// write writes the block to the writer +func (b *ssaScriptInfo) write(c *astikit.WriteChainer) (err error) { + if _, err = c.Write(astikit.WriteWithLabel("script info header", []byte("[Script Info]"))); err != nil { + return + } + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { + return + } + for _, comment := range b.comments { + if _, err = c.Write( + astikit.WriteWithLabel("comment start", []byte("; ")), + astikit.WriteWithLabel("comment text", []byte(comment)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if len(b.collisions) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameCollisions+": "+b.collisions) + if _, err = c.Write( + astikit.WriteWithLabel("collisions label", []byte(ssaScriptInfoNameCollisions+": ")), + astikit.WriteWithLabel("collisions", []byte(b.collisions)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if len(b.originalEditing) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameOriginalEditing+": "+b.originalEditing) + if _, err = c.Write( + astikit.WriteWithLabel("original editing label", []byte(ssaScriptInfoNameOriginalEditing+": ")), + astikit.WriteWithLabel("original editing", []byte(b.originalEditing)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if len(b.originalScript) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameOriginalScript+": "+b.originalScript) + if _, err = c.Write( + astikit.WriteWithLabel("original script label", []byte(ssaScriptInfoNameOriginalScript+": ")), + astikit.WriteWithLabel("original script", []byte(b.originalScript)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if len(b.originalTiming) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameOriginalTiming+": "+b.originalTiming) + if _, err = c.Write( + astikit.WriteWithLabel("original timing label", []byte(ssaScriptInfoNameOriginalTiming+": ")), + astikit.WriteWithLabel("original timing", []byte(b.originalTiming)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if len(b.originalTranslation) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameOriginalTranslation+": "+b.originalTranslation) + if _, err = c.Write( + astikit.WriteWithLabel("original translation label", []byte(ssaScriptInfoNameOriginalTranslation+": ")), + astikit.WriteWithLabel("original translation", []byte(b.originalTranslation)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if b.playDepth != nil { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNamePlayDepth+": "+strconv.Itoa(*b.playDepth)) + if _, err = c.Write( + astikit.WriteWithLabel("play depth label", []byte(ssaScriptInfoNamePlayDepth+": ")), + astikit.WriteWithLabel("play depth", []byte(strconv.Itoa(*b.playDepth))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if b.playResX != nil { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNamePlayResX+": "+strconv.Itoa(*b.playResX)) + if _, err = c.Write( + astikit.WriteWithLabel("play res x label", []byte(ssaScriptInfoNamePlayResX+": ")), + astikit.WriteWithLabel("play res x", []byte(strconv.Itoa(*b.playResX))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if b.playResY != nil { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNamePlayResY+": "+strconv.Itoa(*b.playResY)) + if _, err = c.Write( + astikit.WriteWithLabel("play res y label", []byte(ssaScriptInfoNamePlayResY+": ")), + astikit.WriteWithLabel("play res y", []byte(strconv.Itoa(*b.playResY))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if len(b.scriptType) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameScriptType+": "+b.scriptType) + if _, err = c.Write( + astikit.WriteWithLabel("script type label", []byte(ssaScriptInfoNameScriptType+": ")), + astikit.WriteWithLabel("script type", []byte(b.scriptType)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if len(b.scriptUpdatedBy) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameScriptUpdatedBy+": "+b.scriptUpdatedBy) + if _, err = c.Write( + astikit.WriteWithLabel("script updated by label", []byte(ssaScriptInfoNameScriptUpdatedBy+": ")), + astikit.WriteWithLabel("script updated by", []byte(b.scriptUpdatedBy)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if len(b.synchPoint) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameSynchPoint+": "+b.synchPoint) + if _, err = c.Write( + astikit.WriteWithLabel("synch point label", []byte(ssaScriptInfoNameSynchPoint+": ")), + astikit.WriteWithLabel("synch point", []byte(b.synchPoint)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if b.timer != nil { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameTimer+": "+strings.Replace(strconv.FormatFloat(*b.timer, 'f', -1, 64), ".", ",", -1)) + if _, err = c.Write( + astikit.WriteWithLabel("timer label", []byte(ssaScriptInfoNameTimer+": ")), + astikit.WriteWithLabel("timer", []byte(strings.Replace(strconv.FormatFloat(*b.timer, 'f', -1, 64), ".", ",", -1))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if len(b.title) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameTitle+": "+b.title) + if _, err = c.Write( + astikit.WriteWithLabel("title label", []byte(ssaScriptInfoNameTitle+": ")), + astikit.WriteWithLabel("title", []byte(b.title)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if len(b.updateDetails) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameUpdateDetails+": "+b.updateDetails) + if _, err = c.Write( + astikit.WriteWithLabel("update details label", []byte(ssaScriptInfoNameUpdateDetails+": ")), + astikit.WriteWithLabel("update details", []byte(b.updateDetails)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } if len(b.wrapStyle) > 0 { - o = appendStringToBytesWithNewLine(o, ssaScriptInfoNameWrapStyle+": "+b.wrapStyle) + if _, err = c.Write( + astikit.WriteWithLabel("wrap style label", []byte(ssaScriptInfoNameWrapStyle+": ")), + astikit.WriteWithLabel("wrap style", []byte(b.wrapStyle)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } return } @@ -1173,9 +1273,12 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { return } + // Init chainer + c := astikit.NewWriteChainer(o) + // Write Script Info block var si = newSSAScriptInfo(s.Metadata) - if _, err = o.Write(si.bytes()); err != nil { + if err = si.write(c); err != nil { err = fmt.Errorf("astisub: writing script info block failed: %w", err) return } @@ -1185,9 +1288,12 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { // Write Styles block if len(s.Styles) > 0 { // Header - var b = []byte("\n[V4 Styles]\n") + var header = "\n[V4 Styles]\n" if v4plus { - b = []byte("\n[V4+ Styles]\n") + header = "\n[V4+ Styles]\n" + } + if _, err = c.Write(astikit.WriteWithLabel("styles header", []byte(header))); err != nil { + return } // Format @@ -1201,25 +1307,33 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { styles[ss.name] = ss styleNames = append(styleNames, ss.name) } - b = append(b, []byte("Format: "+strings.Join(format, ", ")+"\n")...) + if _, err = c.Write( + astikit.WriteWithLabel("styles format label", []byte("Format: ")), + astikit.WriteWithLabel("styles format", []byte(strings.Join(format, ", "))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } // Styles sort.Strings(styleNames) for _, n := range styleNames { - b = append(b, []byte("Style: "+styles[n].string(format)+"\n")...) - } - - // Write - if _, err = o.Write(b); err != nil { - err = fmt.Errorf("astisub: writing styles block failed: %w", err) - return + if _, err = c.Write( + astikit.WriteWithLabel("style label", []byte("Style: ")), + astikit.WriteWithLabel("style", []byte(styles[n].string(format))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } } // Write Events block if len(s.Items) > 0 { // Header - var b = []byte("\n[Events]\n") + if _, err = c.Write(astikit.WriteWithLabel("events header", []byte("\n[Events]\n"))); err != nil { + return + } // Format // We need to declare those 9 columns here otherwise VLC doesn't display subtitles properly @@ -1242,17 +1356,23 @@ func (s Subtitles) WriteToSSA(o io.Writer) (err error) { events = append(events, newSSAEventFromItem(*i)) } format = append(format, ssaEventFormatNameText) - b = append(b, []byte("Format: "+strings.Join(format, ", ")+"\n")...) + if _, err = c.Write( + astikit.WriteWithLabel("events format label", []byte("Format: ")), + astikit.WriteWithLabel("events format", []byte(strings.Join(format, ", "))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } // Styles for _, e := range events { - b = append(b, []byte(ssaEventCategoryDialogue+": "+e.string(format)+"\n")...) - } - - // Write - if _, err = o.Write(b); err != nil { - err = fmt.Errorf("astisub: writing events block failed: %w", err) - return + if _, err = c.Write( + astikit.WriteWithLabel("event dialogue label", []byte(ssaEventCategoryDialogue+": ")), + astikit.WriteWithLabel("event dialogue", []byte(e.string(format))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } } } return diff --git a/stl.go b/stl.go index 14e05eb..dcf7d4a 100644 --- a/stl.go +++ b/stl.go @@ -969,18 +969,19 @@ func (s Subtitles) WriteToSTL(o io.Writer) (err error) { return } + // Init chainer + c := astikit.NewWriteChainer(o) + // Write GSI block var g = newGSIBlock(s) - if _, err = o.Write(g.bytes()); err != nil { - err = fmt.Errorf("astisub: writing gsi block failed: %w", err) + if _, err = c.Write(astikit.WriteWithLabel("gsi block", g.bytes())); err != nil { return } // Loop through items for idx, item := range s.Items { // Write tti block - if _, err = o.Write(newTTIBlock(item, idx+1, g.displayStandardCode).bytes(g)); err != nil { - err = fmt.Errorf("astisub: writing tti block #%d failed: %w", idx+1, err) + if _, err = c.Write(astikit.WriteWithLabel("tti block", newTTIBlock(item, idx+1, g.displayStandardCode).bytes(g))); err != nil { return } } diff --git a/subtitles.go b/subtitles.go index ea1e853..07c2969 100644 --- a/subtitles.go +++ b/subtitles.go @@ -9,6 +9,7 @@ import ( "math" "os" "path/filepath" + "reflect" "sort" "strconv" "strings" @@ -1000,6 +1001,76 @@ func (s *Subtitles) Order() { }) } +// ClipFrom clip items from input time +func (s *Subtitles) ClipFrom(cf time.Duration) { + newIndex := 0 + var items []*Item + for index := 0; index < len(s.Items); index++ { + s.Items[index].StartAt -= cf + s.Items[index].EndAt -= cf + s.Items[index].Index = newIndex + if s.Items[index].StartAt < 0 { + s.Items[index].StartAt = 0 + } + if s.Items[index].EndAt > 0 { + items = append(items, s.Items[index]) + } + } + s.Items = items +} + +func copy(source interface{}, destin interface{}) { + x := reflect.ValueOf(source) + if x.Kind() == reflect.Ptr { + starX := x.Elem() + y := reflect.New(starX.Type()) + starY := y.Elem() + starY.Set(starX) + reflect.ValueOf(destin).Elem().Set(y.Elem()) + } +} + +// Clone subtitles +func (s *Subtitles) Clone() *Subtitles { + sub := &Subtitles{} + copy(s.Metadata, sub.Metadata) + for k, r := range s.Regions { + copy(r, sub.Regions[k]) + } + for k, r := range s.Styles { + copy(r, sub.Styles[k]) + } + for i := 0; i < len(s.Items); i++ { + n := &Item{} + copy(s.Items[i], n) + sub.Items = append(sub.Items, n) + } + return sub +} + +// ClipFrom clip items until input time +func (s *Subtitles) ClipTo(ct time.Duration) { + lastIndex := 0 + for index := 0; index < len(s.Items); index++ { + lastIndex = index + if s.Items[index].StartAt > ct { + break + } + if s.Items[index].EndAt > ct { + s.Items[index].EndAt = ct + break + } + } + s.Items = s.Items[:lastIndex+1] +} + +// FixIndex fix item index +func (s *Subtitles) FixIndex() { + for i := 0; i < len(s.Items); i++ { + s.Items[i].Index = i + 1 + } +} + // RemoveStyling removes the styling from the subtitles func (s *Subtitles) RemoveStyling() { s.Regions = map[string]*Region{} diff --git a/subtitles_test.go b/subtitles_test.go index 749105d..d2b8fc0 100644 --- a/subtitles_test.go +++ b/subtitles_test.go @@ -280,6 +280,32 @@ func TestSubtitles_RemoveStyling(t *testing.T) { }, s) } +func TestSubtitles_FixIndex(t *testing.T) { + var s = mockSubtitles() + s.FixIndex() + for i := 0; i < len(s.Items); i++ { + assert.Equal(t, i+1, s.Items[i].Index) + } +} + +func TestSubtitles_ClipTo(t *testing.T) { + var s = mockSubtitles() + s.ClipTo(2 * time.Second) + assert.Equal(t, 1, len(s.Items)) + assert.Equal(t, 1*time.Second, s.Items[0].StartAt) + assert.Equal(t, 2*time.Second, s.Items[0].EndAt) +} + +func TestSubtitles_ClipFrom(t *testing.T) { + var s = mockSubtitles() + s.ClipFrom(2 * time.Second) + assert.Equal(t, 2, len(s.Items)) + assert.Equal(t, 0*time.Second, s.Items[0].StartAt) + assert.Equal(t, 1*time.Second, s.Items[0].EndAt) + assert.Equal(t, 1*time.Second, s.Items[1].StartAt) + assert.Equal(t, 5*time.Second, s.Items[1].EndAt) +} + func TestSubtitles_ApplyLinearCorrection(t *testing.T) { s := &astisub.Subtitles{Items: []*astisub.Item{ { diff --git a/webvtt.go b/webvtt.go index 8dfd4ca..2f0aecb 100644 --- a/webvtt.go +++ b/webvtt.go @@ -13,6 +13,8 @@ import ( "unicode/utf8" "golang.org/x/net/html" + + "github.com/asticode/go-astikit" ) // https://www.w3.org/TR/webvtt1/ @@ -412,11 +414,12 @@ func parseTextWebVTT(i string, sa *StyleAttributes) (o Line) { } // Push the tag to stack - sa.WebVTTTags = append(sa.WebVTTTags, WebVTTTag{ + tag := WebVTTTag{ Name: tagName, Classes: classes, Annotation: annotation, - }) + } + sa.WebVTTTags = append(sa.WebVTTTags, tag) } case html.TextToken: @@ -424,7 +427,9 @@ func parseTextWebVTT(i string, sa *StyleAttributes) (o Line) { var styleAttributes *StyleAttributes if len(sa.WebVTTTags) > 0 { tags := make([]WebVTTTag, len(sa.WebVTTTags)) - copy(tags, sa.WebVTTTags) + for i, t := range sa.WebVTTTags { + tags[i] = t + } styleAttributes = &StyleAttributes{ WebVTTTags: tags, } @@ -490,6 +495,7 @@ func formatDurationWebVTT(i time.Duration) string { } // WriteToWebVTT writes subtitles in .vtt format +// if set true in second args write index as item index func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { // Do not write anything if no subtitles if len(s.Items) == 0 { @@ -497,19 +503,29 @@ func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { return } + // Init chainer + c := astikit.NewWriteChainer(o) + // Add header - var c []byte - c = append(c, []byte("WEBVTT")...) + if _, err = c.Write(astikit.WriteWithLabel("header", []byte("WEBVTT"))); err != nil { + return + } // Write X-TIMESTAMP-MAP if set if s.Metadata != nil { webVTTTimestampMap := s.Metadata.WebVTTTimestampMap if webVTTTimestampMap != nil { - c = append(c, []byte("\n")...) - c = append(c, []byte(webVTTTimestampMap.String())...) + if _, err = c.Write( + astikit.WriteWithLabel("newline", []byte("\n")), + astikit.WriteWithLabel("timestamp map", []byte(webVTTTimestampMap.String())), + ); err != nil { + return + } } } - c = append(c, []byte("\n\n")...) + if _, err = c.Write(astikit.WriteWithLabel("newline", []byte("\n\n"))); err != nil { + return + } var style []string for _, s := range s.Styles { @@ -519,7 +535,9 @@ func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { } if len(style) > 0 { - c = append(c, []byte(fmt.Sprintf("STYLE\n%s\n\n", strings.Join(style, "\n")))...) + if _, err = c.Write(astikit.WriteWithLabel("style", []byte(fmt.Sprintf("STYLE\n%s\n\n", strings.Join(style, "\n"))))); err != nil { + return + } } // Add regions @@ -530,137 +548,237 @@ func (s Subtitles) WriteToWebVTT(o io.Writer) (err error) { sort.Strings(k) for _, id := range k { - c = append(c, []byte("Region: id="+s.Regions[id].ID)...) - if s.Regions[id].InlineStyle.WebVTTLines != 0 { - c = append(c, bytesSpace...) - c = append(c, []byte("lines="+strconv.Itoa(s.Regions[id].InlineStyle.WebVTTLines))...) - } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTLines != 0 { - c = append(c, bytesSpace...) - c = append(c, []byte("lines="+strconv.Itoa(s.Regions[id].Style.InlineStyle.WebVTTLines))...) - } - if s.Regions[id].InlineStyle.WebVTTRegionAnchor != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("regionanchor="+s.Regions[id].InlineStyle.WebVTTRegionAnchor)...) - } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTRegionAnchor != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("regionanchor="+s.Regions[id].Style.InlineStyle.WebVTTRegionAnchor)...) - } - if s.Regions[id].InlineStyle.WebVTTScroll != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("scroll="+s.Regions[id].InlineStyle.WebVTTScroll)...) - } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTScroll != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("scroll="+s.Regions[id].Style.InlineStyle.WebVTTScroll)...) - } - if s.Regions[id].InlineStyle.WebVTTViewportAnchor != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("viewportanchor="+s.Regions[id].InlineStyle.WebVTTViewportAnchor)...) - } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTViewportAnchor != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("viewportanchor="+s.Regions[id].Style.InlineStyle.WebVTTViewportAnchor)...) - } - if s.Regions[id].InlineStyle.WebVTTWidth != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("width="+s.Regions[id].InlineStyle.WebVTTWidth)...) - } else if s.Regions[id].Style != nil && s.Regions[id].Style.InlineStyle != nil && s.Regions[id].Style.InlineStyle.WebVTTWidth != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("width="+s.Regions[id].Style.InlineStyle.WebVTTWidth)...) - } - c = append(c, bytesLineSeparator...) + var r = s.Regions[id] + if _, err = c.Write(astikit.WriteWithLabel("region id", []byte("Region: id="+r.ID))); err != nil { + return + } + + // Lines + lines := r.InlineStyle.WebVTTLines + if lines == 0 && r.Style != nil && r.Style.InlineStyle != nil { + lines = r.Style.InlineStyle.WebVTTLines + } + if lines != 0 { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("lines", []byte("lines="+strconv.Itoa(lines))), + ); err != nil { + return + } + } + + // Region anchor + ra := r.InlineStyle.WebVTTRegionAnchor + if ra == "" && r.Style != nil && r.Style.InlineStyle != nil { + ra = r.Style.InlineStyle.WebVTTRegionAnchor + } + if ra != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("regionanchor", []byte("regionanchor="+ra)), + ); err != nil { + return + } + } + + // Scroll + scroll := r.InlineStyle.WebVTTScroll + if scroll == "" && r.Style != nil && r.Style.InlineStyle != nil { + scroll = r.Style.InlineStyle.WebVTTScroll + } + if scroll != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("scroll", []byte("scroll="+scroll)), + ); err != nil { + return + } + } + + // Viewport anchor + va := r.InlineStyle.WebVTTViewportAnchor + if va == "" && r.Style != nil && r.Style.InlineStyle != nil { + va = r.Style.InlineStyle.WebVTTViewportAnchor + } + if va != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("viewportanchor", []byte("viewportanchor="+va)), + ); err != nil { + return + } + } + + // Width + width := r.InlineStyle.WebVTTWidth + if width == "" && r.Style != nil && r.Style.InlineStyle != nil { + width = r.Style.InlineStyle.WebVTTWidth + } + if width != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("width", []byte("width="+width)), + ); err != nil { + return + } + } + + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { + return + } } if len(s.Regions) > 0 { - c = append(c, bytesLineSeparator...) + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { + return + } } // Loop through subtitles for index, item := range s.Items { // Add comments if len(item.Comments) > 0 { - c = append(c, []byte("NOTE ")...) + if _, err = c.Write(astikit.WriteWithLabel("note", []byte("NOTE "))); err != nil { + return + } for _, comment := range item.Comments { - c = append(c, []byte(comment)...) - c = append(c, bytesLineSeparator...) + if _, err = c.Write( + astikit.WriteWithLabel("comment", []byte(comment)), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + ); err != nil { + return + } + } + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { + return } - c = append(c, bytesLineSeparator...) } // Add time boundaries - c = append(c, []byte(strconv.Itoa(index+1))...) - c = append(c, bytesLineSeparator...) - c = append(c, []byte(formatDurationWebVTT(item.StartAt))...) - c = append(c, bytesWebVTTTimeBoundariesSeparator...) - c = append(c, []byte(formatDurationWebVTT(item.EndAt))...) + if _, err = c.Write( + astikit.WriteWithLabel("index", []byte(strconv.Itoa(index+1))), + astikit.WriteWithLabel("line separator", bytesLineSeparator), + astikit.WriteWithLabel("start at", []byte(formatDurationWebVTT(item.StartAt))), + astikit.WriteWithLabel("time boundaries separator", bytesWebVTTTimeBoundariesSeparator), + astikit.WriteWithLabel("end at", []byte(formatDurationWebVTT(item.EndAt))), + ); err != nil { + return + } // Add styles if item.InlineStyle != nil { - if item.InlineStyle.WebVTTAlign != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("align:"+item.InlineStyle.WebVTTAlign)...) - } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTAlign != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("align:"+item.Style.InlineStyle.WebVTTAlign)...) - } - if item.InlineStyle.WebVTTLine != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("line:"+item.InlineStyle.WebVTTLine)...) - } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTLine != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("line:"+item.Style.InlineStyle.WebVTTLine)...) - } - if item.InlineStyle.WebVTTPosition != nil { - c = append(c, bytesSpace...) - c = append(c, []byte("position:"+item.InlineStyle.WebVTTPosition.String())...) - } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTPosition != nil { - c = append(c, bytesSpace...) - c = append(c, []byte("position:"+item.Style.InlineStyle.WebVTTPosition.String())...) + // Align + align := item.InlineStyle.WebVTTAlign + if align == "" && item.Style != nil && item.Style.InlineStyle != nil { + align = item.Style.InlineStyle.WebVTTAlign + } + if align != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("align", []byte("align:"+align)), + ); err != nil { + return + } } + + // Line + line := item.InlineStyle.WebVTTLine + if line == "" && item.Style != nil && item.Style.InlineStyle != nil { + line = item.Style.InlineStyle.WebVTTLine + } + if line != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("line", []byte("line:"+line)), + ); err != nil { + return + } + } + + // Position + pos := item.InlineStyle.WebVTTPosition + if pos == nil && item.Style != nil && item.Style.InlineStyle != nil { + pos = item.Style.InlineStyle.WebVTTPosition + } + if pos != nil { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("position", []byte("position:"+pos.String())), + ); err != nil { + return + } + } + + // Region if item.Region != nil { - c = append(c, bytesSpace...) - c = append(c, []byte("region:"+item.Region.ID)...) + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("region", []byte("region:"+item.Region.ID)), + ); err != nil { + return + } } - if item.InlineStyle.WebVTTSize != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("size:"+item.InlineStyle.WebVTTSize)...) - } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTSize != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("size:"+item.Style.InlineStyle.WebVTTSize)...) + + // Size + size := item.InlineStyle.WebVTTSize + if size == "" && item.Style != nil && item.Style.InlineStyle != nil { + size = item.Style.InlineStyle.WebVTTSize } - if item.InlineStyle.WebVTTVertical != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("vertical:"+item.InlineStyle.WebVTTVertical)...) - } else if item.Style != nil && item.Style.InlineStyle != nil && item.Style.InlineStyle.WebVTTVertical != "" { - c = append(c, bytesSpace...) - c = append(c, []byte("vertical:"+item.Style.InlineStyle.WebVTTVertical)...) + if size != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("size", []byte("size:"+size)), + ); err != nil { + return + } + } + + // Vertical + vertical := item.InlineStyle.WebVTTVertical + if vertical == "" && item.Style != nil && item.Style.InlineStyle != nil { + vertical = item.Style.InlineStyle.WebVTTVertical + } + if vertical != "" { + if _, err = c.Write( + astikit.WriteWithLabel("space", bytesSpace), + astikit.WriteWithLabel("vertical", []byte("vertical:"+vertical)), + ); err != nil { + return + } } } - // Add new line - c = append(c, bytesLineSeparator...) + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { + return + } - // Loop through lines + // Add lines for _, l := range item.Lines { - c = append(c, l.webVTTBytes()...) + if err = l.writeWebVTT(c); err != nil { + return + } } // Add new line - c = append(c, bytesLineSeparator...) - } - - // Remove last new line - c = c[:len(c)-1] - - // Write - if _, err = o.Write(c); err != nil { - err = fmt.Errorf("astisub: writing failed: %w", err) - return + if index < len(s.Items)-1 { + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { + return + } + } } return } -func (l Line) webVTTBytes() (c []byte) { +func (l Line) writeWebVTT(c *astikit.WriteChainer) (err error) { + // Voice name if l.VoiceName != "" { - c = append(c, []byte("")...) + if _, err = c.Write( + astikit.WriteWithLabel("voice start", []byte("")), + ); err != nil { + return + } } + + // Items for idx := 0; idx < len(l.Items); idx++ { var previous, next *LineItem if idx > 0 { @@ -669,16 +787,23 @@ func (l Line) webVTTBytes() (c []byte) { if idx < len(l.Items)-1 { next = &l.Items[idx+1] } - c = append(c, l.Items[idx].webVTTBytes(previous, next)...) + if err = l.Items[idx].writeWebVTT(c, previous, next); err != nil { + return + } + } + + if _, err = c.Write(astikit.WriteWithLabel("line separator", bytesLineSeparator)); err != nil { + return } - c = append(c, bytesLineSeparator...) return } -func (li LineItem) webVTTBytes(previous, next *LineItem) (c []byte) { +func (li LineItem) writeWebVTT(c *astikit.WriteChainer, previous, next *LineItem) (err error) { // Add timestamp - if li.StartAt > 0 { - c = append(c, []byte("<"+formatDurationWebVTT(li.StartAt)+">")...) + if _, err = c.Write( + astikit.WriteWithCondition("start at", []byte("<"+formatDurationWebVTT(li.StartAt)+">"), li.StartAt > 0), + ); err != nil { + return } // Get color - only add TTMLColor-based tag if there are no WebVTT color tags @@ -698,30 +823,40 @@ func (li LineItem) webVTTBytes(previous, next *LineItem) (c []byte) { } } - // Append + // Write if color != "" { - c = append(c, []byte("")...) + if _, err = c.Write(astikit.WriteWithLabel("color start", []byte(""))); err != nil { + return + } } if li.InlineStyle != nil { for idx, tag := range li.InlineStyle.WebVTTTags { if previous != nil && previous.InlineStyle != nil && len(previous.InlineStyle.WebVTTTags) > idx && tag.Name == previous.InlineStyle.WebVTTTags[idx].Name { continue } - c = append(c, []byte(tag.startTag())...) + if _, err = c.Write(astikit.WriteWithLabel("tag start", []byte(tag.startTag()))); err != nil { + return + } } } - c = append(c, []byte(escapeHTML(li.Text))...) + if _, err = c.Write(astikit.WriteWithLabel("text", []byte(escapeHTML(li.Text)))); err != nil { + return + } if li.InlineStyle != nil { for i := len(li.InlineStyle.WebVTTTags) - 1; i >= 0; i-- { tag := li.InlineStyle.WebVTTTags[i] if next != nil && next.InlineStyle != nil && len(next.InlineStyle.WebVTTTags) > i && tag.Name == next.InlineStyle.WebVTTTags[i].Name { continue } - c = append(c, []byte(tag.endTag())...) + if _, err = c.Write(astikit.WriteWithLabel("tag end", []byte(tag.endTag()))); err != nil { + return + } } } if color != "" { - c = append(c, []byte("")...) + if _, err = c.Write(astikit.WriteWithLabel("color end", []byte(""))); err != nil { + return + } } return } diff --git a/webvtt_internal_test.go b/webvtt_internal_test.go index f77b718..28de9b0 100644 --- a/webvtt_internal_test.go +++ b/webvtt_internal_test.go @@ -1,12 +1,13 @@ package astisub import ( + "bytes" "strconv" "testing" "time" + "github.com/asticode/go-astikit" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestParseTextWebVTT(t *testing.T) { @@ -165,7 +166,8 @@ func TestCueVoiceSpanRegex(t *testing.T) { } func TestLineWebVTTBytes(t *testing.T) { - require.Equal(t, "1 2 3\n", string(Line{Items: []LineItem{ + w := &bytes.Buffer{} + err := Line{Items: []LineItem{ { InlineStyle: &StyleAttributes{WebVTTTags: []WebVTTTag{ {Name: "t1"}, @@ -185,5 +187,7 @@ func TestLineWebVTTBytes(t *testing.T) { }}, Text: " 3", }, - }}.webVTTBytes())) + }}.writeWebVTT(astikit.NewWriteChainer(w)) + assert.NoError(t, err) + assert.Equal(t, "1 2 3\n", w.String()) } diff --git a/webvtt_test.go b/webvtt_test.go index 0771cd4..f677363 100644 --- a/webvtt_test.go +++ b/webvtt_test.go @@ -347,3 +347,16 @@ Normal text with magenta and unknown color` assert.Nil(t, unknownColorItem.InlineStyle.TTMLColor) // Unknown color should not be converted assert.Nil(t, unknownColorItem.InlineStyle.TTMLBackgroundColor) } + +func BenchmarkWriteToWebVTT(b *testing.B) { + s, err := astisub.OpenFile("./testdata/example-in.vtt") + if err != nil { + b.Fatal(err) + } + w := &bytes.Buffer{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + w.Reset() + s.WriteToWebVTT(w) + } +}